Защита советника

 
Всем доброго дня. 
Кто знает, подскажите пожалуйста. Как в советнике сделать правильно защиту на копирование? 
Я планирую вставлять в код номер счета , который в мт сверху показан. И после компилировать и передавать человеку. 
И чтобы он работал и на демо счетах. Как это правильно сделать?
 
Mikhail Ostashov:
Всем доброго дня. 
Кто знает, подскажите пожалуйста. Как в советнике сделать правильно защиту на копирование? 
Я планирую вставлять в код номер счета , который в мт сверху показан. И после компилировать и передавать человеку. 
И чтобы он работал и на демо счетах. Как это правильно сделать?

Самое базовое - нужно применить шифрование, везде где важные данные нужно применять криптографию. Такие данные должны шифроваться.

Номер счета в исходнике лучше вообще не хранить - следует хранить только проверочный отпечаток.

++ Оптимально это HMAC-SHA-256 , так как он использует дополнительный секретный ключ и значительно усложняет перебор.

++ MQL5 штатно поддерживает SHA-256 и AES-256 через через CryptEncode() . Номер текущего счёта получается через AccountInfoInteger(ACCOUNT_LOGIN) , а сервер - через AccountInfoString(ACCOUNT_SERVER) .


//+------------------------------------------------------------------+
//| Преобразование строки в UTF-8 без завершающего нуля              |
//+------------------------------------------------------------------+
bool StringToUtf8Bytes(const string text, uchar &result[])
{
   int copied = StringToCharArray(
      text,
      result,
      0,
      WHOLE_ARRAY,
      CP_UTF8
   );

   if(copied <= 0)
      return false;

   // StringToCharArray при WHOLE_ARRAY добавляет завершающий 0.
   ArrayResize(result, copied - 1);

   return true;
}

//+------------------------------------------------------------------+
//| SHA-256                                                          |
//+------------------------------------------------------------------+
bool Sha256Bytes(const uchar &data[], uchar &digest[])
{
   uchar empty_key[];

   ResetLastError();

   int written = CryptEncode(
      CRYPT_HASH_SHA256,
      data,
      empty_key,
      digest
   );

   if(written != 32)
   {
      PrintFormat(
         "Ошибка SHA-256. Код: %d, записано байт: %d",
         GetLastError(),
         written
      );

      return false;
   }

   ArrayResize(digest, 32);
   return true;
}

//+------------------------------------------------------------------+
//| HMAC-SHA-256                                                     |
//+------------------------------------------------------------------+
bool HmacSha256(
   const string secret,
   const string message,
   uchar &mac[]
)
{
   const int BLOCK_SIZE = 64;

   uchar key[];
   uchar message_bytes[];

   if(!StringToUtf8Bytes(secret, key))
      return false;

   if(!StringToUtf8Bytes(message, message_bytes))
      return false;

   // Если ключ длиннее блока SHA-256, сначала хешируем его.
   if(ArraySize(key) > BLOCK_SIZE)
   {
      uchar shortened_key[];

      if(!Sha256Bytes(key, shortened_key))
         return false;

      ArrayResize(key, ArraySize(shortened_key));
      ArrayCopy(key, shortened_key);
   }

   // Ключ, дополненный нулями до 64 байт.
   uchar padded_key[];
   ArrayResize(padded_key, BLOCK_SIZE);
   ArrayInitialize(padded_key, 0);

   if(ArraySize(key) > 0)
      ArrayCopy(padded_key, key, 0, 0, ArraySize(key));

   uchar inner_data[];
   uchar outer_data[];

   ArrayResize(
      inner_data,
      BLOCK_SIZE + ArraySize(message_bytes)
   );

   // inner pad = key XOR 0x36
   for(int i = 0; i < BLOCK_SIZE; i++)
      inner_data[i] = (uchar)(padded_key[i] ^ 0x36);

   if(ArraySize(message_bytes) > 0)
   {
      ArrayCopy(
         inner_data,
         message_bytes,
         BLOCK_SIZE,
         0,
         ArraySize(message_bytes)
      );
   }

   uchar inner_hash[];

   if(!Sha256Bytes(inner_data, inner_hash))
      return false;

   ArrayResize(
      outer_data,
      BLOCK_SIZE + ArraySize(inner_hash)
   );

   // outer pad = key XOR 0x5C
   for(int i = 0; i < BLOCK_SIZE; i++)
      outer_data[i] = (uchar)(padded_key[i] ^ 0x5C);

   ArrayCopy(
      outer_data,
      inner_hash,
      BLOCK_SIZE,
      0,
      ArraySize(inner_hash)
   );

   return Sha256Bytes(outer_data, mac);
}

//+------------------------------------------------------------------+
//| Байты в HEX                                                      |
//+------------------------------------------------------------------+
string BytesToHex(const uchar &data[])
{
   string result = "";

   for(int i = 0; i < ArraySize(data); i++)
      result += StringFormat("%.2X", data[i]);

   return result;
}


Формирование идентификатора лицензии

string GetCurrentLicensePayload()
{
   long login   = AccountInfoInteger(ACCOUNT_LOGIN);
   string server = AccountInfoString(ACCOUNT_SERVER);

   return StringFormat(
      "%I64d|%s|MY_EA_V1",
      login,
      server
   );
}


Проверка при запуске советника

const string LICENSE_MAC =
   "ВСТАВЬТЕ_СЮДА_64_СИМВОЛА_HEX";

// Сгенерируй случайную строку минимум из 32 байт.
// Не используй пароль вроде "12345" или название советника.
string GetLicenseSecret()
{
   return
      "D7A9F4C8216E53B0"
      "19C8A42F6D03E7B5"
      "A8E176C94B2D50F3"
      "63AF9082C1E74D5B";
}

bool IsAccountLicensed()
{
   string payload = GetCurrentLicensePayload();

   uchar mac[];

   if(!HmacSha256(GetLicenseSecret(), payload, mac))
      return false;

   string calculated_mac = BytesToHex(mac);

   return calculated_mac == LICENSE_MAC;
}

int OnInit()
{
   if(!IsAccountLicensed())
   {
      PrintFormat(
         "Советник не лицензирован для счета %I64d, сервер %s",
         AccountInfoInteger(ACCOUNT_LOGIN),
         AccountInfoString(ACCOUNT_SERVER)
      );

      return INIT_FAILED;
   }

   return INIT_SUCCEEDED;
}


Скрипт для генерации LICENSE_MAC - этот код запускается только у разработчика. Его не нужно передавать пользователю.

#property script_show_inputs

input long   LicenseAccount = 12345678;
input string LicenseServer  = "Broker-Live";

void OnStart()
{
   string payload = StringFormat(
      "%I64d|%s|MY_EA_V1",
      LicenseAccount,
      LicenseServer
   );

   uchar mac[];

   if(!HmacSha256(GetLicenseSecret(), payload, mac))
   {
      Print("Не удалось создать лицензионный HMAC");
      return;
   }

   Print("Payload: ", payload);
   Print("LICENSE_MAC = \"", BytesToHex(mac), "\";");
}


Скопированный результат вставляется в пользовательскую версию советника:

const string LICENSE_MAC =
   "5F601A32B2D6A35C3685D61EBB5BE6D4D422B58BA21A89CE0AB4AC37D271D743";
 
Igor Zakharev #:

Самое базовое - нужно применить шифрование, везде где важные данные нужно применять криптографию. Такие данные должны шифроваться.

Номер счета в исходнике лучше вообще не хранить - следует хранить только проверочный отпечаток.

++ Оптимально это HMAC-SHA-256 , так как он использует дополнительный секретный ключ и значительно усложняет перебор.

++ MQL5 штатно поддерживает SHA-256 и AES-256 через через CryptEncode() . Номер текущего счёта получается через AccountInfoInteger(ACCOUNT_LOGIN) , а сервер - через AccountInfoString(ACCOUNT_SERVER) .



Формирование идентификатора лицензии


Проверка при запуске советника


Скрипт для генерации LICENSE_MAC - этот код запускается только у разработчика. Его не нужно передавать пользователю.


Скопированный результат вставляется в пользовательскую версию советника:

Благодарю 🤝. Всех благ вам 
 
Igor Zakharev #:

Самое базовое - нужно применить шифрование, везде где важные данные нужно применять криптографию. Такие данные должны шифроваться.

Номер счета в исходнике лучше вообще не хранить - следует хранить только проверочный отпечаток.

++ Оптимально это HMAC-SHA-256 , так как он использует дополнительный секретный ключ и значительно усложняет перебор.

++ MQL5 штатно поддерживает SHA-256 и AES-256 через через CryptEncode() . Номер текущего счёта получается через AccountInfoInteger(ACCOUNT_LOGIN) , а сервер - через AccountInfoString(ACCOUNT_SERVER) .



Формирование идентификатора лицензии


Проверка при запуске советника


Скрипт для генерации LICENSE_MAC - этот код запускается только у разработчика. Его не нужно передавать пользователю.


Скопированный результат вставляется в пользовательскую версию советника:

Серьезно? 
Т. е., если онинит подменить, то защита всë?