English Русский 中文 Deutsch 日本語 Português
Uso de criptografía con aplicaciones externas

Uso de criptografía con aplicaciones externas

MetaTrader 5Trading | 23 noviembre 2020, 09:15
561 0
Andrei Novichkov
Andrei Novichkov

Introducción

La criptografía se usa en programas MQL con muy poca frecuencia. En la vida diaria del tráder, no existen tantas oportunidades para usar la criptografía. Una excepción sería un copiador de señales paranoico que quisiera proteger los datos enviados para que nadie los espiara, y quizá eso sea todo. Cuando los datos no salen del terminal, resulta muy difícil imaginar por qué habría que encriptarlos/desencriptarlos. Aparte, esto podría indicar un bajo nivel de competencia del desarrollador, que crearía además una carga adicional al terminal.

¿Puede que no sea necesario en absoluto usar la criptografía en el trading? Obviamente, existe esa necesidad. Por ejemplo, vamos a considerar la posibilidad de obtener una licencia. Imaginemos una pequeña empresa o incluso un desarrollador independiente cuyos productos sean populares. Los problemas de licencia resultan del todo relevantes en este caso y, por consiguiente, necesitaríamos la encriptación/desencriptación para las licencias.

En la licencia, podremos indicar los datos del usuario y una lista editable con los productos de los que este dispone. Un indicador o asesor comienza a comerciar, verifica la disponibilidad de una licencia y su vencimiento para el producto dado. Un programa envía una solicitud al servidor, actualiza la licencia (de ser necesario) o recibe una nueva. Quizá esta no sea la ruta más segura y eficaz, pero la utilizaremos en este artículo con fines ilustrativos. Claro está, en este caso, la licencia será leída/escrita por diferentes herramientas de software: un terminal, un servidor remoto, módulos de control y módulos de registro. De su escritura pueden encargarse varias personas, en diferentes momentos y en diferentes idiomas.

El objetivo del presente artículo es analizar los modos de encriptación/desencriptación con los que un objeto encriptado por un programa escrito en C# o C++ pueda ser desencriptado con toda garantía por el terminal MetaTrader, y viceversa.

El artículo ha sido pensado tanto para desarrolladores de cualificación media, como para principiantes.

Formulando la tarea

Ya hemos hablado de ello en la introducción. Vamos a intentar imitar una solución a un problema real que requiere la creación, la encriptación y la desencriptación de una licencia para varios productos: indicadores y asesores expertos. Para nosotros no es esencial qué programa encripta y desencripta la licencia. Por ejemplo, podemos crear una licencia inicialmente en la computadora del desarrollador, luego corrigirla en el departamento de ventas y desencriptarla en la computadora del tráder. Este proceso no debe acusar errores relacionados con algoritmos mal configurados.

Aparte de la solución de la tarea principal, vamos a analizar el complejo problema de las licencias. No pretendemos de ninguna forma crear un objeto efectivo y funcional, es decir, una licencia; solo vamos a ofrecer una de las posibles opciones que pueden y deben ser corregidas, desarrolladas o consideradas como una opción que no debe usarse.

Datos iniciales

Tomaremos como datos iniciales para el trabajo la documenación del terminal. Tenemos dos funciones estándar que se encargan de todo çel trabajo de encriptación/desencriptación:

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
   );

El argumento method determinará cómo vamos a ejecutar precisamente la encriptación/desencriptación. En este caso, nos interesan tres de los valores que puede adoptar el argumento method: CRYPT_AES128, CRYPT_AES256, CRYPT_DES. Estos tres valores del método suponen algoritmos encriptación simétrica con diferente longitud de clave.

Para trabajar en el marco del presente artículo, usaremos solo uno de ellos, CRYPT_AES128. Se trata de una algoritmo simétrico de encriptación por bloques con una clave de 128 bits (16 bytes). El trabajo con los otros algoritmos será similar al mostrado con este.

El algoritmo AES (no solo el seleccionado, sino también aquellos con otra longitud de clave) tiene algunos ajustes importantes que no se ofrecen en las funciones anteriores. Entre ellos, figuran el modo de encriptación y el rellenado (padding). No vamos a profundizar en los detalles sobre estos términos, aunque sí que necesitaremos su significado. Por consiguiente, el terminal usa el modo de cifrado Electronic Codebook (ECB) y un Padding igual a cero. Nos gustaría agradecer a los compañeros tráders que explicaran esto en la rama del foro MQL5 en inglés. Ahora, nuestra tarea resultará mucho más fácil de resolver.

Desarrollando el objeto de trabajo

Ya que estamos considerando que la encriptación/desencriptación se aplicará a las licencias, nuestro objeto de trabajo será una licencia. Deberá tratarse de algún tipo de construcción que contenga información sobre varios productos a los que se aplica la licencia. Obviamente, aquí necesitaremos los siguientes datos:

  1. El plazo de finalización de la licencia de este instrumento.
  2. El nombre del instrumento.

Vamos a crear la estructura correspondiente con los métodos de acompañamiento más sencillos:

#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


Algunas aclaraciones al respecto:

  • El nombre del instrumento se guarda en forma de matriz de longitud fija.
  • Tenemos una limitación en la longitud del nombre del instrumento, PRODMAXLENGTH.
  • Esta estructura puede ser fácilmente comprimida en una matriz de bytes, cosa que haremos antes de encriptar todo el objeto.

No obstante, dicha estructura por sí misma no es suficiente. Obviamente, la licencia debe contener información sobre el usuario. La información podría incluirse en la estructura ya descrita, pero esto resultaría ineficiente, dado que el usuario podría tener múltiples licencias de productos. Una solución más razonable consistiría en crear una estructura aparte para el usuario y añadirle el número requerido de licencias de producto. De esta manera, el usuario tendrá una licencia que contiene permisos y restricciones para todos los productos con licencia.

Información que puede encontrarse en la estructura que describe al usuario:

  1. El identificador de usuario único. También se podría guardar el nombre, pero no parece adecuado enviar los datos personales cada vez, incluso en forma encriptada.
  2. Información sobre las cuentas de usuario en las que se pueden usar los productos.
  3. Fecha de expiración de la licencia del usuario. Este campo podría limitar el uso de todos los productos existentes, incluso los ilimitados, al tiempo de servicio del usuario como tal.
  4. El número de productos con licencia en el terminal de usuario.
#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

Algunas aclaraciones:

  • Las cuentas del usuario se guardan en una matriz de longitud fija. Están representados por los números de cuenta, aunque, si fuera necesario, la información se podría complementar fácilmente con el nombre del servidor o el bróker y el número de activaciones de una cuenta en particular.

Por el momento, esta estructura tiene suficiente información sobre el usuario y los productos relevantes. Cada uno de ellos tiene un tipo concreto cuya instancia puede ser procesada por la función StructToCharArray.

Ahora, lo que necesitamos es serializar los datos de la estructura en una matriz de bytes que se pueda encriptar aún más. Implementaremos esto de la forma siguiente:

  • Primero, creamos e inicializamos una instancia de la estructura user_lic.
  • A continuación, la serializamos en una matriz de bytes
  • y creamos e inicializamos una o más instancias de la estructura ea_user.
  • Después, serializamos estas en la misma matriz de bytes, aumentamos su tamaño y corregimos el campo ea_count.

Por último, creamos una clase para ejecutar estas operaciones:

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);

Hemos añadido a la clase dos funciones que permiten encriptar y desencriptar, así como una función protegida para verificar la longitud de la clave. Como podemos ver por el código (por ejemplo), la longitud de la clave para el método CRYPT_AES128 debe ser igual a 16 bytes. En realidad, no debería tener menos de 16 bytes. Probablemente, sucede algún tipo de normalización oculta para el desarrollador. No vamos a confiar en esto, así que tendremos que establecer rigurosamente la longitud de clave requerida.

Finalmente, ya podemos encriptar la matriz de bytes resultante y guardarla en un archivo binario. Este archivo deberá guardarse en la carpeta File del terminal, subordinándose a las normas generales. Si fuera necesario, podrá ser leída y desencriptada:

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 funciones son bastante comprensibles, por lo que no requieren de aclaraciones adiconales. El archivo adjunto CryptoMQL.zip contiene dos scripts y un archivo de biblioteca que ejecuta la encriptación desencriptación, así como el archivo encriptado de licencia lic.txt.

El proyecto C#

Creemos un proyecto C # simple para simular el proceso de descifrado y edición por otro programa. Utilice Visual Studio 2017 y cree una aplicación de consola para la plataforma .NET Framework. Compruebe la conexión de System.Security y el espacio System.Security.Cryptography. 

El siguiente problema a enfrentar está en el código, ya que MQL y C # tienen formatos de hora distintos. Este problema ya se abordó y solucionó en este artículo. El autor ha hecho un gran trabajo y podemos utilizar su clase MtConverter en nuestro proyecto.

A continuación, creamos dos clases, EaUser y UserLic, con los campos similares a las estructuras ea_user y user_lic. Nuestra intención es desencriptar la licencia creada por el terminal (el archivo lic.txt), analizar los datos obtenidos, modificar los objetos y volver a encriptar, creando un nuevo archivo. Esta tarea debería ser fácil de implementar si configuramos los modos de encriptación/desencriptación con cuidado. Este es el aspecto del segmento de código correspondiente:

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

Fijémonos en las dos últimas líneas, donde se establecen el modo de encriptación (ECB) y el rellenado. Vamos a utilizar la información disponible sobre la configuración de estos modos. La primera línea del bloque relacionada con el establecimiento de la clave debe estar clara. Usaremos la misma clave utilizada para la encriptación en el terminal, pero esta vez se convertirá en una matriz de bytes:

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

Debemos destacar la línea donde se establece el parámetro "IV". Es el llamado "vector de inicialización", un número aleatorio que participa en todos los modos de encriptación, salvo el modo de ECB. Por eso, simplemente crearemos en este punto una matriz con la longitud necesaria:

byte[] iv   = new byte[16];

Además, debemos considerar que la situación con la clave en C# es distinta a la situación de MQL. Si la longitud de la clave (en este caso, la línea "qwertyuiopasdfgh") es superior a 16, se lanzará una excepción. Por eso, ha sido una buena decisión controlar rigurosamente la longitud de la clave en el código MQL.

El resto resulta bastante sencillo. Leemos el archivo binario -> desencriptamos la secuencia -> llenamos la instancia de la clase UserLic con la ayuda de BinaryReader. Probablemente, podríamos lograr un resultado semejante haciendo que las clases correspondientes fueran serializables. El lector podrá probar esta posibilidad por sí mismo.

Vamos a modificar cualquier campo; en nuestro caso, modificaremos el identificador de usuario. A continuación, encriptamos los datos de la misma forma y creamos el nuevo archivo "lic_C#.txt". El trabajo anterior se efectúa usando dos funciones estáticas en el proyecto, EncryptToFile_Aes y DecryptFromFile_Aes. Para lograr una mejor depuración, hemos añadido dos funciones similares que no operan con archivos, sino con matrices de bytes: EncryptToArray_Aes y DecryptFromArray_Aes.

El proyecto CryptoC#.zip con todos los archivos se adjunta al artículo.

Obviamente, incluso un desarrollador con poca experiencia podrá notar los defectos del proyecto:

  • No existen las comprobaciones necesarias de los argumentos de las funciones llamadas.
  • No existe el procesamiento de las excepciones.
  • El modo de trabajo es de flujo único.

Estas observaciones están plenamente justificadas, pero, en nuestro caso, no hemos implementado las funciones anteriores porque el objetivo del artículo no es crear una aplicación con todas las funciones. Si implementamos todas las partes necesarias, la capa de código adicional resultaría demasiado grande y distraería la atención del problema básico.

El proyecto C ++

La parte final del proyecto en el presente artículo se creará en C ++. Vamos a crear una aplicación de consola en el entorno de Visual Studio 2017. Como no disponemos de ningún soporte de encriptación/desencriptación listo para usar, tendremos que incluir la conocida biblioteca OpenSSL. Para ello, el paquete de instalación de OpenSSL deberá estar descargado e instalado. Como resultado, podemos utilizar todas las bibliotecas e inclusiones de Open SSL, que deberían estar incluidas en los proyectos creados. Para más información sobre las formas de incluir bibliotecas en un proyecto, lea este artículo. Por desgracia, la documentación de OpenSSL está lejos de ser completa; no obstante, no tenemos nada mejor a mano.

Una vez hayamos conectado las bibliotecas, procederemos a escribir el código. Lo primero que debemos hacer es describir de nuevo las dos estructuras ya conocidas:

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()


El código resulta un poco más complejo que en C #, pero no demasiado. Es posible que los lectores atentos detecten algunas diferencias en ciertos tipos de campos. Por ejemplo, en este proyecto, el campo con la matriz de cuentas tiene el tipo de matriz int64_t y el archivo de inclusión MQL tiene el tipo long. Esto se relaciona con el tamaño de los tipos correspondientes. Si no controlamos estas funciones, podrían surgir errores difíciles de detectar. Algunas partes son más simples: podemos no ocuparnos de la conversión del tiempo, cosa que tuvimos que hacer en el anterior ptoyecto.

Además, en este proyecto podemos encontranos con que la longitud de la clave es incorrecta. Para resolver esta tarea, incluiremos la siguiente función en el proyecto:

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

Esta función "cortará" la matriz appBuffer hasta la longitud necesaria aSize. Además, vamos a escribir una función adicional bastante útil:

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

Esta función ofrecerá al desarrollador la posibilidad de desencriptar los códigos de error de la biblioteca OpenSSL. Y, finalmente, un par de funciones que realizarán el trabajo 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)

No merece la pena mostrar aquí la implementación de los métodos; si el lector lo desea, podrá recurrir a los archivos adjuntos. Solo mencionaremos algunos de los puntos esenciales:

  • Aquí no se usa ningún vector de inicialización. Creamos una matriz con el tamaño requerido y la pasamos al punto de llamada.
  • La biblioteca no ofrece ningún beneficio respecto al rellenado (padding). Configuraremos este modo llamando a:
    EVP_CIPHER_CTX_set_padding(ctx.get(), 0);
    
    
    Además, transmitiremos precisamente "cero", y no así:
    EVP_CIPHER_CTX_set_padding(ctx.get(), EVP_PADDING_ZERO);
    
    
    como podríamos pensar. Los problemas relacionados con la adición no terminan aquí. El hecho es que si el valor de rellenado es cero (como en nuestro proyecto), el desarrollador deberá asegurarse de que la longitud del objeto encriptado sea un múltiplo de BLOCK_SIZE = AES_BLOCK_SIZE, es decir, de 16 bytes. Por eso, antes de llamar a aes_encrypt(......), deberemos ofrecer la alineación adecuada de la matriz a encriptar.

Aquí, realizaremos la misma secuencia de acciones que hicimos en el proyecto anterior:

  • Primero, desencriptamos el archivo resultante, lo editamos y lo volvemos a encriptar. En este caso, añadiremos la información sobre una cuenta de usuario más a la licencia.
  • A continuación, obtenemos el último archivo encriptado en esta serie, lic_С ++.Txt. Nótese que el tamaño del archivo ha cambiado. Este es precisamente el tamaño del bloque (16 bytes) añadido durante la alineación.

Todos los archivos originales del proyecto están disponibles en el archivo CryptoС++.zip

    Comprobación final y resultados

    Ahora, vamos a emprender la etapa final de la operación. Para ello, trasladamos el archivo lic_С ++.txt recientemente encriptado a la carpeta File de MetaTrader y lo desencriptamos utilizando el script decryptuser.mq5. Obtenemos el resultado esperado: a pesar del cambio de longitud, el archivo se ha desencriptado con éxito.

    Entonces, ¿qué hemos obtenido como resultado? Lo más importante es que hemos definido los parámetros de encriptación que permiten transferir de un programa a otro archivos encriptados. Claro está, que más adelante podremos confirmar que si falla la encriptación/desencriptación, el problema podría deberse a errores en los programas de aplicación.

      Hashes

      La mayoría de los lectores sabrá que la criptografía no se limita únicamente a la encriptación/desencriptación. Vamos a considerar una primitiva criptográfica: el hashing. Este proceso implica la conversión de una matriz aleatoria en una matriz de longitud fija. La matriz de este tipo se denomina hash, y la función de conversión se denomina función hash. Dos matrices iniciales que se distinguen entre sí en al menos un bit, producirán hashes completamente distintos, que se podrán utilizar para la identificación y comparación.

      Para mejor comprensión, aquí tenemos un ejemplo concreto. Un usuario se registra en un sitio web e introduce sus datos identificativos. Los datos se almacenan en una base de datos muy secreta. Ahora, este mismo usuario trata de entrar en el sitio web ingresando el nombre de usuario y la contraseña en la página principal. ¿Qué tiene que hacer el sitio web? Puede simplemente recuperar de la base de datos la contraseña del usuario y compararla con la ingresada. Pero esto no resulta seguro. La forma más segura consiste en comparar el hash de la contraseña almacenada y el hash de la contraseña introducida. Incluso si se roba el hash almacenado en el sitio, no pasará nada: la propia contraseña permanecerá segura. Al comparar los hash, podemos determinar si la contraseña introducida es correcta.

      El hashing es un proceso unidireccional. Con el hash disponible, no es posible conseguir la matriz de datos para la que se obtiene el hash. Por consiguiente, los hashes son muy importantes para la criptografía: por eso analizamos su cálculo en diferentes entornos en el presente artículo.

      Nuestro propósito será el mismo de antes: encontrar la forma de asegurarnos de que el hash para los mismos datos iniciales sea el mismo al realizar los cálculos en el terminal y en otros programas de terceros.

      En MQL, el hash se calcula usando la misma función de biblioteca que utilizamos anteriormente: CryptEncode. El argumento de la función del método debe establecerse en un valor para calcular el hash. Nosotros vamos a utilizar el valor CRYPT_HASH_SHA256. La documentación ofrece otros valores y otros tipos de hash, por lo que los lectores podrán informarse más sobre este tema. Como matriz inicial, vamos a usar una línea de contraseña ya existente: "qwertyuiopasdfgh". Calcularemos su hash y escribiremos el hash en un archivo. El código resultante es muy sencillo; por consiguiente, nos limitaremos a incluirlo en el archivo del script adjunto decryptuser.mq5, sin crear clases y funciones 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);            
            }      
      
      

      Aquí no se usa la matriz key que utilizamos anteriormente para la encriptación. El hash resultante lo escribimos en la matriz result, lo mostramos en la ventana y lo escribimos en el archivo sha256.bin. La longitud del hash resultante es fija, de 32 bytes. Podemos modificar el tamaño de la matriz de origen: digamos que tiene un carácter de longitud, pero el tamaño del hash seguirá siendo de 32 bytes.

      Repetimos el mismo cálculo añadiendo la funcionalidad requerida a los proyectos de C# y C++. Los cambios son mínimos y muy simples. Utilizamos la misma matriz de origen de la línea de contraseña. Añadimos las mismas líneas de código. Realizamos los cálculos y ... ¡obtenemos resultados desalentadoramente diferentes! Es decir, "no son del todo diferentes". Los hashes calculados por el script MQL y el proyecto de C++ son los mismos. Pero el proyecto C# da un resultado distinto. Vamos a tratar de usar otra línea que conste de un solo carácter: "a". De nuevo, el cálculo en el proyecto C# producirá un hash diferente.

      La respuesta se oculta en la llamada de la función StringToCharArray, que convierte una línea en una matriz. Si observamos la matriz resultante después de llamar a StringToCharArray, veremos que la matriz ha duplicado su tamaño. Por ejemplo, después de llamar a la función con la línea "a", la matriz resultante tendrá dos elementos. El segundo elemento será "0". La llamada de Encoding.ASCII.GetBytes en el proyecto C# no provocará nada semejante. En este caso, "0" no será incluido en la matriz. 

      Ahora, podemos añadir al proyecto de C# un bloque de código que añada "0" a la matriz de bytes. Después de ello, podremos utilizar esta matriz de bytes para calcular el hash. Ahora, obtenemos el resultado esperado. Los tres proyectos calculan el mismo hash para los mismos datos de origen. Los hashes resultantes están disponibles en los archivos sha256.bin, sha256_c#.bin, sha256_С++.bin, que se encuentran en el archivo CryptoMQL.zip adjunto al artículo.

      Debemos tener en cuenta que el ejemplo anterior se refiere a los datos de texto. Obviamente, cuando se trata de una matriz inicialmente binaria, no será necesario llamar a StringToCharArray y Encoding.ASCII.GetBytes. Y no tendremos ningún problema con un 0 adicional en la matriz. Por consiguiente, otra posible opción consistirá en eliminar el 0 del proyecto MQL en lugar de añadirlo a C#.

      No obstante, hemos resuelto el problema inicial: descubir en qué condiciones el hash de un determinado objeto será idéntico al calcularse en entornos diferentes. También hemos logrado el objetivo marcado al principio del artículo. Hemos determinado qué modos de encriptación/desencriptación debemos usar para garantizar la compatibilidad de los resultados en diferentes entornos.

      Conclusión

      Aunque las operaciones de encriptación/desencriptación no se usan con frecuencia en el comercio algorítmico en el terminal MetaTrader 5, esta tarea puede resultar útil cuando surja tal necesidad.

      ¿Qué no hemos analizado en el presente artículo? La creación de archivos: esta opción está disponible para la función CrypetEncode. El estándar de encriptación Base64, también disponible. Sin embargo, no consideramos necesario analizar estos modos aquí. Sí, es cierto que se pueden establecer contraseñas al crear archivos, pero:

      • Esta posibilidad no se menciona en la documentación.
      • La creación de archivos, incluso de aquellos protegidos por contraseña, no tiene relación alguna con la criptografía.
      Otra opción es la encriptación con la ayuda de Base64. Hay algunas referencias engañosas sobre este estándar en relación con la criptografía. ¡Este estándar no debe usarse para la encriptación/desencriptación! Si el lector así lo desea, podrá obtener más información sobre este estándar y su uso en la práctica.

      Probablemente, deberíamos decir algunas palabras sobre el objeto de nuestro trabajo: las licencias. En este artículo, hemos seleccionado un camino que contribuye a mejorar la percepción de la solución al problema de la encriptación/desencriptación, según nos parece. Por eso, para trabajar utilizamos matrices de bytes. Estas se encriptan, se escriben en un archivo, se desencriptan, etc. En una situación real, esto sería muy poco conveniente y podría provocar errores. Al comprimir las estructuras originales en una matriz y descomprimir estas, un error de un solo bit dañaría toda la licencia. Además, esta situación es bastante plausible, vistas las diferencias en los tamaños de los diferentes tipos, como se muestra arriba. Por consiguiente, otro posible formato para almacenar la licencia sería el texto. Nos referimos a xml y json. Presumiblemente, deberíamos decantarnos por este último, dado que, para el formato json, existen excelentes analizadores para MQL, C# y C++.


      Programas utilizados en el artículo:

       # Nombre
      Tipo
       Descripción
      1
      CryptoMQL.zip Fichero Fichero con scripts de encriptación/desencriptación.
      2 CryptoC#.zip Fichero Proyecto C# de encriptación/desencriptación.
      3 CryptoС++.zip Fichero Proyecto C++ de encriptación/desencriptación.


      Traducción del ruso hecha por MetaQuotes Ltd.
      Artículo original: https://www.mql5.com/ru/articles/8093

      Archivos adjuntos |
      CryptoMQL.zip (4.43 KB)
      CryptoCc.zip (7.06 KB)
      CryptoCl4.zip (2456.49 KB)
      Trabajando con las series temporales en la biblioteca DoEasy (Parte 48): Indicadores de periodo y símbolo múltiples en un búfer en una subventana Trabajando con las series temporales en la biblioteca DoEasy (Parte 48): Indicadores de periodo y símbolo múltiples en un búfer en una subventana
      En el presente artículo, analizaremos la creación de indicadores estándar de periodo y símbolo múltiples que utilizan un búfer de indicador para sus construcciones, y que funcionan en una subventana del gráfico. Asimismo, prepararemos las clases de la biblioteca para trabajar con los indicadores estándar que funcionan en la ventana principal del programa, o que tienen más de un búfer para mostrar sus datos.
      Enfoque científico sobre el desarrollo de algoritmos comerciales Enfoque científico sobre el desarrollo de algoritmos comerciales
      En el presente artículo, estudiaremos con ejemplos la metodología de desarrollo de algoritmos comerciales usando un enfoque científico secuencial sobre el análisis de las posibiles patrones de formación de precio y la construcción de algoritmos comerciales basados en dichas leyes.
      Gradient boosting (CatBoost) en las tareas de construcción de sistemas comerciales. Un enfoque ingenuo Gradient boosting (CatBoost) en las tareas de construcción de sistemas comerciales. Un enfoque ingenuo
      Entrenamiento del clasificador CatBoost en el lenguaje Python, exportación al formato mql5; análisis de los parámetros del modelo y simulador de estrategias personalizado. Para preparar los datos y entrenar el modelo, se usan el lenguaje de programación Python y la biblioteca MetaTrader5.
      Sistema de notificaciones de voz para evetos comerciales y señales Sistema de notificaciones de voz para evetos comerciales y señales
      En nuestros tiempos, los asistentes de voz juegan hace mucho un papel considerable en la vida del hombre, ya sea como navegador, buscador de voz o traductor. Por eso, en el presente artículo trataremos de desarrollar un sistema sencillo y comprensible de notificaciones de voz para los diferentes eventos comerciales, los estados del mercado o las señales de los sistemas comerciales.