Дешифрование и распаковка данных: CryptDecode

Для выполнения операций дешифрования и распаковки данных в MQL5 предусмотрена функция CryptDecode.

Функция CryptDecode производит обратное преобразование данных массива data в приемный массив result, используя указанный метод.

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

Обратите внимание, что получение хэш-сумм, выполняемое, в частности, функцией CryptEncode, является односторонним преобразованием: из хэшей нельзя восстановить исходные данные.

Функция возвращает количество байтов, помещенных в массив-приемник или 0 в случае ошибки. Код ошибки попадет в _LastError. Это может быть, например, INVALID_PARAMETER (4003), если пытаться декодировать хэш (method равен одной из CRYPT_HASH-констант) или INVALID_ARRAY (4006), если ключ дешифрования недостаточной длины или отсутствует.

Если ключ неправильный (отличается от того, который использовался при шифровке), получим абракадабру в результате вместо закодированных исходных данных, но код ошибки при этом — нулевой. Это штатное поведение функции.

Проверим работу CryptDecode с помощью одноименного скрипта CryptDecode.mq5.

Во входных параметрах можно указать текст или файл для преобразования. Текст всегда подразумевается в кодировке Base64, так как все закодированные данные имеют двоичный формат и не поддерживаются в input-ах. Метод преобразования выбирается в списке Method.

input string Text// Text (base64, or empty to process File)
input string File = "MQL5Book/clock10.htm.BASE64";
input ENUM_CRYPT_METHOD_EXT Method = _CRYPT_BASE64;

Для методов шифрования необходим ключ, который можно ввести как строку в поле CustomKey, если GenerateKey содержит опцию DUMMY_KEY_CUSTOM, или сгенерировать демо-ключ требуемой длины из перечисления DUMMY_KEY_LENGTH (оно такое же, как в скрипте CryptEncode.mq5).

input DUMMY_KEY_LENGTH GenerateKey = DUMMY_KEY_CUSTOM// GenerateKey (длина, или из CustomKey)
input string CustomKey = "My top secret key is very strong"
input bool DisableCRCinZIP = false;

В GenerateKey и CustomKey нужно выбирать те же значения, что и при запуске CryptEncode.mq5.

Алгоритм в OnStart начинается с описания требуемых массивов и получения ключа из строки или путем незатейливой генерации (только для демо, для генерации рабочего криптостойкого ключа используйте специальный софт или алгоритмы).

void OnStart()
{
   ENUM_CRYPT_METHOD method = 0;
   int methods[];
   uchar key[] = {};        // пустой по умолчанию ключ подходит для zip и base64
   uchar data[], result[];
   uchar zip[], opt[] = {1000};
   
   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;
   }
   
   if(ArraySize(key))
   {   
      Print("Key (bytes):");
      ByteArrayPrint(key);
   }
   else
   {
      Print("Key is not provided");
   }

Далее мы считываем содержимое файла или декодируем Base64 из поля Text (в зависимости от того, что заполнено), чтобы получить данные для обработки.

   method = (ENUM_CRYPT_METHOD)Method;
   Print("- "EnumToString(method), ", key required: "KEY_REQUIRED(method));
   if(StringLen(Text))
   {
      if(method != CRYPT_BASE64)
      {
         // так как все методы, кроме Base64, выдают двоичные результаты,
         // они дополнительно конвертируются в CryptEncode.mq5 с помощью Base64 в текст,
         // поэтому здесь требуется восстановить двоичные данные из текстового ввода
         // перед расшифровкой
         uchar base64[];
         const uchar dummy[] = {};
         PRTF(Text);
         PRTF(StringToCharArray(Textbase640, -1, CP_UTF8));
         ArrayResize(base64ArraySize(base64) - 1);
         Print("Text (bytes):");
         ByteArrayPrint(base64);
         if(!PRTF(CryptDecode(CRYPT_BASE64base64dummydata)))
         {
            return// ошибка
         }
         
         Print("Raw data to decipher (after de-base64):");
         ByteArrayPrint(data);
      }
      else
      {
         PRTF(StringToCharArray(Textdata0StringLen(Text), CP_UTF8));
         ArrayResize(dataArraySize(data) - 1);
      }
   }
   else if(StringLen(File))
   {
      PRTF(File);
      if(PRTF(FileLoad(Filedata)) <= 0)
      {
         return// ошибка
      }
   }

Если пользователь пытается восстановить данные из хэша, выдаем предупреждение, что ничего не получится.

   if(IS_HASH(method))
   {
      Print("WARNING: hashes can not be used to restore data! CryptDecode will fail.");
   }

Наконец, выполняем непосредственно расшифровку или распаковку. В случае текста полученный результат просто выводится в журнал. В случае файла мы добавляем к имени расширение ".dec" и записываем новый файл: его можно будет сравнить с исходным, который обрабатывался скриптом CryptEncode.mq5.

   ResetLastError();
   if(PRTF(CryptDecode(methoddatakeyresult)))
   {
      if(StringLen(Text))
      {
         Print("Text restored:");
         Print(CharArrayToString(result0WHOLE_ARRAYCP_UTF8));
      }
      else // File
      {
         const string filename = File + ".dec";
         if(PRTF(FileSave(filenameresult)))
         {
            Print("File saved: "filename);
         }
      }
   }

Если запустить скрипт с настройками по умолчанию, он попытается раскодировать файл MQL5Book/clock10.htm.BASE64. Предполагается, что такой был создан в ходе экспериментов в предыдущем разделе, поэтому процесс должен пройти успешно.

- CRYPT_BASE64, key required: false
File=MQL5Book/clock10.htm.BASE64 / ok
FileLoad(File,data)=1320 / ok
CryptDecode(method,data,key,result)=988 / ok
FileSave(filename,result)=true / ok
File saved: MQL5Book/clock10.htm.BASE64.dec

Полученный файл clock10.htm.BASE64.dec полностью совпадает с исходным clock10.htm. То же самое должно произойти, если расшифровывать файлы с расширениями AES128, AES256, DES, при условии, что вы укажете тот же ключ, что и при шифровании.

Для наглядности проверим дешифровку текста. Так, шифрование известной фразы методом AES128 выдало нам ранее двоичный результат, превращенный для удобства в такую Base64-строку.

AQuvVCoSy1szaN8Owy8tQxl9rIrRj9hOqK7KgYYGh9E=

Введем её в поле Text, а в выпадающем списке Method выберем AES128. Увидим в журнале следующее.

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 | 

- CRYPT_AES128, key required: true

Text=AQuvVCoSy1szaN8Owy8tQxl9rIrRj9hOqK7KgYYGh9E= / ok

StringToCharArray(Text,base64,0,-1,CP_UTF8)=44 / ok

Text (bytes):

[00] 41 | 51 | 75 | 76 | 56 | 43 | 6F | 53 | 79 | 31 | 73 | 7A | 61 | 4E | 38 | 4F | 

[16] 77 | 79 | 38 | 74 | 51 | 78 | 6C | 39 | 72 | 49 | 72 | 52 | 6A | 39 | 68 | 4F | 

[32] 71 | 4B | 37 | 4B | 67 | 59 | 59 | 47 | 68 | 39 | 45 | 3D | 

CryptDecode(CRYPT_BASE64,base64,dummy,data)=32 / ok

Raw data to decipher (after de-base64):

[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 | 

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

Text restored:

Let's encrypt this message

Сообщение успешно расшифровано.

Если при том же входном тексте выбрать генерацию произвольного ключа (хоть и достаточной длины), вместо сообщения получится тарабарщина.

Key (bytes):

[00] 00 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 0A | 0B | 0C | 0D | 0E | 0F | 

- CRYPT_AES128, key required: true

Text=AQuvVCoSy1szaN8Owy8tQxl9rIrRj9hOqK7KgYYGh9E= / ok

StringToCharArray(Text,base64,0,-1,CP_UTF8)=44 / ok

Text (bytes):

[00] 41 | 51 | 75 | 76 | 56 | 43 | 6F | 53 | 79 | 31 | 73 | 7A | 61 | 4E | 38 | 4F | 

[16] 77 | 79 | 38 | 74 | 51 | 78 | 6C | 39 | 72 | 49 | 72 | 52 | 6A | 39 | 68 | 4F | 

[32] 71 | 4B | 37 | 4B | 67 | 59 | 59 | 47 | 68 | 39 | 45 | 3D | 

CryptDecode(CRYPT_BASE64,base64,dummy,data)=32 / ok

Raw data to decipher (after de-base64):

[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 | 

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

Text restored:

��� �L�� ��J Q+�]�v�9�����n?N�Ű

Аналогично поведет себя программа и если перепутать метод шифрования.

Выбирать методы "расхэширования" не имеет смысла — INVALID_PARAMETER (4003).

- CRYPT_HASH_MD5, key required: false
File=MQL5Book/clock10.htm.MD5 / ok
FileLoad(File,data)=16 / ok
WARNING: hashes can not be used to restore data! CryptDecode will fail.
CryptDecode(method,data,key,result)=0 / INVALID_PARAMETER(4003)

А вот попытка выполнить распаковку (CRYPT_ARCH_ZIP) того, что не является упакованным блоком "deflate", приведет к INTERNAL_ERROR (4001). Такую же ошибку можно получить, если включить опцию пропуска CRC для "архива" без неё или, наоборот, разжимать данные без опции, хотя упаковка делалась с ней.