English Русский 中文 Español Deutsch 日本語 Português 한국어 Italiano Türkçe
Sécurisation du code MQL5 : Protection par mot de passe, générateurs de clés, délais, licences à distance et techniques avancées de cryptage des clés de licence EA

Sécurisation du code MQL5 : Protection par mot de passe, générateurs de clés, délais, licences à distance et techniques avancées de cryptage des clés de licence EA

MetaTrader 5Exemples | 12 janvier 2022, 14:53
470 0
investeo
investeo

Introduction

La plupart des développeurs doit de sécuriser leur code. Cet article présentera différentes manières de protéger le logiciel MQL5. Tous les exemples de l'article feront référence aux Expert Advisors mais les mêmes règles peuvent être appliquées aux Scripts et Indicateurs. L'article commence par une simple protection par mot de passe et suit avec des générateurs de clés, une licence pour un compte de courtier donné et une protection dans délai. Ensuite, il introduit un concept de serveur de licences distant. Mon dernier article sur le cadre de MQL5-RPCa décrit les Appels de Procédure à Distance depuis MetaTrader 5 vers le serveur XML-RPC.

Je vais utiliser cette solution pour fournir un exemple de licence à distance. Je décrirai également comment améliorer cette solution avec le cryptage base64 et fournirai des conseils pour la prise en charge de PGP afin de créer une protection ultra-sécurisée pour les Expert Advisors et indicateurs MQL5. Je suis conscient que MetaQuotes Software Corp. fournit quelques options pour accorder directement une licence de code depuis MQL5.com section Marché. C'est vraiment bon pour tous les développeurs et n'invalidera pas les idées présentées dans cet article. Les deux solutions utilisées ensemble ne peuvent que renforcer la protection et la sécuriser contre le vol de logiciels.


1. Mot de passe de protection

Commençons par quelque chose de simple. La première solution la plus utilisée pour une protection des logiciels informatiques est la protection par mot de passe ou clé de licence. Lors de la première exécution après l'installation, l'utilisateur est prié grâce à une boîte de dialogue d’insérer un mot de passe lié à une copie du logiciel (comme la clé de série Microsoft Windows ou Microsoft Office) et si le mot de passe entré correspond, l'utilisateur est autorisé à utiliser une seule copie enregistrée d’un logiciel. Nous pouvons utiliser une variable d'entrée ou une zone de texte directe pour entrer le code. Un exemple de code stub est présenté ci-dessous.

Le code ci-dessus initialise un CChartObjectEdit champ qui est utilisé pour insérer un mot de passe. Il existe un tableau prédéfini de mots de passe autorisés qui correspond à un mot de passe inséré par un utilisateur. Le mot de passe est vérifié dans la méthodeOnChartEvent()après réception de l’évènement 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]); 
      }
  }
//+------------------------------------------------------------------+

Cette méthode est simple mais il est possible pour quelqu'un de publier le mot de passe sur un site Web avec des numéros de série piratés. L'auteur d'EA ne peut rien faire jusqu'à ce qu'un nouvel Expert Advisor soit libéré et que le mot de passe volé soit blacklisté.


2. Générateur de clé

Les générateurs de clés sont un mécanisme qui permet d'utiliser un ensemble de mots de passe basé sur des règles prédéfinies. Je vais donner un aperçu en fournissant un stub pour un générateur de clé ci-dessous. Dans l'exemple présenté ci-dessous, la clé doit être constituée de trois chiffres séparés par deux tirets. Par conséquent, le format autorisé pour un mot de passe est XXXXX-XXXXX-XXXXX.

Le premier nombre doit être divisible par 3, le deuxième nombre doit être divisible par 4 et le troisième nombre doit être divisible par 5. Par conséquent, les mots de passe autorisés peuvent être 3-4-5, 18000-20000-20000 ou le plus compliqué 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]); 
      }
  }
//+------------------------------------------------------------------+

Bien entendu, le nombre de chiffres dans un nombre peut être fixé à une valeur donnée et les calculs peuvent être plus compliqués. On peut également ajouter une variable qui n'est valide qu'avec un matériel donné en ajoutant le numéro de série du disque dur ou l'ID CPU au calcul. Dans ce cas, la personne chargée d'exécuter l'EA devrait exécuter un générateur supplémentaire calculé à base du matériel.

La sortie serait une entrée dans un keygen et le mot de passe généré ne serait valide que pour un matériel donné. Cela a une limitation de quelqu'un qui change de matériel informatique ou qui utilise VPS pour exécuter EA, mais cela pourrait être résolu en donnant deux ou trois mots de passe valides. C’est également la cas den la section Marché du site web de MQL5.


3. Licence de Compte Unique

Étant donné que le numéro du Compte du terminal d'un courtier donné est unique, il peut être utilisé pour permettre l'utilisation de l'EA sur un ou un ensemble de numéros de compte. Dans un tel cas,il suffit d’utiliser les méthodesAccountInfoString(ACCOUNT_COMPANY) et AccountInfoInteger(ACCOUNT_LOGIN)pour récupérer les données du compte et les comparer avec les valeurs autorisées et pre-compilées.

//+------------------------------------------------------------------+
//|                                           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
  } 
  }

C'est une protection simple mais assez puissante. L'inconvénient est qu'il est nécessaire de recompiler l'EA pour chaque nouveau numéro de compte ajouté à la base de données des comptes.


4. Protection à Durée limitée

La protection à durée limitée est utile lorsque la licence est accordée à titre provisoire, par exemple en utilisant la version d'essai du logiciel ou lorsque la licence est accordée sur une base mensuelle ou annuelle. Comme il est évident que cela peut être appliqué pour les Expert Advisors et les Indicateurs.

La première idée est de vérifier l'heure du serveur et, sur cette base, de permettre à l'utilisateur d'utiliser l'indicateur ou l’Expert Advisor dans un délai donné. Après son expiration, le concédant est en mesure de désactiver partiellement ou totalement sa fonctionnalité pour le titulaire.

//+------------------------------------------------------------------+
//|                                         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."); 
  }

Le seul inconvénient est que la solution devrait être compilée séparément pour chaque titulaire.


5. Licences à distance

Ne serait-il pas bon d'avoir un contrôle total sur la désactivation de la licence ou l'extension de la période d'essai par utilisateur ? Cela peut simplement être fait en utilisant l'appel MQL5-RPC qui enverrait une requête avec le nom du compte et recevrait la valeur s'il faut exécuter le script en mode d'essai ou le désactiver.

Veuillez consulter le code ci-dessous pour un exemple d'implémentation :

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

Ceci est un simple serveur XML-PRC implémenté dans Python avec deux licences prédéfinis pourMetaTrader 5. Les licences sont définies pour le conseiller expert "RemoteProtectedEA" exécuté sur le serveur de démonstration MetaQuotes par défaut (access.metatrader5.com:443) avec les numéros de compte 1024221 et 1024223. Une solution industrielle utiliserait probablement une base de données de licences dans Postgresql ou toute autre base de données, mais l'exemple ci-dessus est plus que suffisant pour cet article car il gère très bien les licences distantes.

Si vous avez besoin d’une brève explication sur la façon d’installer Python veuillez lire "MQL5-RPC. Appels de procédure à distance depuis MQL5 : Accès aux services Web et analyseur ATC XML-RPC pour le plaisir et le profit".

L'EA qui utilise la licence distante doit simplement préparer un appel MQL5-RPC distant à la méthode isValid() qui renvoie des valeurs booléennes vraies ou fausses en fonction de la validité de la licence. L'exemple ci-dessous indique un échantillon d’EA basé sur la protection de compte :

//+------------------------------------------------------------------+
//|                                            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
     }
  }
//+------------------------------------------------------------------+

Si vous exécutez les deux scripts, vous devriez pouvoir ajouter une licence à distance pour votre numéro de compte. La licence à distance peut également être utilisée pour une licence limitée dans le temps ou une licence avec mot de passe qui peut être désactivée à distance après une période d'essai. Par exemple, vous accorderiez un EA à quelqu'un pour 10 jours de test, s'il n'est pas satisfait du produit, vous désactivez la licence ou dans le cas où il est satisfait, vous pouvez activer la licence pour une période donnée.


6. Cryptage de licence sécurisé

Les idées présentées dans le dernier paragraphe utilisaient des appels de procédure à distance pour échanger des informations entre le serveur de licences et le terminal client. Cela pourrait éventuellement être piraté en utilisant des packages de renifleur sur une copie enregistrée de l'EA. En utilisant l'application renifleur, le pirate est capable de capturer tous les paquets TCP envoyés entre deux machines. Nous surmonterons ce problème en utilisant le cryptage base64 pour envoyer des données de compte et recevoir un message crypté.

Pour une personne compétente, il serait également possible d'utiliser PGP et/ou de mettre tout le code dans une DLL pour une protection supplémentaire. Une idée m’était venue que le message sera en réalité un autre message RPC (comme enpoupée russe Matryoshka) qui sera plus tard convertie dans les données MQL5.

La première étape consiste à ajouter à la base64 et la prise en charge en matière cryptage et décryptage pour MQL5-RPC. Heureusement que cela était déjà accompli pourMetaTrader 4 sur https://www.mql5.com/fr/code/8098par Renat et par conséquent, je devais le convertir à 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++;
     }
//----
  }
//+------------------------------------------------------------------+

Pour une description détaillée du cryptage de base64, vous souhaiterez peut-être consulter unarticle Wikipedia

Un échantillon de test du script du MQL5 cryptage et décryptage de base64 est présenté ci-dessous.

//+------------------------------------------------------------------+
//|                                                   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);
   
  }
//+------------------------------------------------------------------+

Le script produit le résultat suivant.

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é du cryptage peut être simplement vérifiée en Python en 4 lignes de code :

import base64

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

<test>Abrakadabra</test>

La seconde étape consiste à crypter le résultat XMLRPC en base64 (Technique aka 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()        

Une fois la licence cryptée, nous pouvons utiliser la méthode MQL5-RPC pour reconvertir le message décrypté en données 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
     }
  }
//+------------------------------------------------------------------+

Le résultat de l'exécution du script à condition que le serveur RemoteLicenseExampleBase64 soit en cours d'exécution est le suivant :

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.

Comme vous pouvez le consulter, la charge utile XML-RPC contient une chaîne qui est en fait un message XML-RPC crypté en base64. Ce message crypté en base64 est décodé en chaîne XML et ensuite décrypté en données MQL5.


7. Directives avancées d'anti-décompilation

Dès que le code MQL5 est décompilé, même les protections les plus sécurisées exposées à un reverse-engineering qualifié seront vulnérables au crack. Après quelques recherches sur Google, j'ai trouvé un site Web qui propose un dé-compilateur MQL5, mais je soupçonne simplement qu'il s'agit d'un contrefait conçu pour soutirer de l'argent à des personnes naïves qui voudraient voler le code de quelqu'un. De toute façon, je n'ai pas essayé et je me trompe peut-être. Même si une telle solution existait, vous devriez pouvoir renforcer la protection en envoyant des paramètres d'entrée EA/indicateurs cryptés ou des index d'objets en passant.

Il sera très difficile pour un pirate informatique d'obtenir les paramètres d'entrée corrects pour l'EA protégé ou de voir les valeurs d'entrée correctes de l'indicateur protégé, ce qui le rendra à son tour inutile. Il est également possible d'envoyer des paramètres corrects si l'ID de compte correspond ou d'envoyer de faux paramètres non cryptés si l'ID de compte n'est pas valide. Pour cette solution,nous souhaiterons peut-être utiliser PGP (Pretty Good privacy). Même si le code est décompilé, les données seront envoyées cryptées avec une clé PGP privée et les paramètres EA ne seront décryptés que lorsque l'ID de compte et la clé PGP correspondent.


Conclusion

Dans cet article, j'ai présenté quelques façons de protéger le code MQL5. J'ai également introduit le concept de licence à distance via l'appel MQL5-RPC et ajouté la prise en charge du cryptage base64. J'espère que l'article servira de base à d'autres idées sur la façon de sécuriser le code MQL5. Tout le code source est joint à l'article.


Traduit de l’anglais par MetaQuotes Ltd.
Article original : https://www.mql5.com/en/articles/359

Faites la Promotion de vos Projets de Développement à l'Aide des Bibliothèques EX5 Faites la Promotion de vos Projets de Développement à l'Aide des Bibliothèques EX5
Cacher les détails d'implémentation des classes/fonctions dans un fichier .ex5 vous permettra de partager vos algorithmes de savoir-faire avec d'autres développeurs, de mettre en place des projets communs et de les promouvoir sur le Web. Et tandis que l'équipe MetaQuotes ne ménage aucun effort pour amener la possibilité d'héritage direct des classes de la bibliothèque ex5, nous allons l'implémenter dès maintenant.
Trademinator 3 : Montée des Machines de Trading Trademinator 3 : Montée des Machines de Trading
Dans l’article « Dr. Tradelove... » nous avons créé un Expert Advisor, qui optimise indépendamment les paramètres d’un système de trading présélectionné. De plus, nous avons décidé de créer un Expert Advisor qui peut non seulement optimiser les paramètres d’un système de trading sous-jacent à l’EA, mais également sélectionner le meilleur des systèmes de trading. Voyons ce qui peut en découler...
La Dernière Croisade La Dernière Croisade
Jetez un œil à votre terminal de trading. Quels moyens de présentation des prix pouvez-vous voir? Barres, chandeliers, lignes. Nous courons après le temps et les prix alors que nous ne profitons que des prix. Doit-on seulement prêter attention aux prix lors de l'analyse du marché ? Cet article propose un algorithme et un script pour la représentation graphique de points et de chiffres («zéros et croix  ») Une attention particulière est accordée à divers modèles de prix dont l'utilisation pratique est décrite dans les recommandations fournies.
Création d'Expert Advisors à l'aide de l'Assistant visuel d'Expert Advisor Création d'Expert Advisors à l'aide de l'Assistant visuel d'Expert Advisor
Expert Advisor Visual Wizard pour MetaTrader 5 fournit un environnement graphique très intuitif avec un ensemble complet de blocs de trading prédéfinis qui vous permettent de concevoir des Expert Advisors en quelques minutes. L'approche cliquer, glisser-déposer d'Expert Advisor Visual Wizard vous permet de créer des représentations visuelles des stratégies et des signaux de trading forex comme vous le feriez avec un crayon et du papier. Ces diagrammes de trading sont automatiquement analysés par le générateur de code MQL5 de Molanis qui les transforme en Expert Advisors prêts à l'emploi. L'environnement graphique interactif simplifie le processus de conception et élimine le besoin d'écrire du code MQL5.