Cifrado, hashing y empaquetado de datos: CryptEncode

La función MQL5 responsable de la encriptación, hashing y compresión de datos es CryptEncode. Transforma los datos del array de origen pasado data al array de destino result mediante el método especificado.

int CryptEncode(ENUM_CRYPT_METHOD method, const uchar &data[], const uchar &key[], uchar &result[])

Los métodos de cifrado también requieren pasar un array de bytes key con una clave privada (secreta): su longitud depende del método específico y se especifica en la tabla de métodos ENUM_CRYPT_METHOD de la sección anterior. Si el tamaño del array key es mayor, sólo se seguirán utilizando para la clave los primeros bytes de la cantidad requerida.

No se necesita una clave para hashing o compresión, pero hay una advertencia para CRYPT_ARCH_ZIP. El hecho es que la implementación del algoritmo «deflate» integrado en el terminal añade varios bytes a los datos resultantes para controlar la integridad: 2 bytes iniciales contienen la configuración del algoritmo «deflate», y 4 bytes al final contienen la suma de comprobación Adler32. Debido a esta característica, el contenedor empaquetado resultante difiere del generado por los archivos ZIP para cada elemento individual del archivo (el estándar ZIP almacena CRC32, que tiene un significado similar, en sus encabezados). Por lo tanto, para poder crear y leer archivos ZIP compatibles basados en datos empaquetados por la función CryptEncode, MQL5 permite desactivar su propia comprobación de integridad y la generación de bytes extra utilizando un valor especial en el array key.

uchar key[] = {1000};
CryptEncode(CRYPT_ARCH_ZIPdatakeyresult);

Se puede utilizar cualquier clave con una longitud de al menos 4 bytes. El array result obtenido puede enriquecerse con un título según el formato ZIP estándar (esta cuestión queda fuera del ámbito del libro) para crear un archivo accesible a otros programas.

La función devuelve el número de bytes colocados en el array de destino o 0 en caso de error. El código de error, como es habitual, se almacenará en _LastError.

Comprobemos el rendimiento de la función utilizando el script CryptEncode.mq5. Permite al usuario introducir texto (Text) o especificar un archivo (File) para su procesamiento. Para utilizar el archivo, debe borrar el campo Text.

Puede elegir un Method específico o recorrer todos los métodos a la vez para ver y comparar visualmente los distintos resultados. Para un bucle de revisión de este tipo, deje el valor predeterminado _CRYPT_ALL en el parámetro Method.

Por cierto, para introducir esta funcionalidad, de nuevo necesitamos extender la enumeración estándar (esta vez ENUM_CRYPT_METHOD), pero como las enumeraciones en MQL5 no pueden ser heredadas como clases, aquí se declara una nueva enumeración ENUM_CRYPT_METHOD_EXT. Una ventaja añadida es que hemos añadido nombres más fáciles de usar para los elementos (en los comentarios, con pistas que se mostrarán en el cuadro de diálogo de configuración).

enum ENUM_CRYPT_METHOD_EXT
{
   _CRYPT_ALL = 0xFF,                      // Try All in a Loop
   _CRYPT_DES = CRYPT_DES,                 // DES    (key required, 7 bytes)
   _CRYPT_AES128 = CRYPT_AES128,           // AES128 (key required, 16 bytes)
   _CRYPT_AES256 = CRYPT_AES256,           // AES256 (key required, 32 bytes)
   _CRYPT_HASH_MD5 = CRYPT_HASH_MD5,       // MD5
   _CRYPT_HASH_SHA1 = CRYPT_HASH_SHA1,     // SHA1
   _CRYPT_HASH_SHA256 = CRYPT_HASH_SHA256// SHA256
   _CRYPT_ARCH_ZIP = CRYPT_ARCH_ZIP,       // ZIP
   _CRYPT_BASE64 = CRYPT_BASE64,           // BASE64
};
   
input string Text = "Let's encrypt this message"// Text (empty to process File)
input string File = "MQL5Book/clock10.htm"// File (used only if Text is empty)
input ENUM_CRYPT_METHOD_EXT Method = _CRYPT_ALL;

De manera predeterminada, el parámetro Text se rellena con un mensaje que se supone cifrado. Puede sustituirlo por el suyo propio. Si borramos Text, el programa procesará el archivo. Al menos uno de los parámetros (Text o File) debe contener información.

Dado que el cifrado requiere una clave, las otras dos opciones permiten introducirla directamente como texto (aunque la clave no tiene por qué ser texto y puede contener cualquier dato binario, pero no se admiten en las entradas) o generar la longitud deseada, según el método de cifrado.

enum DUMMY_KEY_LENGTH
{
   DUMMY_KEY_0 = 0,   // 0 bytes (no key)
   DUMMY_KEY_7 = 7,   // 7 bytes (sufficient for DES)
   DUMMY_KEY_16 = 16// 16 bytes (sufficient for AES128)
   DUMMY_KEY_32 = 32// 32 bytes (sufficient for AES256)
   DUMMY_KEY_CUSTOM,  // use CustomKey
};
   
input DUMMY_KEY_LENGTH GenerateKey = DUMMY_KEY_CUSTOM// GenerateKey (length, or from CustomKey)
input string CustomKey = "My top secret key is very strong";

Por último, existe una opción DisableCRCinZIP para activar el modo de compatibilidad ZIP, que sólo afecta al método CRYPT_ARCH_ZIP.

input bool DisableCRCinZIP = false;

Para simplificar la comprobación de si el método requiere una clave de cifrado o si se calcula un hash (una conversión unidireccional irreversible), se definen 2 macros:

#defineKEY_REQUIRED(C) ((C) ==CRYPT_DES|| (C) ==CRYPT_AES128|| (C) ==CRYPT_AES256)

#defineIS_HASH(C) ((C) ==CRYPT_HASH_MD5|| (C) ==CRYPT_HASH_SHA1|| (C) ==CRYPT_HASH_SHA256)

El comienzo de OnStart contiene una descripción de las variables y arrays necesarios.

void OnStart()
{
   ENUM_CRYPT_METHOD method = 0;
   int methods[];           // here we will collect all the elements of ENUM_CRYPT_METHOD for looping over them
   uchar key[] = {};        // empty by default: suitable for hashing, zip, base64
   uchar zip[], opt[] = {1000}; // "options" for zip
   uchar data[], result[];  // initial data and result

Según la configuración de GenerateKey obtenemos la clave del campo CustomKey o simplemente rellenamos el array key con valores enteros monótonamente crecientes. En realidad, la clave debe ser un bloque de valores secreto, no trivial y elegido arbitrariamente.

   if(GenerateKey == DUMMY_KEY_CUSTOM)
   {
      if(StringLen(CustomKey))
      {
         PRTF(CustomKey);
         StringToCharArray(CustomKeykey0, -1, CP_UTF8);
         ArrayResize(keyArraySize(key) - 1);
      }
   }
   else if(GenerateKey != DUMMY_KEY_0)
   {
      ArrayResize(keyGenerateKey);
      for(int i = 0i < GenerateKey; ++ikey[i] = (uchar)i;
   }

Aquí y más abajo, observe el uso de ArrayResize después de StringToCharArray. Asegúrese de reducir el array en 1 elemento, porque en caso de que la función StringToCharArray convierta la cadena en un array de bytes, incluyendo el terminal 0, esto puede romper la ejecución esperada del programa. En concreto, en este caso, tendremos un byte cero extra en la clave secreta, y si un programa con un artefacto similar no se utiliza en el lado receptor, entonces no será capaz de descifrar el mensaje. Estos ceros adicionales también pueden afectar a la compatibilidad con los protocolos de intercambio de datos (si se realiza una u otra integración de un programa MQL con el «mundo exterior»).

A continuación, registramos una representación en bruto de la clave resultante en formato hexadecimal: esto se hace mediante la función ByteArrayPrint que se utilizó en la sección Escritura y lectura de archivos en modo simplificado.

   if(ArraySize(key))
   {   
      Print("Key (bytes):");
      ByteArrayPrint(key);
   }
   else
   {
      Print("Key is not provided");
   }

En función de la disponibilidad de Text o File, rellenamos el array data con caracteres de texto o con el contenido del archivo.

   if(StringLen(Text))
   {
      PRTF(Text);
      PRTF(StringToCharArray(Textdata0, -1, CP_UTF8));
      ArrayResize(dataArraySize(data) - 1);
   }
   else if(StringLen(File))
   {
      PRTF(File);
      if(PRTF(FileLoad(Filedata)) <= 0)
      {
         return// error
      }
   }

Por último, hacemos un bucle a través de todos los métodos o realizamos la transformación una vez con un método específico.

   const int n = (Method == _CRYPT_ALL) ?
      EnumToArray(methodmethods0UCHAR_MAX) : 1;
   ResetLastError();
   for(int i = 0i < n; ++i)
   {
      method = (ENUM_CRYPT_METHOD)((Method == _CRYPT_ALL) ? methods[i] : Method);
      Print("- "i" "EnumToString(method), ", key required: ",
         KEY_REQUIRED(method));
      
      if(method == CRYPT_ARCH_ZIP)
      {
         if(DisableCRCinZIP)
         {
            ArrayCopy(zipopt); // array with additional option dynamic for ArraySwap
         }
         ArraySwap(keyzip); // change key to empty or option
      }
      
      if(PRTF(CryptEncode(methoddatakeyresult)))
      {
         if(StringLen(Text))
         {
            // code page Latin (Western) to unify the display for all users
            Print(CharArrayToString(result0WHOLE_ARRAY1252));
            ByteArrayPrint(result);
            if(method != CRYPT_BASE64)
            {
               const uchar dummy[] = {};
               uchar readable[];
               if(PRTF(CryptEncode(CRYPT_BASE64resultdummyreadable)))
               {
                  PrintFormat("Try to decode this with CryptDecode.mq5 (%s):",
                     EnumToString(method));
                  // to receive encoded data back for decoding
                  // via string input, apply Base64 over binary result
                  Print("base64:'" + CharArrayToString(readable0WHOLE_ARRAY1252) + "'");
               }
            }
         }
         else
         {
            string parts[];
            const string filename = File + "." +
               parts[StringSplit(EnumToString(method), '_', parts) - 1];
            if(PRTF(FileSave(filenameresult)))
            {
               Print("File saved: "filename);
               if(IS_HASH(method))
               {
                  ByteArrayPrint(result1000"");
               }
            }
         }
      }
   }
}

Cuando convertimos texto, registramos el resultado, pero como casi siempre son datos binarios, con la excepción del método CRYPT_BASE64, su visualización será un completo galimatías (a decir verdad, los datos binarios no deberían registrarse, pero lo hacemos por claridad). Los símbolos no imprimibles y los símbolos con códigos superiores a 128 se muestran de forma diferente en ordenadores con idiomas distintos. Por ello, con el fin de unificar la visualización de ejemplos para todos los lectores, al formar una línea en CharArrayToString, utilizamos una página de código explícita (1252, lenguas de Europa Occidental). Es cierto que los tipos de letra utilizados al publicar un libro contribuirán con toda probabilidad a la forma en que se muestren determinados caracteres (el conjunto de glifos de los tipos de letra puede ser limitado).

Es importante tener en cuenta que controlamos la elección de la página de código sólo en el método de visualización, y los bytes del array result no cambian por ello (por supuesto, la cadena obtenida de este modo no debe enviarse a ningún otro sitio; sólo es necesario para la visualización utilizar los bytes del propio resultado para el intercambio de datos).

Sin embargo, sigue siendo deseable que ofrezcamos al usuario alguna posibilidad de guardar el resultado cifrado para descodificarlo más tarde. La forma más sencilla es volver a transformar los datos binarios utilizando el método CRYPT_BASE64.

En el caso de la codificación de archivos, simplemente guardamos el resultado en un nuevo archivo con un nombre en el que se añade al original la extensión de la última palabra del nombre del método. Por ejemplo, aplicando CRYPT_HASH_MD5 al archivo Example.txt, obtendremos el archivo de salida Example.txt.MD5 que contiene el hash MD5 del archivo fuente. Tenga en cuenta que, para el método CRYPT_ARCH_ZIP, obtendremos un archivo con extensión ZIP, pero no es un archivo ZIP estándar (debido a la falta de encabezados con meta información y tabla de contenidos).

Vamos a ejecutar el script con la configuración predeterminada: corresponden a comprobar en el bucle todos los métodos para el texto «Let's encrypt this message» («vamos a cifrar este mensaje»).

CustomKey=My top secret key is very strong / ok

Key (bytes):

[00] 4D | 79 | 20 | 74 | 6F | 70 | 20 | 73 | 65 | 63 | 72 | 65 | 74 | 20 | 6B | 65 |

[16] 79 | 20 | 69 | 73 | 20 | 76 | 65 | 72 | 79 | 20 | 73 | 74 | 72 | 6F | 6E | 67 |

Text=Let's encrypt this message / ok

StringToCharArray(Text,data,0,-1,CP_UTF8)=26 / ok

- 0 CRYPT_BASE64, key required: false

CryptEncode(method,data,key,result)=36 / ok

TGV0J3MgZW5jcnlwdCB0aGlzIG1lc3NhZ2U=

[00] 54 | 47 | 56 | 30 | 4A | 33 | 4D | 67 | 5A | 57 | 35 | 6A | 63 | 6E | 6C | 77 |

[16] 64 | 43 | 42 | 30 | 61 | 47 | 6C | 7A | 49 | 47 | 31 | 6C | 63 | 33 | 4E | 68 |

[32] 5A | 32 | 55 | 3D |

- 1 CRYPT_AES128, key required: true

CryptEncode(method,data,key,result)=32 / ok

¯T* Ë[3hß Ã/-C }¬ŠÑØN¨®Ê† ‡Ñ

[00] 01 | 0B | AF | 54 | 2A | 12 | CB | 5B | 33 | 68 | DF | 0E | C3 | 2F | 2D | 43 |

[16] 19 | 7D | AC | 8A | D1 | 8F | D8 | 4E | A8 | AE | CA | 81 | 86 | 06 | 87 | D1 |

CryptEncode(CRYPT_BASE64,result,dummy,readable)=44 / ok

Try to decode this with CryptDecode.mq5 (CRYPT_AES128):

base64:'AQuvVCoSy1szaN8Owy8tQxl9rIrRj9hOqK7KgYYGh9E='

- 2 CRYPT_AES256, key required: true

CryptEncode(method,data,key,result)=32 / ok

ø‘UL»ÉsëDC‰ô ¬.K)ŒýÁ LḠ+< !Dï

[00] F8 | 91 | 55 | 4C | BB | C9 | 73 | EB | 44 | 43 | 89 | F4 | 06 | 13 | AC | 2E |

[16] 4B | 29 | 8C | FD | C1 | 11 | 4C | E1 | B8 | 05 | 2B | 3C | 14 | 21 | 44 | EF |

CryptEncode(CRYPT_BASE64,result,dummy,readable)=44 / ok

Try to decode this with CryptDecode.mq5 (CRYPT_AES256):

base64:'+JFVTLvJc+tEQ4n0BhOsLkspjP3BEUzhuAUrPBQhRO8='

- 3 CRYPT_DES, key required: true

CryptEncode(method,data,key,result)=32 / ok

µ b &“#ÇÅ+ýº'¥ B8f¡rØ-Pè<6âì‚Ë£

[00] B5 | 06 | 9D | 62 | 11 | 26 | 93 | 23 | C7 | C5 | 2B | FD | BA | 27 | A5 | 10 |

[16] 42 | 38 | 66 | A1 | 72 | D8 | 2D | 50 | E8 | 3C | 36 | E2 | EC | 82 | CB | A3 |

CryptEncode(CRYPT_BASE64,result,dummy,readable)=44 / ok

Try to decode this with CryptDecode.mq5 (CRYPT_DES):

base64:'tQadYhEmkyPHxSv9uielEEI4ZqFy2C1Q6Dw24uyCy6M='

- 4 CRYPT_HASH_SHA1, key required: false

CryptEncode(method,data,key,result)=20 / ok

§ßö*©ºø

€|)bËbzÇ͠ۀ

[00] A7 | DF | F6 | 2A | A9 | BA | F8 | 0A | 80 | 7C | 29 | 62 | CB | 62 | 7A | C7 |

[16] CD | 0E | DB | 80 |

CryptEncode(CRYPT_BASE64,result,dummy,readable)=28 / ok

Try to decode this with CryptDecode.mq5 (CRYPT_HASH_SHA1):

base64:'p9/2Kqm6+AqAfCliy2J6x80O24A='

- 5 CRYPT_HASH_SHA256, key required: false

CryptEncode(method,data,key,result)=32 / ok

ÚZ2š€»”¾7 €… ñ—ÄÁ´˜¦“ome2r@¾ô®³”

[00] DA | 5A | 32 | 9A | 80 | BB | 94 | BE | 37 | 0C | 80 | 85 | 07 | F1 | 96 | C4 |

[16] C1 | B4 | 98 | A6 | 93 | 6F | 6D | 65 | 32 | 72 | 40 | BE | F4 | AE | B3 | 94 |

CryptEncode(CRYPT_BASE64,result,dummy,readable)=44 / ok

Try to decode this with CryptDecode.mq5 (CRYPT_HASH_SHA256):

base64:'2loymoC7lL43DICFB/GWxMG0mKaTb21lMnJAvvSus5Q='

- 6 CRYPT_HASH_MD5, key required: false

CryptEncode(method,data,key,result)=16 / ok

zIGT…  Fû;–3þèå

[00] 7A | 49 | 47 | 54 | 85 | 1B | 7F | 11 | 46 | FB | 3B | 97 | 33 | FE | E8 | E5 |

CryptEncode(CRYPT_BASE64,result,dummy,readable)=24 / ok

Try to decode this with CryptDecode.mq5 (CRYPT_HASH_MD5):

base64:'eklHVIUbfxFG+zuXM/7o5Q=='

- 7 CRYPT_ARCH_ZIP, key required: false

CryptEncode(method,data,key,result)=34 / ok

x^óI-Q/VHÍK.ª,(Q(ÉÈ,VÈM-.NLO

[00] 78 | 5E | F3 | 49 | 2D | 51 | 2F | 56 | 48 | CD | 4B | 2E | AA | 2C | 28 | 51 |

[16] 28 | C9 | C8 | 2C | 56 | C8 | 4D | 2D | 2E | 4E | 4C | 4F | 05 | 00 | 80 | 07 |

[32] 09 | C2 |

CryptEncode(CRYPT_BASE64,result,dummy,readable)=48 / ok

Try to decode this with CryptDecode.mq5 (CRYPT_ARCH_ZIP):

base64:'eF7zSS1RL1ZIzUsuqiwoUSjJyCxWyE0tLk5MTwUAgAcJwg=='

En este caso, la clave tiene longitud suficiente para los tres métodos de cifrado, y los demás métodos para los que no es necesaria simplemente la ignoran. Por lo tanto, todas las llamadas a funciones se han completado con éxito.

En la siguiente sección, aprenderemos a descodificar cifrados y podremos comprobar si la función CryptDecode devuelve el mensaje original. Tenga en cuenta esta parte del registro.

La opción DisableCRCinZIP activada reducirá el resultado del método CRYPT_ARCH_ZIP en unos pocos bytes de sobrecarga.

- 7 CRYPT_ARCH_ZIP, key required: false

CryptEncode(method,data,key,result)=28 / ok

óI-Q/VHÍK.ª,(Q(ÉÈ,VÈM-.NLO

[00] F3 | 49 | 2D | 51 | 2F | 56 | 48 | CD | 4B | 2E | AA | 2C | 28 | 51 | 28 | C9 |

[16] C8 | 2C | 56 | C8 | 4D | 2D | 2E | 4E | 4C | 4F | 05 | 00 |

CryptEncode(CRYPT_BASE64,result,dummy,readable)=40 / ok

Try to decode this with CryptDecode.mq5 (CRYPT_ARCH_ZIP):

base64:'80ktUS9WSM1LLqosKFEoycgsVshNLS5OTE8FAA=='

Ahora vamos a trasladar los experimentos sobre codificación a los archivos. Para ello, ejecute de nuevo el script y borre el texto del campo Text. Como resultado, el programa procesará el archivo MQL5Book/clock10.htm varias veces y creará varios archivos derivados con diferentes extensiones.

File=MQL5Book/clock10.htm / ok
FileLoad(File,data)=988 / ok
- 0 CRYPT_BASE64, key required: false
CryptEncode(method,data,key,result)=1320 / ok
FileSave(filename,result)=true / ok
File saved: MQL5Book/clock10.htm.BASE64
- 1 CRYPT_AES128, key required: true
CryptEncode(method,data,key,result)=992 / ok
FileSave(filename,result)=true / ok
File saved: MQL5Book/clock10.htm.AES128
- 2 CRYPT_AES256, key required: true
CryptEncode(method,data,key,result)=992 / ok
FileSave(filename,result)=true / ok
File saved: MQL5Book/clock10.htm.AES256
- 3 CRYPT_DES, key required: true
CryptEncode(method,data,key,result)=992 / ok
FileSave(filename,result)=true / ok
File saved: MQL5Book/clock10.htm.DES
- 4 CRYPT_HASH_SHA1, key required: false
CryptEncode(method,data,key,result)=20 / ok
FileSave(filename,result)=true / ok
File saved: MQL5Book/clock10.htm.SHA1
[00] 486ADFDD071CD23AB28E820B164D813A310B213F
- 5 CRYPT_HASH_SHA256, key required: false
CryptEncode(method,data,key,result)=32 / ok
FileSave(filename,result)=true / ok
File saved: MQL5Book/clock10.htm.SHA256
[00] 8990BBAC9C23B1F987952564EBCEF2078232D8C9D6F2CCC2A50784E8CDE044D0
- 6 CRYPT_HASH_MD5, key required: false
CryptEncode(method,data,key,result)=16 / ok
FileSave(filename,result)=true / ok
File saved: MQL5Book/clock10.htm.MD5
[00] 0CC4FBC899554BE0C0DBF5C18748C773
- 7 CRYPT_ARCH_ZIP, key required: false
CryptEncode(method,data,key,result)=687 / ok
FileSave(filename,result)=true / ok
File saved: MQL5Book/clock10.htm.ZIP

Puede mirar dentro de todos los archivos desde el gestor de archivos y asegurarse de que no queda nada en común con el contenido original. Muchos administradores de archivos tienen comandos o plugins para calcular sumas hash de modo que puedan compararse con valores hexadecimales MD5, SHA1 y SHA256 impresos en el registro.

Si intentamos codificar un texto o un archivo sin proporcionar una clave de la longitud correcta, obtendremos un error INVALID_ARRAY(4006). Por ejemplo, para un mensaje de texto predeterminado, seleccionamos AES256 en el parámetro method (requiere una clave de 32 bytes). Utilizando el parámetro GenerateKey, pedimos una clave con una longitud de 16 bytes (o puede eliminar parcial o totalmente el texto del campo CustomKey, dejando GenerateKey por defecto).

Key (bytes):
[00] 00 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 0A | 0B | 0C | 0D | 0E | 0F | 
Text=Let's encrypt this message / ok
StringToCharArray(Text,data,0,-1,CP_UTF8)=26 / ok
- 0 CRYPT_AES256, key required: true
CryptEncode(method,data,key,result)=0 / INVALID_ARRAY(4006)

También puede comprimir el mismo archivo (como hicimos con clock10.htm) utilizando el método CRYPT_ARCH_ZIP o un archivador normal. Si después se mira con una utilidad de visor binario (que suele estar integrada en el administrador de archivos), ambos resultados mostrarán un bloque empaquetado común, y las diferencias estarán solo en los metadatos que lo enmarcan.

Comparación de un archivo comprimido con el método CRYPT_ARCH_ZIP (izquierda) y un archivo ZIP estándar (derecha)

Comparación de un archivo comprimido con el método CRYPT_ARCH_ZIP (izquierda) y un archivo ZIP estándar (derecha)

Muestra que la parte central y principal del archivo es una secuencia de bytes (resaltados en oscuro) idéntica a la producida por la función CryptEncode.

Por último, mostraremos cómo se ha generado la representación textual Base64 de un archivo gráfico clock10.png. Para ello, borre el campo Text y escriba MQL5Book/clock10.png en el parámetro File. Elija Base64 en la lista desplegable Method.

File=MQL5Book/clock10.png / ok
FileLoad(File,data)=457 / ok
- 0 CRYPT_BASE64, key required: false
CryptEncode(method,data,key,result)=612 / ok
FileSave(filename,result)=true / ok
File saved: MQL5Book/clock10.png.BASE64

Como resultado, se ha creado el archivo clock10.png.BASE64. En su interior, veremos la propia línea que se inserta en el código de la página web, en la etiqueta img.

Por cierto: el método de compresión «deflate» es la base del formato gráfico PNG, por lo que podemos utilizar CryptEncode para guardar mapas de bits de recursos en archivos PNG. El archivo de encabezado PNG.mqh se incluye con el libro, con un soporte mínimo para las estructuras internas necesarias para describir la imagen: se sugiere experimentar con su código fuente de forma independiente. Utilizando PNG.mqh hemos escrito un sencillo script CryptPNG.mq5 que convierte el recurso del archivo «euro.bmp» suministrado con el terminal al archivo «my.png». La carga de archivos PNG no está implementada.

#resource "\\Images\\euro.bmp"
   
#include <MQL5Book/PNG.mqh>
   
void OnStart()
{
   uchar null[];      // empty key for CRYPT_ARCH_ZIP
   uchar result[];    // receiving array
   uint data[];       // original pixels
   uchar bytes[];     // original bytes
   int widthheight;
   PRTF(ResourceReadImage("::Images\\euro.bmp"datawidthheight));
   
   ArrayResize(bytesArraySize(data) * 3 + width); // *3 for PNG_CTYPE_TRUECOLOR (RGB)
   ArrayInitialize(bytes0);
   int j = 0;
   for(int i = 0i < ArraySize(data); ++i)
   {
      if(i % width == 0bytes[j++] = 0// each line is prepended with a filter mode byte
      const uint c = data[i];
      // bytes[j++] = (uchar)((c >> 24) & 0xFF); // alpha, for PNG_CTYPE_TRUECOLORALPHA (ARGB)
      bytes[j++] = (uchar)((c >> 16) & 0xFF);
      bytes[j++] = (uchar)((c >> 8) & 0xFF);
      bytes[j++] = (uchar)(c & 0xFF);
   }
   
   PRTF(CryptEncode(CRYPT_ARCH_ZIPbytesnullresult));
   
   int h = PRTF(FileOpen("my.png"FILE_BIN | FILE_WRITE));
   
   PNG::Image image(widthheightresult); // default PNG_CTYPE_TRUECOLOR (RGB)
   image.write(h);
   
   FileClose(h);
}