English Русский 中文 Español Deutsch 日本語
Usando criptografia com aplicativos externos

Usando criptografia com aplicativos externos

MetaTrader 5Negociação | 1 dezembro 2020, 09:08
1 343 0
Andrei Novichkov
Andrei Novichkov

Introdução

A criptografia raramente é usada em programas MQL, e normalmente um trader não se defronta na prática com ela. Imaginemos o seguinte cenário: um copiador de sinais paranóico que realmente não quer que outra pessoa "ouça" os dados que está enviando, talvez só isso. Neste caso, é muito difícil imaginar a necessidade de criptografar/descriptografar tais dados nos locais em que eles não abandonam o terminal. Portanto, se um desenvolvedor quiser fazer isso neste caso, estaria expondo sua incompetência ao dar uma carga extra no terminal.

Logo, talvez não haja necessidade de criptografar? Claro, existe tal necessidade. Vejamos, por exemplo, o licenciamento. É fácil imaginar uma pequena empresa gerida por um desenvolvedor independente cujos produtos fazem sucesso. Para ele, a questão do licenciamento é muito relevante, tornando importante a necessidade de criptografar/descriptografar licenças.

Na licença será possível especificar os dados do usuário e uma lista editável de ferramentas disponibilizadas para ele. O indicador ou EA em questão arranca, verifica se há uma licença, bem como sua data de validade para o instrumento escolhido; consulta o servidor, renova a licença ou obtém uma nova, se necessário. Este provavelmente não é o caminho mais eficiente e seguro, mas neste artigo iremos usá-lo para dar uma ideia. Obviamente, neste caso, a leitura/registro da licença será feita por diferentes ferramentas de software, entre elas estão o terminal, um servidor remoto, os módulos de controle e os módulos de registro. Também é óbvio que elas podem ser escritas por pessoas diferentes, em momentos diferentes e em línguas diferentes.

O objetivo deste artigo é estudar os modos de criptografia/descriptografia em que um objeto criptografado por um programa escrito em C# ou C++ pode ter a garantia de ser descriptografado pelo terminal MetaTrader e vice-versa.

O artigo se destina a desenvolvedores com habilidades intermediárias e iniciantes.

Formulação do problema

Isso já foi brevemente discutido na introdução. Tentaremos simular uma solução para um problema real com a criação, criptografia e descriptografia de uma licença para vários instrumentos - indicadores e EAs. Além disso, consideraremos, sem importância, qual programa será usado para criptografar e qual para descriptografar. Por exemplo, é fácil imaginar um cenário em que a criação inicial de uma licença ocorre no computador do desenvolvedor, correções no departamento de vendas e a descriptografia no computador do trader. Obviamente, não deve haver erros por causa de algoritmos mal configurados.

Ao longo do caminho, juntamente com a solução do problema principal, abordaremos a complexa tarefa do licenciamento, considerando-a como secundária. De forma alguma pretendendo criar um objeto eficaz e funcional na forma de licença, em vez disso, proponho uma das diferentes variantes que pode e deve ser corrigida, desenvolvida ou considerada apenas como uma opção que não deve ser utilizada.

Dados de origem

Consultamos a documentação do terminal para trabalhar com os dados de origem. Existem duas funções padrão responsáveis por toda a criptografia/descriptografia:

int  CryptEncode(
   ENUM_CRYPT_METHOD   method,        // conversion method
   const uchar&        data[],        // source array
   const uchar&        key[],         // encryption key
   uchar&              result[]       // destination array
   );

int  CryptDecode(
   ENUM_CRYPT_METHOD   method,        // conversion method
   const uchar&        data[],        // source array
   const uchar&        key[],         // encryption key
   uchar&              result[]       // destination array
   );

O argumento method define como será realizada a criptografia/descriptografia. Neste caso, o que chama a atenção é os três valores que pode tomar o argumento method: CRYPT_AES128, CRYPT_AES256, CRYPT_DES. Todos eles representam algoritmos de criptografia simétricos com diferentes comprimentos de chave.

A nível deste artigo, vamos usar apenas um deles, nomeadamente CRYPT_AES128. Trata-se de um algoritmo de criptografia de bloco com uma chave de 128 bits (16 bytes). Trabalharemos com outros algoritmos de maneira semelhante.

O algoritmo AES (não apenas o selecionado, mas também aquele com um comprimento de chave diferente) possui várias configurações importantes que não são introduzidas nas funções acima. Entre elas está o modo de criptografia e adição (padding). Não vamos considerar em detalhes cada um deles, porém, precisaremos dos seus valores. Já agora, no terminal, é usado o modo de criptografia Electronic Codebook (ECB) e Padding igual a zero. Devemos agradecer aos colegas da parte em inglês do fórum por ter passado essa informação. Agora nossa tarefa será muito mais fácil de resolver.

Desenvolvendo o objeto de trabalho

Visto que aqui consideramos questões de criptografia/descriptografia aplicadas ao licenciamento, o objeto de trabalho será a licença. Ela deverá ser uma construção contendo informações sobre as várias ferramentas que estão na área de responsabilidade da licença. Obviamente, são necessárias informações sobre:

  1. A data de expiração da licença da ferramenta em questão.
  2. O nome da ferramenta.

Vamos criar a respectiva estrutura com os métodos de acompanhamento mais simples:

#define PRODMAXLENGTH 255

struct ea_user 
  {
   ea_user() {expired = -1;}
   datetime expired;                //License expiration (-1 - unlimited)
   int      namelength;             //Product name length
   char    uname[PRODMAXLENGTH];    //Product name
   void SetEAname(string name) 
     {
      namelength = StringToCharArray(name, uname);
     }
   string GetEAname() 
     {
      return CharArrayToString(uname, 0, namelength);
     }
   bool IsExpired() 
     {
      if (expired == -1) 
         return false; // NOT expired
      return expired <= TimeLocal();
     }
  };//struct ea_user


Eis algumas explicações:

  • O nome da ferramenta é armazenado como uma matriz de comprimento fixo.
  • O nome da ferramentas tem uma comprimento limitado — PRODMAXLENGTH.
  • Essa estrutura pode ser facilmente compactada numa matriz de bytes, o que deve ser feito antes de criptografar o objeto inteiro.

No entanto, essa estrutura por si só não é suficiente. Obviamente, deve haver informações acerca do usuário. É possível incluir essas informações na estrutura já descrita, porém, de maneira ineficaz, uma vez que um usuário pode ter uma licença para várias ferramentas ao mesmo tempo. É mais racional criar uma estrutura separada para o usuário e adicionar o número necessário de licenças da ferramenta. Assim, a cada usuário corresponde só uma licença contendo permissões e restrições para todas as ferramentas licenciadas.

Informações que podem estar contidas na estrutura que descreve o usuário:

  1. Seu identificador único. Seria possível salvar o nome, mas parece indesejável enviar dados pessoais novamente, ainda que de forma criptografada.
  2. Informações sobre as contas de usuário nas quais as ferramentas podem ser executadas.
  3. A data de expiração da licença do usuário. Este campo pode limitar o tempo de operação de todas as ferramentas disponíveis, mesmo as ilimitadas, ao tempo de serviço ao usuário como tal.
  4. Número de ferramentas licenciadas no terminal do usuário:
#define COUNTACC 5

struct user_lic {
   user_lic() {
      uid       = -1;
      log_count =  0;
      ea_count  =  0;
      expired   = -1;
      ArrayFill(logins, 0, COUNTACC, 0);
   }
   long uid;                       //User ID
   datetime expired;               //End of user service (-1 - unlimited)
   int  log_count;                 //The number of the user's accounts
   long logins[COUNTACC];          //User's accounts
   int  ea_count;                  //The number of licensed products
   bool AddLogin(long lg){
      if (log_count >= COUNTACC) return false;
      logins[log_count++] = lg;
      return true;
   }
   long GetLogin(int num) {
      if (num >= log_count) return -1;
      return logins[num];
   }
   bool IsExpired() {
      if (expired == -1) return false; // NOT expired
      return expired <= TimeLocal();
   }   
};//struct user_lic

Alguns esclarecimentos:

  • As contas de usuário são armazenadas numa matriz de comprimento fixo. Elas são representadas por números, embora, se necessário, as informações possam ser facilmente complementadas com os nomes do servidor ou da corretora, com os valores de ativação para uma conta individual.

Por enquanto, consideraremos duas estruturas de dados contendo informações abrangentes sobre o usuário e suas ferramentas. Cada uma delas é um tipo cuja instância pode ser processada pela funçãoStructToCharArray, o que é obrigatório.

Agora precisamos serializar os dados da estrutura numa matriz de bytes para, a seguir, serem criptografados. Isso será feito da seguinte maneira:

  • Criamos e inicializamos uma instância da estrutura user_lic.
  • Vamos serializá-la numa matriz de bytes.
  • Criamos e inicializamos uma ou várias instâncias da estrutura ea_user.
  • Vamos serializá-las na mesma matriz de bytes, aumentando seu tamanho e ajustando o campo ea_count.

Para realizar essas operações, criamos uma classe:

class CLic {

public:

   static int iSizeEauser;
   static int iSizeUserlic;
   
   CLic() {}
  ~CLic() {}
   
   int SetUser(const user_lic& header){
      Reset();
      if (!StructToCharArray(header, dest) ) return 0;
      return ArraySize(dest);
   }//int SetUser(user_lic& header)

   int AddEA(const ea_user& ea) {
      int c = ArraySize(dest);
      if (c == 0) return 0;
      uchar tmp[];
      if (!StructToCharArray(ea, tmp) ) return 0;
      ArrayInsert(dest, tmp, c);
      return ArraySize(dest);
   }//int AddEA(ea_user& ea)
   
   bool GetUser(user_lic& header) const {
      if (ArraySize(dest) < iSizeUserlic) return false;
      return CharArrayToStruct(header, dest);
   }//bool GetUser(user_lic& header)
   
   //num - 0 based
   bool GetEA(int num, ea_user& ea) const {
      int index = iSizeUserlic + num * iSizeEauser;
      if (ArraySize(dest) < index + iSizeEauser) return false;
      return CharArrayToStruct(ea, dest, index);
   }//bool GetEA(int num, ea_user& ea)
   
   int Encode(ENUM_CRYPT_METHOD method, string key, uchar&  buffer[]) const {
      if (ArraySize(dest) < iSizeUserlic) return 0;
      if(!IsKeyCorrect(method, key) ) return 0;      
      uchar k[];
      StringToCharArray(key, k);
      return CryptEncode(method, dest, k, buffer); 
   }
   
   int Decode(ENUM_CRYPT_METHOD method, string key, uchar&  buffer[]) {
      Reset();
      if(!IsKeyCorrect(method, key) ) return 0;
      uchar k[];
      StringToCharArray(key, k);
      return CryptDecode(method, buffer, k, dest); 
   }   

protected:
   void Reset() {ArrayResize(dest, 0);}
   
   bool IsKeyCorrect(ENUM_CRYPT_METHOD method, string key) const {
      int len = StringLen(key);
      switch (method) {
         case CRYPT_AES128:
            if (len == 16) return true;
            break;
         case CRYPT_AES256:
            if (len == 32) return true;
            break;
         case CRYPT_DES:
            if (len == 7) return true;
            break;
      }
#ifdef __DEBUG_USERMQH__
   Print("Key length is incorrect: ",len);
#endif       
      return false;
   }//bool IsKeyCorrect(ENUM_CRYPT_METHOD method, string key)
   
private:
   uchar dest[];
};//class CLic

   static int CLic::iSizeEauser  = sizeof(ea_user);
   static int CLic::iSizeUserlic = sizeof(user_lic);

À classe foram adicionadas duas funções para criptografia/descriptografia com argumentos bastante óbvios e uma função segura para verificar o comprimento da chave. Pelo seu código você podemos ver que, por exemplo, para o método CRYPT_AES128 o comprimento da chave deve ser de 16 bytes. Na verdade, deve ter pelo menos de 16 bytes. Provavelmente, no futuro, ocorrerá algum tipo de normalização oculta que o desenvolvedor não poderá ver. Porém, em nosso trabalho, não dependeremos disso e definiremos o comprimento da chave estritamente.

Finalmente, já é possível criptografar a matriz de bytes resultante e salvá-la num arquivo binário. Este arquivo deve ser armazenado na pasta File do terminal, obedecendo as regras gerais, e, se necessário, pode ser lido e descriptografado:

bool CreateLic(ENUM_CRYPT_METHOD method, string key, CLic& li, string licname) {
   uchar cd[];
   if (li.Encode(method, key, cd) == 0) return false;
   int h = FileOpen(licname, FILE_WRITE | FILE_BIN);
   if (h == INVALID_HANDLE) {
#ifdef __DEBUG_USERMQH__
      Print("File create failed: ",licname);
#endif    
      return false;
   }
   FileWriteArray(h, cd);
   FileClose(h);  
#ifdef __DEBUG_USERMQH__    
   li.SaveArray();
#endif    
   return true;
}// bool CreateLic(ENUM_CRYPT_METHOD method, string key, const CLic& li, string licname)


bool ReadLic(ENUM_CRYPT_METHOD method, string key, CLic& li, string licname) {
   int h = FileOpen(licname, FILE_READ | FILE_BIN);
   if (h == INVALID_HANDLE) {
#ifdef __DEBUG_USERMQH__
      Print("File open failed: ",licname);
#endif    
      return false;
   }
   uchar cd[];
   FileReadArray(h,cd);
   if (ArraySize(cd) < CLic::iSizeUserlic) {
#ifdef __DEBUG_USERMQH__
      Print("File too small: ",licname);
#endif    
      return false;
   }
   li.Decode(method, key, cd);
   FileClose(h);
   return true;
}// bool ReadLic(ENUM_CRYPT_METHOD method, string key, CLic& li, string licname)

Ambas as funções são extremamente simples e autoexplicativas. O arquivo CryptoMQL.zip anexado contém dois scripts e um arquivo de biblioteca que executa a criptografia/descriptografia, bem como um arquivo de licença criptografado lic.txt.

Projeto em C#

Vamos criar um projeto em C# simples para simular o processo de descriptografia e edição com sendo levado a cabo por outro programa. Vamos usar o Visual Studio 2017 e criar um aplicativo de console para o .NET Framework. Verificamos a conexão do assembly System.Security e do espaço System.Security.Cryptography. 

Embarcamos na escrita do código e rapidamente nos defrontamos com um problema, particularmente diferentes formatos de hora para MQL e C#. No entanto, este problema já foi abordado e resolvido neste artigo. Agradecemos ao autor do artigo pela ajuda, e usamos a classe MtConverter que ele desenvolveu para o seu projecto

Criamos a duas classes EaUser e UserLic com campos semelhantes aos das estruturas ea_user e user_lic. A tarefa consiste em descriptografar a licença criada pelo terminal (o arquivo lic.txt), analisar os dados recebidos, modificar os objetos e criptografá-los novamente com a criação de um novo arquivo. Esta tarefa não apresenta nenhuma dificuldade especial, se realizarmos cuidadosamente a configuração dos modos de criptografia/descriptografia. É assim que fica o trecho de código correspondente:

            using (Aes aesAlg = Aes.Create())
            {
                aesAlg.Key = Key;
                aesAlg.IV = IV;
                aesAlg.Mode = CipherMode.ECB;
                aesAlg.Padding = PaddingMode.Zeros;
                ..................................

Preste atenção nas duas últimas linhas, onde o modo de criptografia (ECB) e o preenchimento são definidos. Usamos as informações que já temos sobre as configurações para esses modos. A primeira linha do bloco referente à instalação da chave deve ser clara. A mesma chave é usada para criptografia no terminal, mas convertida numa matriz de bytes:

            string skey = "qwertyuiopasdfgh";
            byte[] Key  = Encoding.ASCII.GetBytes(s: skey);

Reparemos especialmente na linha com a configuração de parâmetro "IV". Este é o chamado "vetor de inicialização", isto é, um número aleatório que participa de todos os modos de criptografia, exceto o modo ECB. Por isso, simplesmente criamos neste ponto uma matriz do comprimento desejado:

byte[] iv   = new byte[16];

Além disso, observe que em C# a situação com o comprimento da chave é diferente do que em MQL. Se o comprimento da chave (neste caso, a linha "qwertyuiopasdfgh") for maior que 16, será iniciada uma exceção. Por isso, a decisão de controlar estritamente o comprimento da chave no código MQL foi feita corretamente.

O resto é bem simples. Lemos o arquivo binário -> descriptografamos o fluxo -> preenchemos a instância criada da classe UserLic com ajuda de BinaryReader. É bem possível que um resultado semelhante seja alcançado tornando as respectivas classes serializáveis. Nada proíbe aqueles que desejam verificar de forma independente tal afirmação.

Modificamos qualquer campo, neste caso vamos alterar o ID do usuário. Criptografamos os dados da mesma maneira e criamos um novo arquivo "lic_C#.txt". Todo o trabalho acima é feito pelas duas funções estáticas EncryptToFile_Aes e DecryptFromFile_Aes do projeto. Além disso, para depuração, foram adicionadas mais duas funções semelhantes que trabalham com matrizes de bytes:EncryptToArray_Aes e DecryptFromArray_Aes...

O projeto CryptoC#.zip com todos os arquivos está anexado ao artigo.

Claro, mesmo um desenvolvedor com pouca experiência notará falhas no projeto:

  • Não há verificações necessárias nos argumentos das funções chamadas.
  • Não há tratamento de exceções.
  • Tem modo de operação de fluxo único.

Esses comentários, sem dúvida justos, neste caso, não foram refletidos no código pelo fato de que nosso objetivo não é criar uma aplicação de tipo industrial. Já uma camada de código adicional, bastante espessa, distrairá a atenção dos interessados da essência do problema que está sendo resolvido.

Projeto C++

O projeto final neste artigo será criado nesta linguagem. Vamos criar um aplicativo de console no Visual Studio 2017. Não temos nenhum suporte para criptografia/descriptografia "fora da caixa", então temos que anexar a conhecida biblioteca OpenSSL, para a qual o pacote de instalação do OpenSSL deve ser baixado e instalado no computador. Como resultado, todas as bibliotecas OpenSSL e "includes" que devem ser anexados ao projeto criado se tornam disponíveis para o desenvolvedor. Qualquer pessoa pode se atualizar sobre como anexar bibliotecas ao seu projeto lendo este artigo. Infelizmente, a documentação do OpenSSL deixa muito a desejar, para dizer o mínimo, mas, mesmo assim, temos que usá-la, por não termos mais nada.

Depois de anexar as bibliotecas, vamos começar a escrever o código. A primeira coisa a fazer é re-descrever as já familiares duas estruturas:

constexpr size_t PRODMAXLENGTH = 255;

#pragma pack(1)
typedef struct EA_USER {
        EA_USER();
        EA_USER(std::string name);
        EA_USER(EA_USER& eauser);
        std::time_t  expired;
        long   namelength;
        char eaname[PRODMAXLENGTH];
        std::string GetName();
        void SetName(std::string newName);
        std::string GetTimeExpired();
        std::string ToString();
        size_t ToArray(byte* pbyte);
        constexpr size_t GetSize() noexcept;
        friend std::ostream& operator<< (std::ostream& out, EA_USER& eauser);
        friend std::istream& operator>> (std::istream& in, EA_USER& eauser);
}EAUSER, *PEAUSER;
#pragma pack()

constexpr size_t COUNTACC = 5;

#pragma pack(1)
typedef struct USER_LIC
{
        using PEAUNIC = std::unique_ptr<EAUSER>;

        USER_LIC();
        USER_LIC(USER_LIC&& ul);
        USER_LIC(const byte* pbyte);
        int64_t uid;                  
        std::time_t expired;       
        long log_count;             
        int64_t logins[COUNTACC];     
        long ea_count;             
        std::vector<PEAUNIC> pEa;

        std::string GetTimeExpired();
        std::string ToString();
        size_t ToArray(byte* pbyte);
        void AddEA(EA_USER eau);
        bool AddAcc(long newAcc);
        size_t GetSize();

        friend std::ostream& operator<< (std::ostream& out, USER_LIC& ul);
        friend std::istream& operator>> (std::istream& in, USER_LIC& ul);

	USER_LIC& operator = (const USER_LIC&) = delete;
	USER_LIC(const USER_LIC&) = delete;
} USERLIC, *PUSERLIC;
#pragma pack()


O código é mais complexo do que o projeto em C#, mas não tão assim. Talvez leitores atentos notem alguma diferença nos tipos de certos campos. Por exemplo, neste projeto, um campo com uma matriz de contas tem o tipo de matriz int64_t, já no arquivo de inclusão em MQL é especificada uma matriz de tipo long. A resposta está no tamanho dos tipos correspondentes, a falta de controle sobre esse recurso pode ser a causa de erros difíceis de detectar. Existem também algumas simplificações, podemos não ter que lidar com a conversão de tempo, que tínhamos que fazer no projeto anterior.

Neste projeto, também podemos enfrentar um outro problema, particularmente a definição incorreta do comprimento da chave. Para resolvê-lo, vamos incluir uma função no projeto:

std::string AES_NormalizeKey(const void *const apBuffer, size_t aSize)

Esta função irá "cortar" a matriz appBuffer para o comprimento desejado aSize. Além disso, vamos escrever uma função auxiliar útil:

void handleErrors(void)
{
        ERR_print_errors_fp(stderr);
}

Esta função fornecerá ao desenvolvedor a descriptografia dos códigos de erro da biblioteca OpenSSL. E, finalmente, algumas funções que farão o trabalho principal:

int aes_decrypt(const byte* key, const byte* iv, const byte* pCtext, byte* pRtext, int iTextsz)
int aes_encrypt(const byte* key, const byte* iv, const byte* pPtext, byte* pCtext, int iTextsz)

Não vale a pena mostrar os métodos, quem quiser poderá vê-los no projeto anexo. No entanto, é necessário ressaltar alguns pontos essenciais:

  • Aqui, como antes, não é usado nenhum vetor de inicialização. Criamos uma matriz com o tamanho desejado e a transferimos ao ponto da chamada.
  • Definimos este modo por meio da chamada:
    EVP_CIPHER_CTX_set_padding(ctx.get(), 0);
    
    
    Além disso, passamos exatamente "zero", e não assim:
    EVP_CIPHER_CTX_set_padding(ctx.get(), EVP_PADDING_ZERO);
    
    
    Os problemas com o complemento não param por aí. Acontece que, caso o valor de preenchimento seja zero (como em nosso projeto), o próprio desenvolvedor deve cuidar para que o comprimento do objeto criptografado seja um múltiplo de BLOCK_SIZE = AES_BLOCK_SIZE, ou seja, 16 bytes. Por isso, antes de chamar aes_encrypt(......), o código deve fornecer o alinhamento da matriz a ser criptografada com base no valor especificado.

Aqui, realizaremos a mesma sequência de ações do projeto anterior:

  • Descriptografamos o arquivo resultante, editamos e criptografamos novamente. Nesse caso, adicionaremos informações sobre outra conta de usuário à licença.
  • Obtemos o último arquivo criptografado desta série, lic_C++.txt . Vale a pena ressaltar que o tamanho do arquivo mudou. Este é exatamente o tamanho do bloco (16 bytes) que foi adicionado durante o alinhamento.

Todos os arquivos de origem do projeto estão anexados ao arquivo CryptoC++.zip

    Última verificação e resultados

    Falta a fase final. Transferimos apenas o arquivo lic_C++.txt recém-criptografado para a pasta File do MetaTrader e o descriptografamos com o decryptuser.mq5. Obtemos o resultado que esperávamos, um arquivo descriptografado com sucesso, apesar da alteração no comprimento.

    Qual é o resultado? O mais importante é que os parâmetros de criptografia/descriptografia foram esclarecidos, tornando possível transferir arquivos criptografados de um programa para outro. Obviamente, no futuro será possível afirmar que se a criptografia/descriptografia falhar em qualquer caso, o problema estará nos erros dos programas aplicativos.

      Hashes

      Provavelmente a esmagadora maioria sabe que a criptografia não se limita apenas à criptografia/descriptografia. Vejamos um primitivo criptográfico como o hashing. Ele é um processo em que uma matriz arbitrária é convertida numa matriz de comprimento fixo, matriz essa chamada de hash, e cuja função de transformação é chamada de função hash. Duas matrizes iniciais que diferem entre si em pelo menos um bit darão como resultado hashes completamente diferentes, que poderão ser usados para identificação e comparação.

      Para uma compreensão mais clara, aqui está um exemplo específico. O usuário se cadastra no site, insere seus dados de identificação. Os dados são armazenados em algum lugar num banco de dados bem secreto. Agora o mesmo usuário tenta entrar no site, insere seu nome de usuário e senha na página principal. O que o site deve fazer? Pode-se simplesmente recuperar a senha do usuário do banco de dados e comparar com a senha inserida. Mas isso não é seguro. O que é seguro é comparar os hashes das senhas: o que está no banco de dados e o que acaba de ser inserido. Mesmo que o hash da senha armazenada no site seja roubado, não acontecerá nada de mal, porque a senha em si mesma permanecerá intacta. A comparação de hashes permitirá definir se o usuário inseriu a senha correta ou não.

      O hash é um movimento de mão única. Com base no hash disponível é impossível obter a matriz de dados para a qual esse hash é obtido. Provavelmente está claro a partir do que foi dito acima como os hashes são importantes para a criptografia e por que neste artigo falamos sobre como calculá-los em vários ambientes.

      Nosso objetivo será o mesmo de antes, descobrir como garantir que o hash para os mesmos dados iniciais seja o mesmo ao calcular no terminal e em outros programas de terceiros.

      Em MQL, para cálculos hash é empregada a mesma função de biblioteca que já usamos antes, CryptEncode. Além disso, o argumento da função method deve ser definido com um valor para calcular o hash. Usamos o valor CRYPT_HASH_SHA256. Na documentação existem outros valores e outros tipos de hashes, quem desejar pode se familiarizar com eles. Como matriz inicial, usamos a string da senha existente: "qwertyuiopasdfgh", calculamos seu hash e o gravamos no arquivo. O código necessário é tão simples que apenas o incluímos no texto do script anexado decryptuser.mq5 sem criar classes, funções e outras coisas separadas:

      string k = "qwertyuiopasdfgh";
      
      
      uchar key[], result[], enc[];
            StringToCharArray(k, enc);
            int sha = CryptEncode(CRYPT_HASH_SHA256,enc,key,result);   
            string sha256;
            for(int i = 0; i < sha; i++) sha256 += StringFormat("%X ",result[i]);
            Print("SHA256 len: ",sha," Value:  ",sha256);
            int h = FileOpen("sha256.bin", FILE_WRITE | FILE_BIN);
            if (h == INVALID_HANDLE) {
               Print("File create failed: sha256.bin");
            }else {
               FileWriteArray(h, result);
               FileClose(h);            
            }      
      
      

      Matriz key usada anteriormente para criptografia não é usada aqui. Escrevemos o hash resultante na matriz result, exibimos na janela e registramos no arquivo sha256.bin. O comprimento do hash resultante é fixado em 32 bytes. Podemos alterar o comprimento do array original, torná-lo, por exemplo, um caractere, mas o tamanho do hash permanece inalterado (32 bytes).

      Repetimos o mesmo cálculo, adicionando funcionalidade a projetos já escritos em C# e C++. As mudanças resultam ser mínimas e muito simples: a mesma matriz de origem a partir da string de senha, as mesmas poucas strings de código. Fazemos o cálculo e... obtemos resultados desencorajadoramente diferentes, quer dizer, "não totalmente diferentes". Os hashes calculados pelo script em MQL e no projeto em C++ são os mesmos. Já o projeto em C# dá um resultado diferente. Vamos tentar usar outra string consistindo num caractere, nomeadamente em "a". E novamente os resultados serão semelhantes, pois o cálculo no projeto em C# dá um hash diferente.

      A resposta está na chamada da função StringToCharArray que converte uma string numa matriz. Se após a chamada de StringToCharArray tentarmos ver o que está na matriz resultante, descobriremos que seu tamanho aumentou. Por exemplo, após chamar uma função com a string "a", a matriz resultante terá dois elementos. O segundo elemento será "0". Já a chamada do método Encoding.ASCII.GetBytes no projeto em C# não fará nada parecido. Aqui, "0" não será incluído na matriz. 

      Com este resultado de pesquisa, adicionamos um bloco de código ao projeto em C #, anexando à matriz de bytes "0" e em seguida o enviamos para calcular o hash. O resultado agora é positivo. Todos os três projetos calculam o mesmo hash para os mesmos dados de origem. Os hashes resultantes são salvos nos arquivos sha256.bin, sha256_c#.bin, sha256_C++.bin e estão no arquivo anexado CryptoMQL.zip.

      É importante ressaltar que o exemplo acima era para dados de texto. Obviamente, quando se trata de uma matriz inicialmente binária, não é requerida nenhuma chamada de StringToCharArray e Encoding.ASCII.GetBytes. E, consequentemente, não haverá nenhum zero adicional na matriz. Por isso, é bem possível que o código deva ser modificado sem adicionar zero à matriz no projeto C#, mas removendo o zero no final da matriz no projeto MQL.

      Vamos parar por aqui concluindo que conseguimos resolver o problema proposto, que era descobrir em que condições o hash de um determinado objeto é o mesmo quando calculado em ambientes diferentes. Consideraremos também que atingimos a meta indicada no início do artigo. Conseguimos descobrir quais modos de criptografia/descriptografia devem ser usados para os resultados em diferentes ambientes serem compatíveis.

      Fim do artigo

      Embora a criptografia/descriptografia não seja a maior das necessidades no trading algorítmico no terminal MetaTrader 5, quando tal necessidade surgir, este artigo poderá ajudar a economizar algum tempo na busca de informações.

      O que é que ainda não demos? Criação de arquivos, existe essa opção para a função CrypetEncode. Padrão de codificação Base64, tal codificação também está disponível. No entanto, aqui não devem ser mencionados esses dois modos. Sim, podemos definir uma senha ao criar arquivos, mas:

      • Esta possibilidade não é mencionada na documentação.
      • A criação de arquivos, embora com uma senha, não tem nada a ver com criptografia.
      Outra opção é a codificação Base64. Raramente, mas, a nível de criptografia, há referências extremamente enganosas a esse padrão. Sob nenhuma circunstância este padrão pode ser usado para criptografar/descriptografar! Os interessados podem obter um entendimento mais profundo desta norma e descobrir a razão pela qual ela é aplicada na prática.

      Provavelmente valeria a pena dizer algumas palavras sobre o objeto de trabalho, quer dizer, sobre as licenças. No artigo, foi escolhido um caminho que contribui para uma percepção mais fácil da abordagem da tarefa de criptografar/ descriptografar, à maneira do autor. É por isso que para trabalhar são usadas matrizes de bytes. Elas são criptografadas, gravadas em arquivos, descriptografadas, etc. Numa situação real, isso seria extremamente inconveniente e estaria repleto de erros. Ao empacotar as estruturas de origem numa matriz e vice-versa, um erro de um bit causaria danos a toda a licença. Tal situação é bem possível, dadas as diferenças nos tamanhos dos tipos individuais, conforme mostrado acima. Assim, para armazenar uma licença, valeria a pena considerar um formato de texto. É aqui que imediatamente pensamos em xml e json. Presumivelmente, valeria a pena nos determos no último, visto que para o formato json existem analisadores excelentes para MQL, C# e C++.


      Programas utilizados no artigo:

       # Nome
      Tipo
       Descrição
      1
      CryptoMQL.zip Arquivo Arquive com scripts de criptografia/descriptografia.
      2 CryptoC#.zip Arquivo Projeto de criptografia/descriptografia em C#.
      3 CryptoС++.zip Arquivo Projeto de criptografia/descriptografia C++.


      Traduzido do russo pela MetaQuotes Ltd.
      Artigo original: https://www.mql5.com/ru/articles/8093

      Arquivos anexados |
      CryptoMQL.zip (4.43 KB)
      CryptoCc.zip (7.06 KB)
      CryptoCl4.zip (2456.49 KB)
      Trabalhando com séries temporais na biblioteca DoEasy (Parte 47): indicadores padrão multiperíodos multissímbolos Trabalhando com séries temporais na biblioteca DoEasy (Parte 47): indicadores padrão multiperíodos multissímbolos
      Neste artigo começaremos a desenvolver métodos para trabalhar com indicadores padrão, o que nos permitirá criar indicadores multissímbolos e multiperíodos padrão. Também adicionaremos o evento "Barras ausentes" às classes das séries temporais e descarregaremos o código do programa principal movendo as funções de preparação da biblioteca para a classe CEngine.
      Sistema de notificações de voz de eventos e sinais de negociação Sistema de notificações de voz de eventos e sinais de negociação
      Hoje em dia, os assistentes de voz ocupam um papel proeminente na vida humana, seja um navegador, um mecanismo de busca por voz ou um tradutor. Por isso, neste artigo, tentarei desenvolver um sistema simples e compreensível de notificações de voz para diferentes eventos, condições de mercado ou sinais de sistemas de negociação.
      Trabalhando com séries temporais na biblioteca DoEasy (Parte 48): indicadores multissímbolos multiperíodos num buffer de uma subjanela Trabalhando com séries temporais na biblioteca DoEasy (Parte 48): indicadores multissímbolos multiperíodos num buffer de uma subjanela
      Neste artigo consideraremos um exemplo que mostra como criar indicadores padrão multissímbolos e multiperíodos que usam um buffer de indicador e funcionam numa subjanela do gráfico principal. Prepararemos classes da biblioteca para trabalhar com indicadores padrão que funcionam na janela principal do programa, ou que tenham mais de um buffer para exibir seus dados.
      Teoria das probabilidades e estatística matemática com exemplos (Parte I): fundamentos e teoria elementar Teoria das probabilidades e estatística matemática com exemplos (Parte I): fundamentos e teoria elementar
      Fazer trading é sempre sobre como tomar decisões diante da incerteza. Isso significa que os resultados das decisões tomadas não são muito óbvios no momento em que são tomadas. Por isso, são importantes as abordagens teóricas para a construção de modelos matemáticos que possibilitem descrever tais situações de maneira significativa.