English Русский 中文 Español 日本語 Português 한국어 Français Italiano Türkçe
Schutz von MQL5-Programmen: Passwörter, Schlüssel, Zeitbegrenzung, Berechtigungsfernabfrage

Schutz von MQL5-Programmen: Passwörter, Schlüssel, Zeitbegrenzung, Berechtigungsfernabfrage

MetaTrader 5Beispiele | 20 April 2016, 14:39
2 231 10
investeo
investeo

Einleitung

Die Mehrzahl der Entwickler benötigt Schutz für ihren Programmcode. In diesem Beitrag werden einige Möglichkeiten für den Schutz in MQL5 geschriebener Programme vorgestellt. Alle in diesem Beitrag vorgestellten Beispiele beziehen sich auf automatische Handelssysteme (im Weiteren Expert-Systeme), wobei dieselben Verfahren jedoch auch bei Skripten und Indikatoren Anwendung finden können. Wir beginnen mit einem einfachen Passwortschutz, kommen danach zur Erzeugung von Schlüsseln, der Einrichtung von Konten sowie der zeitlichen Zugriffsbeschränkung. Dann wird noch der Begriff des „externen Berechtigungsservers“ (Remote License Server) eingeführt. Die Umsetzung von Fernaufrufen beliebiger XML-RPC-Server aus MetaTrader 5 wurde in dem Artikel MQL5-RPC - Fernaufruf von Vorgängen aus MQL5: Zugriff auf Webdienste und Analyse der Daten der Automated Trading Championship 2011 beschrieben.

Auf diese Lösung greife ich zur Umsetzung der Berechtigungsprüfung aus der Ferne zurück. Zudem werden eine Möglichkeit zur Erweiterung dieser Lösung mithilfe der base64-Verschlüsselung vorgestellt und einige Gesichtspunkte der PGP-Verschlüsselung für einen höchst zuverlässigen Schutz von in MQL5 geschriebenen Expert-Systemen und Indikatoren betrachtet. Mir ist bekannt, dass die MetaQuotes Software Corp. auf der MQL5-Webseite in der Rubrik Markt einige Varianten zur Lizenzierung von Programmen anbietet. Das ist für alle Entwickler wirklich gut, beeinträchtigt jedoch nicht die Aktualität der in diesem Beitrag angestellten Überlegungen. Lediglich die gemeinsame Anwendung beider Lösungen stärkt den Schutz und gewährleistet eine zuverlässige Sicherung der Software gegen Diebstahl.


1. Passwortschutz

Fangen wir mit dem Elementarsten an. Am häufigsten erfolgt der Schutz von Software mithilfe eines Passwortes bzw. eines Lizenz- oder Berechtigungsschlüssels. Beim ersten Programmaufruf nach der Installation wird der Benutzer zur Eingabe des Passwortes für die betreffende Programminstanz (wie die Seriennummer bei Microsoft Windows oder Microsoft Office) aufgefordert. Nach Eingabe des richtigen Passworts oder Schlüssels ist der Benutzer berechtigt, die angemeldete Programminstanz im Rahmen der Nutzungsbedingungen unbeschränkt zu nutzen. Zur Eingabe des Passwortes können sowohl ein Eingangsparameter als auch ein eingabefähiges Textfeld verwendet werden. Ein Beispiel für einen solchen „Platzhalter“ folgt etwas weiter unten.

In dem Code wird das Feld CChartObjectEdit bereitgestellt, das zur Eingabe des Passwortes genutzt wird. Das von dem Benutzer eingegebene Passwort wird mit den Werten eines vorgegebenen Datenfeldes mit allen zulässigen Passwörtern abgeglichen. Die Passwortprüfung erfolgt mithilfe der Methode OnChartEvent() nach Eintreten des Ereignisses 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]); 
      }
  }
//+------------------------------------------------------------------+

Diese Methode ist simpel, sie ist jedoch leicht auszuhebeln, wenn zum Beispiel jemand die entwendeten Schlüssel oder Passwörter im Web veröffentlicht. Als Entwickler eines Expert-Systems kann man dann vor der Veröffentlichung einer Neufassung des betroffenen Programms bzw., bis die entwendeten Passwörter auf die „schwarze Liste“ gesetzt wurden, nichts tun.



2. Schlüsselgenerator

Bei Schlüsselgeneratoren (Key Generators) handelt es sich um einen Mechanismus zur Verwendung von nach vorgegebenen Regeln angelegten Passwörtern. Wir betrachten ihre Funktionsweise am Beispiel eines Platzhaltercodes. In diesem unten abgebildeten Beispiel muss der Schlüssel aus drei jeweils (also zwei Mal) durch das Zeichen „-“ getrennte Zahlen bestehen, das zulässige Format für das Passwort hat demnach folgendes Aussehen: XXXXX-XXXXX-XXXXX.

Die erste Zahl muss glatt durch 3 teilbar sein, die zweite durch 4 und die dritte durch 5. Zulässige Passwörter wären somit 3-4-5, 18000-20000-20000 oder etwas komplizierter: 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]); 
      }
  }
//+------------------------------------------------------------------+

Die Anzahl der Ziffern je Zahl kann natürlich beliebig sein, und auch die erforderlichen Rechenoperationen können komplexer angelegt werden. Es kann auch eine mit Hardware-Merkmalen (z. B. der Seriennummer der Festplatte oder Angaben zum Prozessor) verbundene Variable ergänzt werden. Dazu müsste beim Aufrufen des Expert-Systems zusätzlich ein weiterer Generator zum Abrufen der Hardware-Merkmale gestartet werden.

Das Ergebnis kann als Eingangsparameter für den Hauptgenerator dienen, wobei das erzeugte Passwort ausschließlich für die entsprechende Hardware gültig wäre. Das schränkt all jene ein, die ihre Rechnerkonfiguration ändern oder für den Betrieb ihres Expert-Systems VPS nutzen, allerdings ist leicht Abhilfe zu schaffen, indem zwei oder drei gültige Passwörter ausgegeben werden. Diese Vorgehensweise wird auch in der Rubrik Markt der genannten MQL5-Webseite gepflegt.




3. Kontobindung

Den Umstand, dass die Nummern der einzelnen Handelskonten bei dem jeweiligen Maklerunternehmen jeweils nur einmal vergeben werden, können wir nutzen, um den Einsatz des Expert-Systems auf einem oder mehreren Konten zuzulassen. In einem solchen Fall reicht es zum Abfragen der Kontoangaben aus, die Funktionen AccountInfoString(ACCOUNT_COMPANY) und AccountInfoInteger(ACCOUNT_LOGIN) auszuführen und deren Ergebnisse mit den Vorgabewerten der zugelassenen Konten abzugleichen:

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

Ein einfacher, aber absolut leistungsfähiger Schutzmechanismus. Sein einziger Nachteil besteht in der Notwendigkeit, das Expert-System bei jedem dem Kontenverzeichnis neu hinzugefügten Konto erneut zusammenstellen zu müssen.


4. Zeitliche Nutzungsbegrenzung

Die zeitliche Nutzungsbegrenzung (Time-limit protection) eignet sich für befristete Nutzungsberechtigungen wie „Trial“-Programmfassungen zum Kennenlernen oder bei Monats- oder Jahresabonnements. Ein solches Verfahren ist auch für Expert-Systeme und Indikatoren denkbar.

Der erste Gedanke ist die Abfrage der Serverzeit, um dem Benutzer auf deren Grundlage für einen bestimmten Zeitraum die Arbeit mit dem betreffenden Indikator oder Expert-System zu ermöglichen. Nach Ablauf der Zeit kann die Funktionalität des jeweiligen Programms dann ganz oder teilweise gesperrt werden.

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

Das einzige Manko dieses Verfahrens ist, dass das Expert-System für jeden einzelnen Benutzer zusammengestellt werden muss.


5. Berechtigungsprüfung aus der Ferne

Es wäre nicht übel, die volle Kontrolle über die Berechtigungsvergabe zu haben, sei es, um Berechtigungen zu widerrufen oder die Kennenlernphase für bestimmte Nutzer zu verlängern. Das lässt sich mithilfe von MQL5-RPC-Aufrufen recht einfach bewerkstelligen. Diese senden unter Angabe der entsprechenden Kontonummer eine Anfrage an den Server und erhalten als Antwort einen Wert, der die Ausführung des Skripts im Kennenlernmodus („Trial“) ermöglicht bzw. sie verhindert.

Ein Beispiel für die Umsetzung:

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

Dieser einfache in Python programmierte XML-RPC-Server beinhaltet zwei voreingestellte Nutzungsberechtigungen für MetaTrader 5. Die Berechtigungen gelten für das auf den Konten mit den Nummern 1024221 und 1024223 des Vorführservers von MetaQuotes (access.metatrader5.com:443) laufende Expert-System namens RemoteProtectedEA. Bei der gewerblichen Nutzung erfolgt die Speicherung der Berechtigungen besser in der Datenbank Postgresql oder einer anderen Datenbank. Das oben angeführte Beispiel veranschaulicht dennoch recht gut die Funktion eines Systems zur Verwaltung der Nutzungsberechtigungen aus der Ferne.

Hilfe bei der Einrichtung der Programmiersprache Python bietet der Artikel: MQL5-RPC. Fernaufruf von Vorgängen aus MQL5: Zugriff auf Webdienste und Analyse der Daten der Automated Trading Championship 2011.

In einem Expert-System, das die Fernabfrage von Nutzungsberechtigungen verwendet, müssen die MQL5-RPC-Fernaufrufe für die Methode isValid() angelegt werden, da diese je nach Gültigkeit der Berechtigung die Booleschen Werte „true“ oder „false“ ausgibt. Das nachfolgende Beispiel zeigt ein auf der Anbindung an ein Konto beruhendes Expert-System:

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

Nach Fertigstellung beider Skripte kann die Fernverwaltung der Nutzungsberechtigungen für Ihre Konten auf vergleichbare Weise eingesetzt werden. Dieses Verfahren kann zur Erteilung sowohl zeitlich begrenzter als auch passwortgeschützter Nutzungsberechtigungen verwendet werden, die nach Ablauf der Kennenlernphase aufgehoben werden können. Wenn Sie zum Beispiel jemandem ein Expert-System zu Testzwecken für 10 Tage zugänglich machen, können Sie die Nutzungsberechtigung danach widerrufen, wenn der Nutzer nicht zufrieden war, oder sie für einen beliebigen Zeitraum verlängern.


6. Sichere Verschlüsselung der Nutzungsberechtigungen

Bei dem in dem vorstehenden Abschnitt vorgestellten Verfahren wurden für den Datenaustausch zwischen dem Server mit den Nutzungsberechtigungen und dem Ausgabegerät des Benutzers Fernaufrufe von Vorgängen (Remote Procedure Calls) verwendet. Dieses Verfahren leidet unter der Möglichkeit des Datendiebstahls durch das Abfangen von Datenpaketen einer berechtigten Instanz des Expert-Systems. Mithilfe von Spähprogrammen können Datendiebe alle TCP-Pakete abfangen, die zwischen zwei Rechnern ausgetauscht werden. Zur Lösung dieses Problems setzen wir auf die base64-Verschlüsselung für übertragene Kontodaten und eingehende verschlüsselte Meldungen.

Erfahrene Anwender könnten sich auch der PGP-Verschlüsselung bedienen und/oder den gesamten Code zur erweiterten Sicherheit in einer DLL ablegen. De facto handelt es sich dabei lediglich um eine weitere RPC-Meldung (wie in der russischen Matrjoschka), die anschließend in MQL5-Daten übersetzt wird.

Der erste Schritt ist die Ergänzung der base64-Verschlüsselung um die Unterstützung der Ver-/Entschlüsselung für MQL5-RPC. Glücklicherweise besteht mit Renats https://www.mql5.com/de/code/8098 bereits eine Lösung für MetaTrader 4, die wir lediglich auf MQL5 übertragen müssen.

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

Eine ausführlichere Darstellung der base64-Verschlüsselung bietet Wikipedia.

Ein Musterskript für die Ver- und Entschlüsselung in base64 folgt:

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

So sieht das Ergebnis der Skriptausführung aus:

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>

Die Richtigkeit der Verschlüsselung lässt sich mithilfe von vier in Python geschriebenen Zeilen leicht überprüfen:

import base64

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

<test>Abrakadabra</test>

Der zweite Schritt besteht in der abermaligen Verschlüsselung des Ergebnisses der Arbeit des XML-RPC in base64 (nach dem Matrjoschka-Prinzip):

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

Nachdem die Berechtigung verschlüsselt wurde, verwenden wir das MQL5-RPC-Verfahren zur Rückübersetzung der verschlüsselten Meldung in MQL5-Daten.

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

Das Ergebnis der Skriptausführung zeigt, dass der Server RemoteLicenseExampleBase64 gestartet wurde:

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.

Unübersehbar beinhaltet der Hauptteil der XML-RPC-Meldung eine Zeile, die im Grunde eine in base64 verschlüsselte XML-RPC-Meldung ist. Diese verschlüsselte Meldung wird in eine XML-Zeile entschlüsselt, die anschließend in MQL5-Daten übersetzt wird.



7. Empfehlungen zum Schutz vor Dekompilierung

Wird MQL5-Code aus der Maschinensprache zurückübersetzt (dekompiliert), sind selbst die sichersten Schutzmechanismen für erfahrene Datendiebe nicht mehr unüberwindbar. Nachdem ich im Internet etwas herumgesucht habe, fand ich eine Webseite, auf der angeboten wurde, MQL5-Code zu knacken. Aber ich halte das für Schwindel, um von naiven Menschen, die irgendwelchen Code stehlen wollen, Geld abzuziehen. Ich habe es jedenfalls nicht probiert und könnte mich irren. Selbst wenn es so eine Lösung geben würde, sollten wir in der Lage sein, mithilfe der Übertragung der Eingangsparameter in verschlüsselter Form oder der Indizierung der übertragenen Objekte einen noch zuverlässigeren Schutz zu schaffen.

Datendieben dürfte es schwer fallen die richtigen Eingangsparameter geschützter Expert-Systeme zu ermitteln oder die richtigen Parameter eines geschützten Indikators zu sehen, ohne die seine Verwendung nutzlos ist. Zudem können die richtigen Parameter verschickt werden, wenn das betreffende Konto über eine Nutzungsberechtigung verfügt, bzw. offenkundig falsche, wenn keine Nutzungsberechtigung vorliegt. Dazu kann man sich der Pretty Good Privacy- oder kurz: PGP-Verschlüsselung bedienen. Selbst wenn der Code dekompliert wird, werden mit einem PGP-Schlüssel chiffrierte Daten übertragen, und die Parameter des Expert-Systems können nur sichtbar gemacht werden, wenn die Kontonummer zu dem PGP-Schlüssel passt oder umgekehrt.



Fazit

In diesem Beitrag wurden einige Möglichkeiten für den Schutz von MQL5-Code vorgestellt. Darüber hinaus wurden die Idee einer Berechtigungsprüfung aus der Ferne mittels MQL5-RPC eingeführt und die Unterstützung einer base64-Verschlüsselung hinzugefügt. Ich hoffe, mit diesem Beitrag eine Grundlage für die Entwicklung weiterer Ideen zum Schutz von MQL5-Programmcodes geliefert zu haben. Alle Programmcodes finden im Anhang zu diesem Artikel.


Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/359

Letzte Kommentare | Zur Diskussion im Händlerforum (10)
Carl Schreiber
Carl Schreiber | 15 Dez. 2022 in 07:54

Ich habe auch früher mal versucht sockets zu verwenden, bin aber davon abgekommen, weil der server blockiert, bis er was kriegt ...

Es gab auch mal eine Lösung über shared memeory - die habe ich auch nicht hin gekriegt.

Ich würde eine RAM-Disk verwenden und dann mit den Windows-Funktionen (kernel32.dll) dort einfach Dateien schreiben, lesen und löschen, das klappt und ist recht einfach.

Aleksi-Trader
Aleksi-Trader | 15 Dez. 2022 in 08:22
Carl Schreiber #:

Ich habe auch früher mal versucht sockets zu verwenden, bin aber davon abgekommen, weil der server blockiert, bis er was kriegt ....

Sehe ich das richtig das ich erst alle Daten senden muss und dann fragen soll ob es eine Verbindung gibt?

Carl Schreiber
Carl Schreiber | 15 Dez. 2022 in 10:26

Naja, nach dem Client-Server-Modell wird erst der Server gestartet, denn der wartet auf die Fragen der (vieler) Client(s) und dann der/die Clients.

Im Programm des Servers passiert nix (blockiert) bis etwas von den Clients kommt, dann macht er kurz 'was und fällt dann wieder in seine Warte-Starre.

Such mal nach trade copier in der CodeBase...
Aleksi-Trader
Aleksi-Trader | 15 Dez. 2022 in 10:41
Carl Schreiber #:

Naja, nach dem Client-Server-Modell wird erst der Server gestartet, denn der wartet auf die Fragen der (vieler) Client(s) und dann der/die Clients.

Im Programm des Servers passiert nix (blockiert) bis etwas von den Clients kommt, dann macht er kurz 'was und fällt dann wieder in seine Warte-Starre.

Ahhh logisch, erst alles senden dann antwortet der Server. In den Fall das der Server eine Antwort gibt besteht die Verbindung und ich kann die Antwort abfragen und entsprechend reagieren. Wenn keine Antwort kommt dann die Meldung " License server not connected. "
Carl Schreiber
Carl Schreiber | 15 Dez. 2022 in 11:15

Deepl sagt dies: https://www.deepl.com/translator#en/de/License%20server%20not%20connected

Die Frage ist jetzt warum? Läuft er? Geschützt? ...

Ökonometrie EURUSD Ein-Schritt-Voraus Prognose Ökonometrie EURUSD Ein-Schritt-Voraus Prognose
Der Artikel konzentriert sich auf die Ein-Schritt-Voraus Prognose für EURUSD mit EViews Software und einer weiteren Bewertung der Prognoseergebnisse mit den Programmen in EViews. Die Prognose beinhaltet Regressionsmodelle und wird mit den Mitteln eines für MetaTrader 4 entwickelten Expert Advisor ausgewertet.
Trademinator 3: Aufstand der Handelsrobots Trademinator 3: Aufstand der Handelsrobots
In dem Beitrag „Dr. Tradelove...“ haben wir ein Expert-System angelegt, das die Parameter eines vorher ausgewählten automatischen Handelssystems unabhängig optimiert. Mehr noch, wir haben beschlossen, ein Expert-System zu schaffen, das nicht nur die Parameter des einen, ihm zugeordneten Handelssystems optimieren kann, sondern auch unter mehreren das beste Handelssystem auswählen kann. Schauen wir uns an, wozu es im Stande ist...
Test (Optimirung) Technik und Einige Kriterien für die Auswahl der Expert Adviosor Parameter Test (Optimirung) Technik und Einige Kriterien für die Auswahl der Expert Adviosor Parameter
Es ist kein Problem den Heiligen Gral des Testens zu finden, es ist jedoch viel schwieriger ihn loszuwerden. Dieser Artikel befasst sich mit der Auswahl der Expert Advisor Betriebsparameter mit automatisierter Gruppenverarbeitung von Optimierung und Testergebnissen auf eine maximale Nutzung der Terminal-Leistungsfähigkeit und minimaler Endnutzer-Belastung.
Erstellen von Expert-Systemen mit dem Hilfsprogramm Expert Advisor Visual Wizard Erstellen von Expert-Systemen mit dem Hilfsprogramm Expert Advisor Visual Wizard
Das Hilfsprogramm Expert Advisor Visual Wizard für MetaTrader 5 bietet eine höchst verständliche intuitive grafische Umgebung mit einer umfangreichen Auswahl vorgefertigter Programmblöcke für den Handel, die die Erstellung automatischer Handelssysteme (hier: Expert-Systeme) zu einer Sache von Minuten machen. Die auf Anklicken, Ziehen und Ablegen (click, drag and drop) beruhende Arbeitsweise des Expert Advisor Visual Wizard ermöglicht die Erstellung grafischer Abbildungen von Handelsstrategien und -signalen wie bei der Arbeit mit Papier und Bleistift. Diese Handelsdiagramme werden automatisch von dem von Molanis entwickelten MQL5-Codegenerator analysiert und in einsatzbereite Expert-Systeme übersetzt. Die interaktive grafische Umgebung vereinfacht die Planung und beseitigt die Notwendigkeit des Schreibens von MQL5-Code.