English Русский 中文 Español Deutsch 日本語 Português 한국어 Français Türkçe
Sicurezza del codice MQL5: Protezione con password, generatori di chiavi, limiti di tempo, licenze remote e tecniche avanzate di crittografia delle chiavi di licenza EA

Sicurezza del codice MQL5: Protezione con password, generatori di chiavi, limiti di tempo, licenze remote e tecniche avanzate di crittografia delle chiavi di licenza EA

MetaTrader 5Esempi | 17 dicembre 2021, 16:04
765 0
investeo
investeo

Introduzione

La maggior parte degli sviluppatori ha bisogno di proteggere il proprio codice. Questo articolo presenterà alcuni modi diversi per proteggere il software MQL5. Tutti gli esempi nell'articolo faranno riferimento agli Expert Advisor, ma le stesse regole possono essere applicate a Script e Indicatori. L'articolo inizia con una semplice protezione tramite password e prosegue con generatori di chiavi, concessione in licenza di un determinato account broker e protezione a tempo. Quindi introduce un concetto di server di licenza remoto. Il mio ultimo articolo sul framework MQL5-RPC ha descritto le chiamate di procedura remota da MetaTrader 5 a qualsiasi server XML-RPC.

Utilizzerò questa soluzione per fornire un esempio di licenza remota. Descriverò anche come migliorare questa soluzione con la codifica base64 e fornirò consigli per il supporto PGP per realizzare una protezione ultra sicura per Expert Advisor e Indicatori MQL5. Sono consapevole che MetaQuotes Software Corp. fornisce alcune opzioni per la licenza del codice direttamente dalla sezione MQL5.com Market. Questo è davvero un bene per tutti gli sviluppatori e non invaliderà le idee presentate in questo articolo. Entrambe le soluzioni utilizzate insieme possono solo rendere la protezione più forte e più sicura contro il furto di software.


1. Protezione della password

Cominciamo con qualcosa di semplice. La prima soluzione più utilizzata per la protezione del software del computer è la protezione tramite password o chiave di licenza. Durante la prima esecuzione dopo l'installazione, all'utente viene chiesto con una finestra di dialogo di inserire una password associata a una copia del software (come la chiave seriale di Microsoft Windows o Microsoft Office) e, se la password inserita corrisponde, all'utente è consentito utilizzare una singola copia registrata di un software. Possiamo usare una variabile di input o una casella di testo diretta per inserire il codice. Di seguito è mostrato un codice stub di esempio.

Il codice seguente inizializza un campo CChartObjectEdit che viene utilizzato per inserire una password. Esiste un array predefinito di password consentite che viene confrontata con una password inserita da un utente. La password viene verificata nel metodo OnChartEvent() dopo aver ricevuto l'evento CHARTEVENT_OBJECT_ENDEDIT.

//+------------------------------------------------------------------+
//|                                          PasswordProtectedEA.mq5 |
//|                                      Copyright 2012, Investeo.pl |
//|                                           http://www.investeo.pl |
//+------------------------------------------------------------------+
#property copyright "Copyright 2012, Investeo.pl"
#property link      "http://www.investeo.pl"
#property version   "1.00"

#include <ChartObjects/ChartObjectsTxtControls.mqh>

CChartObjectEdit password_edit;

const string allowed_passwords[] = { "863H-6738725-JG76364",
                             "145G-8927523-JG76364",
                             "263H-7663233-JG76364" };
                             
int    password_status = -1;
string password_message[] = { "WRONG PASSWORD. Trading not allowed.",
                         "EA PASSWORD verified." };

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   password_edit.Create(0, "password_edit", 0, 10, 10, 260, 25);
   password_edit.BackColor(White);
   password_edit.BorderColor(Black);
   password_edit.SetInteger(OBJPROP_SELECTED, 0, true);
//---
   return(0);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   password_edit.Delete();
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
  if (password_status>0) 
  {
    // password correct
  } 
  }
//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//---
   if (id == CHARTEVENT_OBJECT_ENDEDIT && sparam == "password_edit" )
      {
         password_status = -1;
         
         for (int i=0; i<ArraySize(allowed_passwords); i++)
            if (password_edit.GetString(OBJPROP_TEXT) == allowed_passwords[i]) 
            {
               password_status = i;
               break;
            }
            
         if (password_status == -1) 
            password_edit.SetString(OBJPROP_TEXT, 0, password_message[0]);
         else 
            password_edit.SetString(OBJPROP_TEXT, 0, password_message[1]); 
      }
  }
//+------------------------------------------------------------------+

Questo metodo è semplice, ma è possibile che qualcuno pubblichi la password su un sito web con numeri di serie compromessi. L'autore dell’EA non può fare nulla finché non viene rilasciato un nuovo Expert Advisor e la password rubata non viene inserita nella lista nera.


2. Generatore di chiavi

I generatori di chiavi sono un meccanismo che consente di utilizzare un insieme di password basate su regole predefinite. Darò una panoramica fornendo uno stub per un generatore di chiavi di seguito. Nell'esempio presentato di seguito la chiave deve essere composta da tre numeri separati da due trattini. Pertanto il formato consentito per una password è XXXXX-XXXXX-XXXXX.

Il primo numero deve essere divisibile per 3, il secondo numero deve essere divisibile per 4 e il terzo numero deve essere divisibile per 5. Pertanto, le password consentite possono essere 3-4-5, 18000-20000-20000 o quella più complicata 3708-102792-2844770.

//+------------------------------------------------------------------+
//|                                      KeyGeneratorProtectedEA.mq5 |
//|                                      Copyright 2012, Investeo.pl |
//|                                           http://www.investeo.pl |
//+------------------------------------------------------------------+
#property copyright "Copyright 2012, Investeo.pl"
#property link      "http://www.investeo.pl"
#property version   "1.00"

#include <ChartObjects/ChartObjectsTxtControls.mqh>
#include <Strings/String.mqh>

CChartObjectEdit password_edit;
CString user_pass;

const double divisor_sequence[] = { 3.0, 4.0, 5.0 };
                             
int    password_status = -1;
string password_message[] = { "WRONG PASSWORD. Trading not allowed.",
                         "EA PASSWORD verified." };

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   password_edit.Create(0, "password_edit", 0, 10, 10, 260, 25);
   password_edit.BackColor(White);
   password_edit.BorderColor(Black);
   password_edit.SetInteger(OBJPROP_SELECTED, 0, true);
//---
   return(0);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   password_edit.Delete();
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
  if (password_status==3) 
  {
    // password correct
  } 
  }
  
//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//---
   if (id == CHARTEVENT_OBJECT_ENDEDIT && sparam == "password_edit" )
      {
         password_status = 0;
         
         user_pass.Assign(password_edit.GetString(OBJPROP_TEXT));

         int hyphen_1 = user_pass.Find(0, "-");
         int hyphen_2 = user_pass.FindRev("-");
         
         if (hyphen_1 == -1 || hyphen_2 == -1 || hyphen_1 == hyphen_2) {
            password_edit.SetString(OBJPROP_TEXT, 0, password_message[0]);
            return;
         } ;     
         
         long pass_1 = StringToInteger(user_pass.Mid(0, hyphen_1));
         long pass_2 = StringToInteger(user_pass.Mid(hyphen_1 + 1, hyphen_2));
         long pass_3 = StringToInteger(user_pass.Mid(hyphen_2 + 1, StringLen(user_pass.Str())));
         
         // PrintFormat("%d : %d : %d", pass_1, pass_2, pass_3);
         
         if (MathIsValidNumber(pass_1) && MathMod((double)pass_1, divisor_sequence[0]) == 0.0) password_status++;
         if (MathIsValidNumber(pass_2) && MathMod((double)pass_2, divisor_sequence[1]) == 0.0) password_status++;
         if (MathIsValidNumber(pass_3) && MathMod((double)pass_3, divisor_sequence[2]) == 0.0) password_status++;
            
         if (password_status != 3) 
            password_edit.SetString(OBJPROP_TEXT, 0, password_message[0]);
         else
            password_edit.SetString(OBJPROP_TEXT, 0, password_message[1]); 
      }
  }
//+------------------------------------------------------------------+

Ovviamente, il numero di cifre in un numero può essere impostato su un dato valore e i calcoli possono essere più complicati. Si potrebbe anche aggiungere una variabile valida solo con un determinato hardware, aggiungendo al calcolo il numero di serie dell'HDD o l'ID della CPU. In tal caso, per eseguire l'EA si dovrebbe eseguire un generatore aggiuntivo calcolato sulla base dell'hardware.

L'output sarebbe un input per un keygen e la password generata sarebbe valida solo per un determinato hardware. Ciò pone il limite per qualcuno che cambia l'hardware del computer o usa VPS per l'esecuzione di EA, ma si potrebbe risolvere dando via due o tre password valide. Questo è anche il caso nella sezione Market del sito MQL5.


3. Licenza per account singolo

Poiché il numero di conto del terminale di un dato broker è unico, questo può essere utilizzato per consentire l'utilizzo dell'EA su uno o un insieme di numeri di conto. In tal caso, è sufficiente utilizzare i metodi AccountInfoString(ACCOUNT_COMPANY) e AccountInfoInteger(ACCOUNT_LOGIN) per recuperare i dati dell'account e confrontarli con i valori consentiti precompilati:

//+------------------------------------------------------------------+
//|                                           AccountProtectedEA.mq5 |
//|                                      Copyright 2012, Investeo.pl |
//|                                           http://www.investeo.pl |
//+------------------------------------------------------------------+
#property copyright "Copyright 2012, Investeo.pl"
#property link      "http://www.investeo.pl"
#property version   "1.00"


const string allowed_broker = "MetaQuotes Software Corp.";
const long allowed_accounts[] = { 979890, 436290, 646490, 225690, 279260 };
                             
int password_status = -1;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   string broker = AccountInfoString(ACCOUNT_COMPANY);
   long account = AccountInfoInteger(ACCOUNT_LOGIN);
   
   printf("The name of the broker = %s", broker);
   printf("Account number =  %d", account);
   
   if (broker == allowed_broker) 
      for (int i=0; i<ArraySize(allowed_accounts); i++)
       if (account == allowed_accounts[i]) { 
         password_status = 1;
         Print("EA account verified");
         break;
       }
   if (password_status == -1) Print("EA is not allowed to run on this account."); 
    
//---
   return(0);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---  
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
  if (password_status == 1) 
  {
    // password correct
  } 
  }

Questa è una protezione semplice, ma abbastanza potente. Lo svantaggio è che è necessario ricompilare l'EA per ogni nuovo numero di account aggiunto al database degli account.


4. Protezione a tempo

La protezione a tempo è utile quando la licenza viene concessa su base temporanea, ad esempio utilizzando la versione di prova del software o quando la licenza viene concessa su base mensile o annuale. È ovvio che questo può essere applicato per Expert Advisor e Indicatori.

La prima idea è quella di controllare l'ora del server e in base a ciò consentire all'utente di utilizzare l'indicatore o l’Expert Advisor entro un determinato periodo di tempo. Dopo la scadenza, il licenziante è in grado di disabilitare parzialmente o totalmente la sua funzionalità al licenziatario.

//+------------------------------------------------------------------+
//|                                         TimeLimitProtectedEA.mq5 |
//|                                      Copyright 2012, Investeo.pl |
//|                                           http://www.investeo.pl |
//+------------------------------------------------------------------+
#property copyright "Copyright 2012, Investeo.pl"
#property link      "http://www.investeo.pl"
#property version   "1.00"
                           
datetime allowed_until = D'2012.02.11 00:00'; 
                             
int password_status = -1;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   printf("This EA is valid until %s", TimeToString(allowed_until, TIME_DATE|TIME_MINUTES));
   datetime now = TimeCurrent();
   
   if (now < allowed_until) 
         Print("EA time limit verified, EA init time : " + TimeToString(now, TIME_DATE|TIME_MINUTES));
   
    
//---
   return(0);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
  if (TimeCurrent() < allowed_until) 
    {        
    }
   else Print("EA expired."); 
  }

L'unico inconveniente è che la soluzione dovrebbe essere compilata separatamente per ciascun licenziatario.


5. Licenze remote

Non sarebbe bello avere il controllo totale sul disabilitare la licenza o estendere il periodo di prova in base all'utente? Questo è fattibile semplicemente utilizzando la chiamata MQL5-RPC, la quale invierebbe una query con il nome dell'account e riceverà il valore per eseguire lo script in modalità di prova o disabilitarlo.

Consulta il codice seguente per un'implementazione di esempio:

from SimpleXMLRPCServer import SimpleXMLRPCServer
from SimpleXMLRPCServer import SimpleXMLRPCRequestHandler

class RequestHandler( SimpleXMLRPCRequestHandler ):
    rpc_path = ( '/RPC2', )
    

class RemoteLicenseExample(SimpleXMLRPCServer):

    def __init__(self):
        SimpleXMLRPCServer.__init__( self, ("192.168.2.103", 9099), requestHandler=RequestHandler, logRequests = False)
        
        self.register_introspection_functions()
        self.register_function( self.xmlrpc_isValid, "isValid" )        
        
        self.licenses = {} 
        
    def addLicense(self, ea_name, broker_name, account_number):
        if ea_name in self.licenses:
            self.licenses[ea_name].append({ 'broker_name': broker_name, 'account_number' : account_number })
        else:
            self.licenses[ea_name] = [ { 'broker_name': broker_name, 'account_number' : account_number } ]
             
    def listLicenses(self):
        print self.licenses
        
    def xmlrpc_isValid(self, ea_name, broker_name, account_number):
        isValidLicense = False
        
        ea_name = str(ea_name)
        broker_name = str(broker_name)
        
        print "Request for license", ea_name, broker_name, account_number
        
        try:
            account_number = int(account_number)
        except ValueError as error:
            return isValidLicense
    
        if ea_name in self.licenses:
            for license in self.licenses[ea_name]:
                if license['broker_name'] == broker_name and license['account_number'] == account_number:
                    isValidLicense = True
                    break
                
        print "License valid:", isValidLicense
        
        return isValidLicense
    
if __name__ == '__main__':
    server = RemoteLicenseExample()
    server.addLicense("RemoteProtectedEA", "MetaQuotes Software Corp.", 1024221)
    server.addLicense("RemoteProtectedEA", "MetaQuotes Software Corp.", 1024223)
    
    server.listLicenses()
    server.serve_forever()  

Questo è un semplice server XML-RPC implementato in Python con due licenze predefinite per MetaTrader 5. Le licenze sono impostate per l'expert advisor "RemoteProtectedEA" in esecuzione sul server demo MetaQuotes predefinito (access.metatrader5.com:443) con i numeri di conto 1024221 e 1024223. Una soluzione industriale probabilmente farebbe uso di un database di licenze in Postgresql o in qualsiasi altro database, ma l'esempio sopra è più che sufficiente per questo articolo poiché gestisce molto bene le licenze remote.

Se hai bisogno di una breve spiegazione su come installare Python, leggi "MQL5-RPC. Remote Procedure Calls from MQL5: Web Service Access and XML-RPC ATC Analyzer for Fun and Profit".

L'EA che utilizza la licenza remota deve semplicemente preparare una chiamata MQL5-RPC remota al metodo isValid(), il quale restituisce valori booleani veri o falsi a seconda della validità della licenza. L'esempio seguente mostra un esempio di EA basato sulla protezione dell'account:

//+------------------------------------------------------------------+
//|                                            RemoteProtectedEA.mq5 |
//|                                      Copyright 2012, Investeo.pl |
//|                                           http://www.investeo.pl |
//+------------------------------------------------------------------+
#property copyright "Copyright 2012, Investeo.pl"
#property link      "http://www.investeo.pl"
#property version   "1.00"

#include <MQL5-RPC.mqh>
#include <Arrays\ArrayObj.mqh>
#include <Arrays\ArrayInt.mqh>
#include <Arrays\ArrayString.mqh>
#include <Arrays\ArrayBool.mqh>

bool license_status=false;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
/* License proxy server */
   CXMLRPCServerProxy s("192.168.2.103:9099");
   if(s.isConnected()==true)
     {

      CXMLRPCResult *result;

/* Get account data */
      string broker= AccountInfoString(ACCOUNT_COMPANY);
      long account = AccountInfoInteger(ACCOUNT_LOGIN);

      printf("The name of the broker = %s",broker);
      printf("Account number =  %d",account);

/* Get remote license status */
      CArrayObj* params= new CArrayObj;
      CArrayString* ea = new CArrayString;
      CArrayString* br = new CArrayString;
      CArrayInt *ac=new CArrayInt;

      ea.Add("RemoteProtectedEA");
      br.Add(broker);
      ac.Add((int)account);

      params.Add(ea); params.Add(br); params.Add(ac);

      CXMLRPCQuery query("isValid",params);

      result=s.execute(query);

      CArrayObj *resultArray=result.getResults();
      if(resultArray!=NULL && resultArray.At(0).Type()==TYPE_BOOL)
        {
         CArrayBool *stats=resultArray.At(0);

         license_status=stats.At(0);
        }
      else license_status=false;

      if(license_status==true) printf("License valid.");
      else printf("License invalid.");

      delete params;
      delete result;
     }
   else Print("License server not connected.");
//---
   return(0);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---

  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   if(license_status==true)
     {
      // license valid
     }
  }
//+------------------------------------------------------------------+

Se esegui entrambi gli script, dovresti essere in grado di aggiungere una licenza remota per il tuo numero di account. La licenza remota può essere utilizzata anche per una licenza a tempo o una licenza con password che può essere disattivata da remoto dopo un periodo di prova. Ad esempio, daresti un EA a qualcuno per 10 giorni di prova: se non è soddisfatto del prodotto, disattivi la licenza o, nel caso sia soddisfatto, puoi attivare la licenza per un determinato periodo di tempo.


6. Crittografia sicura della licenza

Le idee presentate nell'ultimo paragrafo hanno utilizzato le chiamate di procedura remota per scambiare informazioni tra il server delle licenze e il client terminal. Questo potrebbe essere violato utilizzando pacchetti sniffer su una copia registrata di EA. Utilizzando l'applicazione sniffer, l'hacker è in grado di catturare tutti i pacchetti TCP inviati tra due macchine. Supereremo questo problema utilizzando la codifica base64 per inviare i dati dell'account e ricevere messaggi crittografati.

Per una persona esperta sarebbe anche possibile utilizzare PGP e/o inserire tutto il codice in una DLL per ulteriore protezione. Mi è venuta l'idea che il messaggio sarà in effetti un altro messaggio RPC (come in Russian Matryoshka doll) che verrà ulteriormente convertito in dati MQL5.

Il primo passaggio consiste nell'aggiungere il supporto per la codifica e decodifica base64 per MQL5-RPC. Fortunatamente, questo è già stato fatto per MetaTrader 4 su https://www.mql5.com/it/code/8098 da Renat, quindi avevo solo bisogno di convertirlo in MQL5.

//+------------------------------------------------------------------+
//|                                                       Base64.mq4 |
//|                      Copyright © 2006, MetaQuotes Software Corp. |
//|                                  MT5 version © 2012, Investeo.pl |
//|                                        https://www.metaquotes.net |
//+------------------------------------------------------------------+
#property copyright "Copyright © 2006, MetaQuotes Software Corp."
#property link      "https://www.metaquotes.net"
 
static uchar ExtBase64Encode[64]={ 'A','B','C','D','E','F','G','H','I','J','K','L','M',
                                 'N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
                                 'a','b','c','d','e','f','g','h','i','j','k','l','m',
                                 'n','o','p','q','r','s','t','u','v','w','x','y','z',
                                 '0','1','2','3','4','5','6','7','8','9','+','/'      };
                                 
static uchar ExtBase64Decode[256]={
                    -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
                    -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
                    -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  62,  -1,  -1,  -1,  63,
                    52,  53,  54,  55,  56,  57,  58,  59,  60,  61,  -1,  -1,  -1,  -2,  -1,  -1,
                    -1,   0,   1,   2,   3,   4,   5,   6,   7,   8,   9,  10,  11,  12,  13,  14,
                    15,  16,  17,  18,  19,  20,  21,  22,  23,  24,  25,  -1,  -1,  -1,  -1,  -1,
                    -1,  26,  27,  28,  29,  30,  31,  32,  33,  34,  35,  36,  37,  38,  39,  40,
                    41,  42,  43,  44,  45,  46,  47,  48,  49,  50,  51,  -1,  -1,  -1,  -1,  -1,
                    -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
                    -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
                    -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
                    -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
                    -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
                    -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
                    -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
                    -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1 };
                               

void Base64Encode(string in,string &out)
  {
   int i=0,pad=0,len=StringLen(in);

   while(i<len)
     {
      
      int b3,b2,b1=StringGetCharacter(in,i);
      i++;
      if(i>=len) { b2=0; b3=0; pad=2; }
      else
        {
         b2=StringGetCharacter(in,i);
         i++;
         if(i>=len) { b3=0; pad=1; }
         else       { b3=StringGetCharacter(in,i); i++; }
        }
      //----
      int c1=(b1 >> 2);
      int c2=(((b1 & 0x3) << 4) | (b2 >> 4));
      int c3=(((b2 & 0xf) << 2) | (b3 >> 6));
      int c4=(b3 & 0x3f);
 
      out=out+CharToString(ExtBase64Encode[c1]);
      out=out+CharToString(ExtBase64Encode[c2]);
      switch(pad)
        {
         case 0:
           out=out+CharToString(ExtBase64Encode[c3]);
           out=out+CharToString(ExtBase64Encode[c4]);
           break;
         case 1:
           out=out+CharToString(ExtBase64Encode[c3]);
           out=out+"=";
           break;
         case 2:
           out=out+"==";
           break;
        }
     }
//----
  }

void Base64Decode(string in,string &out)
  {
   int i=0,len=StringLen(in);
   int shift=0,accum=0;

   while(i<len)
     {
      int value=ExtBase64Decode[StringGetCharacter(in,i)];
      if(value<0 || value>63) break;
      
      accum<<=6;
      shift+=6;
      accum|=value;
      if(shift>=8)
        {
         shift-=8;
         value=accum >> shift;
         out=out+CharToString((uchar)(value & 0xFF));
        } 
      i++;
     }
//----
  }
//+------------------------------------------------------------------+

Per una descrizione dettagliata della codifica base64 potresti voler leggere l’articolo di Wikipedia .

Di seguito viene presentato un test di esempio dello script di codifica e decodifica MQL5 base64:

//+------------------------------------------------------------------+
//|                                                   Base64Test.mq5 |
//|                                      Copyright 2012, Investeo.pl |
//|                                           http://www.investeo.pl |
//+------------------------------------------------------------------+
#property copyright "Copyright 2012, Investeo.pl"
#property link      "http://www.investeo.pl"
#property version   "1.00"

#include <Base64.mqh>

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   string to_encode = "<test>Abrakadabra</test>";
   
   string encoded;
   string decoded;
   
   Base64Encode(to_encode, encoded);
   
   Print(encoded);
   
   Base64Decode(encoded, decoded);
   
   Print(decoded);
   
  }
//+------------------------------------------------------------------+

Lo script produce il seguente risultato.

DK      0       Base64Test (EURUSD,H1)  16:21:13        Original string: <test>Abrakadabra</test>
PO      0       Base64Test (EURUSD,H1)  16:21:13        Base64 encoded string: PHRlc3Q+QWJyYWthZGFicmE8L3Rlc3Q+
FM      0       Base64Test (EURUSD,H1)  16:21:13        Base64 decoded string: <test>Abrakadabra</test>

La validità della codifica può essere semplicemente verificata in Python in 4 righe di codice:

import base64

encoded = 'PHRlc3Q+QWJyYWthZGFicmE8L3Rlc3Q+'
decoded = base64.b64decode(encoded)
print decoded

<test>Abrakadabra</test>

Il secondo passaggio è crittografare il risultato XMLRPC in base64 (nota anche come tecnica Matryoshka):

import base64
from SimpleXMLRPCServer import SimpleXMLRPCServer
from SimpleXMLRPCServer import SimpleXMLRPCRequestHandler

class RequestHandler( SimpleXMLRPCRequestHandler ):
    rpc_path = ( '/RPC2', )
    

class RemoteLicenseExampleBase64(SimpleXMLRPCServer):

    def __init__(self):
        SimpleXMLRPCServer.__init__( self, ("192.168.2.103", 9099), requestHandler=RequestHandler, logRequests = False)
        
        self.register_introspection_functions()
        self.register_function( self.xmlrpc_isValid, "isValid" )        
        
        self.licenses = {} 
        
    def addLicense(self, ea_name, broker_name, account_number):
        if ea_name in self.licenses:
            self.licenses[ea_name].append({ 'broker_name': broker_name, 'account_number' : account_number })
        else:
            self.licenses[ea_name] = [ { 'broker_name': broker_name, 'account_number' : account_number } ]
             
    def listLicenses(self):
        print self.licenses
        
    def xmlrpc_isValid(self, ea_name, broker_name, account_number):
        isValidLicense = False
        
        ea_name = str(ea_name)
        broker_name = str(broker_name)
        
        print "Request for license", ea_name, broker_name, account_number
        
        try:
            account_number = int(account_number)
        except ValueError as error:
            return isValidLicense
    
        if ea_name in self.licenses:
            for license in self.licenses[ea_name]:
                if license['broker_name'] == broker_name and license['account_number'] == account_number:
                    isValidLicense = True
                    break
                
        print "License valid:", isValidLicense
        
        # additional xml encoded with base64
        xml_response = "<?xml version='1.0'?><methodResponse><params><param><value><boolean>%d</boolean></value></param></params></methodResponse>"
        
        retval = xml_response % int(isValidLicense)
        
        return base64.b64encode(retval)
    
if __name__ == '__main__':
    server = RemoteLicenseExampleBase64()
    server.addLicense("RemoteProtectedEA", "MetaQuotes Software Corp.", 1024221)
    server.addLicense("RemoteProtectedEA", "MetaQuotes Software Corp.", 1024223)
    
    server.listLicenses()
    server.serve_forever()        

Dopo che la licenza è stata crittografata, possiamo utilizzare il metodo MQL5-RPC per riconvertire il messaggio decrittografato in dati MQL5:

//+------------------------------------------------------------------+
//|                                      RemoteProtectedEABase64.mq5 |
//|                                      Copyright 2012, Investeo.pl |
//|                                           http://www.investeo.pl |
//+------------------------------------------------------------------+
#property copyright "Copyright 2012, Investeo.pl"
#property link      "http://www.investeo.pl"
#property version   "1.00"

#include <MQL5-RPC.mqh>
#include <Arrays\ArrayObj.mqh>
#include <Arrays\ArrayInt.mqh>
#include <Arrays\ArrayString.mqh>
#include <Arrays\ArrayBool.mqh>
#include <Base64.mqh>

bool license_status=false;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
/* License proxy server */
   CXMLRPCServerProxy s("192.168.2.103:9099");

   if(s.isConnected()==true)
     {
      CXMLRPCResult *result;

/* Get account data */
      string broker= AccountInfoString(ACCOUNT_COMPANY);
      long account = AccountInfoInteger(ACCOUNT_LOGIN);

      printf("The name of the broker = %s",broker);
      printf("Account number =  %d",account);

/* Get remote license status */
      CArrayObj* params= new CArrayObj;
      CArrayString* ea = new CArrayString;
      CArrayString* br = new CArrayString;
      CArrayInt *ac=new CArrayInt;

      ea.Add("RemoteProtectedEA");
      br.Add(broker);
      ac.Add((int)account);

      params.Add(ea); params.Add(br); params.Add(ac);

      CXMLRPCQuery query("isValid",params);

      result=s.execute(query);

      CArrayObj *resultArray=result.getResults();
      if(resultArray!=NULL && resultArray.At(0).Type()==TYPE_STRING)
        {
         CArrayString *stats=resultArray.At(0);

         string license_encoded=stats.At(0);

         printf("encoded license: %s",license_encoded);

         string license_decoded;

         Base64Decode(license_encoded,license_decoded);

         printf("decoded license: %s",license_decoded);

         CXMLRPCResult license(license_decoded);
         resultArray=license.getResults();

         CArrayBool *bstats=resultArray.At(0);

         license_status=bstats.At(0);
        }
      else license_status=false;

      if(license_status==true) printf("License valid.");
      else printf("License invalid.");

      delete params;
      delete result;
     }
   else Print("License server not connected.");

//---
   return(0);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---

  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   if(license_status==true)
     {
      // license valid
     }
  }
//+------------------------------------------------------------------+

Il risultato dell'esecuzione dello script, a condizione che il server RemoteLicenseExampleBase64 sia in esecuzione, è il seguente:

KI  0  RemoteProtectedEABase64 (EURUSD,H1) 19:47:57  The name of the broker = MetaQuotes Software Corp.
GP  0  RemoteProtectedEABase64 (EURUSD,H1) 19:47:57  Account number =  1024223
EM  0  RemoteProtectedEABase64 (EURUSD,H1) 19:47:57  <?xml version='1.0'?><methodResponse><params><param><value><string>PD94bWwgdmVyc2lvbj0nMS4wJz8+PG1ldGhvZFJlc3BvbnNlPjxwYXJhbXM+PHBhcmFtPjx2YWx1ZT48Ym9vbGVhbj4xPC9ib29sZWFuPjwvdmFsdWU+PC9wYXJhbT48L3BhcmFtcz48L21ldGhvZFJlc3BvbnNlPg==</string></value></param></params></methodResponse>
DG  0  RemoteProtectedEABase64 (EURUSD,H1) 19:47:57  encoded license: PD94bWwgdmVyc2lvbj0nMS4wJz8+PG1ldGhvZFJlc3BvbnNlPjxwYXJhbXM+PHBhcmFtPjx2YWx1ZT48Ym9vbGVhbj4xPC9ib29sZWFuPjwvdmFsdWU+PC9wYXJhbT48L3BhcmFtcz48L21ldGhvZFJlc3BvbnNlPg==
FL  0  RemoteProtectedEABase64 (EURUSD,H1) 19:47:57  decoded license: <?xml version='1.0'?><methodResponse><params><param><value><boolean>1</boolean></value></param></params></methodResponse>
QL  0  RemoteProtectedEABase64 (EURUSD,H1) 19:47:57  License valid.

Come puoi vedere, il payload XML-RPC contiene una stringa che è in effetti un messaggio XML-RPC codificato da base64. Questo messaggio codificato in base64 viene decodificato in una stringa XML e successivamente decodificato in dati MQL5.


7. Linee guida anti-decompilazione avanzate

Non appena il codice MQL5 viene decompilato, anche le protezioni più sicure che sono esposte a un esperto di reverse-engineering saranno vulnerabili per essere violate. Dopo aver cercato su Google, ho trovato un sito che offre il decompilatore MQL5, ma ho semplicemente il sospetto che questo sia un falso creato per portare via soldi a persone ingenue che vorrebbero rubare il codice di qualcuno. Comunque non l'ho provato e potrei sbagliarmi. Anche se tale soluzione esistesse, dovresti essere in grado di creare una protezione più forte inviando parametri di input EA/indicatore crittografati o passando gli indici degli oggetti.

Sarà molto difficile per un hacker ottenere i parametri di input corretti per l'EA protetto o vedere i valori di input corretti dell'indicatore protetto che a sua volta lo renderà inutile. È anche possibile inviare parametri corretti se l'ID account corrisponde o inviare parametri falsi non crittografati se l'ID account non è valido. Per quella soluzione si potrebbe voler usare PGP (Pretty Good privacy). Anche se il codice viene decompilato, i dati verranno inviati crittografati con la chiave PGP privata e i parametri EA verranno decrittografati solo quando l'ID account e la chiave PGP corrispondono.


Conclusione

In questo articolo ho presentato alcuni modi per proteggere il codice MQL5. Ho anche introdotto il concetto di licenza remota tramite chiamata MQL5-RPC e aggiunto il supporto per la codifica base64. Spero che l'articolo serva come base per ulteriori idee su come proteggere il codice MQL5. Tutto il codice sorgente è allegato all'articolo.


Tradotto dall’inglese da MetaQuotes Ltd.
Articolo originale: https://www.mql5.com/en/articles/359

Promuovi i tuoi progetti di sviluppo utilizzando le librerie EX5 Promuovi i tuoi progetti di sviluppo utilizzando le librerie EX5
Nascondere i dettagli di implementazione di classi/funzioni in un file .ex5 ti consentirà di condividere i tuoi algoritmi di know-how con altri sviluppatori, impostare progetti comuni e promuoverli nel Web. E mentre il team di MetaQuotes non risparmia alcuno sforzo per realizzare la possibilità di ereditarietà diretta delle classi della libreria ex5, noi lo implementeremo proprio ora.
Trademinator 3: Ascesa delle macchine di trading Trademinator 3: Ascesa delle macchine di trading
Nell'articolo "Dr. Tradelove ..." abbiamo creato un Expert Advisor che ottimizza in modo indipendente i parametri di un sistema di trading preselezionato. Inoltre, abbiamo deciso di creare un Expert Advisor in grado non solo di ottimizzare i parametri di un sistema di trading sottostante l'EA, ma anche di selezionare il migliore dei diversi sistemi di trading. Vediamo cosa può venirne fuori...
L'ultima crociata L'ultima crociata
Dai un'occhiata al tuo terminale di trading. Quali mezzi di presentazione del prezzo vedi? Bar, candele, linee. Inseguiamo il tempo e i prezzi mentre guadagniamo solo dai prezzi. Dobbiamo prestare attenzione solo ai prezzi quando analizziamo il mercato? Questo articolo propone un algoritmo e uno script per la creazione di grafici a punti e cifre ("naught and cross"). Vengono presi in considerazione vari modelli di prezzo il cui uso pratico è delineato nelle raccomandazioni fornite.
Creazione di un Expert Advisor mediante Expert Advisor Visual Wizard Creazione di un Expert Advisor mediante Expert Advisor Visual Wizard
Expert Advisor Visual Wizard per MetaTrader 5 fornisce un ambiente grafico altamente intuitivo con un set completo di blocchi di trading predefiniti, i quali consentono di progettare un Expert Advisor in pochi minuti. L'approccio click, drag and drop di Expert Advisor Visual Wizard ti consente di creare rappresentazioni visive delle strategie e dei segnali di trading forex come faresti con carta e matita. Questi diagrammi di trading vengono analizzati automaticamente dal generatore di codice MQL5 di Molanis che li trasforma in Expert Advisor pronti all'uso. L'ambiente grafico interattivo semplifica il processo di progettazione ed elimina la necessità di scrivere codice MQL5.