Verwendung von Kryptographie mit externen Anwendungen

19 November 2020, 08:28
Andrei Novichkov
0
157

Einführung

Kryptographie wird in MQL-Programmen nur selten verwendet. Es gibt nicht so viele Möglichkeiten die Kryptographie im täglichen Handel zu verwenden. Eine Ausnahme wäre ein paranoider Signalkopierer, der die gesendeten Daten vor dem Abhören schützen möchte, und vielleicht ist das auch schon alles. Wenn die Daten das Terminal nicht verlassen, ist es sehr schwer vorstellbar, warum man sie ver- bzw. entschlüsseln soll. Darüber hinaus kann dies ein Indikator für die geringe Kompetenz des Entwicklers sein, der eine zusätzliche Terminal-Last erzeugt.

Vielleicht gibt es ja keine Notwendigkeit, Kryptographie im Handel einzusetzen? Eigentlich gibt es doch einen. Denken Sie zum Beispiel an die Lizenzierung. Es kann ein kleines Unternehmen oder sogar ein einzelner Entwickler sein, dessen Produkte populär sind. Lizenzierungsfragen sind in diesem Fall relevant, und daher ist eine Lizenzverschlüsselung/-beschreibung erforderlich.

Es ist möglich, in der Lizenz Nutzerdaten und eine editierbare Liste von Produkten anzugeben. Ein Indikator oder ein Expert Advisor beginnt zu arbeiten, prüft die Verfügbarkeit einer Lizenz und deren Ablauf für das jeweilige Produkt. Ein Programm sendet eine Anfrage an den Server, aktualisiert ggf. die Lizenz oder erhält eine neue Lizenz. Dies kann nicht der effizienteste und sicherste Weg sein, aber wir werden ihn in diesem Artikel zu Demonstrationszwecken verwenden. Natürlich wird die Lizenz in diesem Fall von verschiedenen Software-Tools gelesen/geschrieben — einem Terminal, einem Remote-Server, Kontrollmodulen und Protokollierungsmodulen. Sie alle können von verschiedenen Personen, zu verschiedenen Zeiten und in verschiedenen Sprachen geschrieben werden.

Der Zweck dieses Artikels ist es, die Ver-/Entschlüsselungsmodi zu untersuchen, bei denen ein Objekt, das durch ein Programm in C# oder C++ verschlüsselt wurde, durch das MetaTrader-Terminal entschlüsselt werden kann und umgekehrt.

Der Artikel richtet sich sowohl an Programmierer mit mittleren Fähigkeiten als auch an Anfänger.

Formulierung des Ziels

Dies wurde bereits in der Einleitung erwähnt. Wir werden versuchen, eine Lösung für ein reales Problem zu simulieren, das die Erstellung, Ver- und Entschlüsselung einer Lizenz für mehrere Produkte — Indikatoren und Expert Advisors — erfordert. Es ist für uns nicht entscheidend, welches Programm verschlüsselt und welches die Lizenz entschlüsselt. Zum Beispiel kann eine Lizenz zunächst auf dem Computer des Entwicklers erstellt werden, dann wird sie in der Verkaufsabteilung korrigiert und auf dem Computer des Händlers entschlüsselt. Der Prozess muss frei von Fehlern sein, die mit schlecht konfigurierten Algorithmen verbunden sind.

Zusammen mit der Lösung der Hauptaufgabe werden wir das komplexe Problem der Lizenzierung betrachten. Dabei wird es sich nicht um eine gebrauchsfertige Lizenz handeln, sondern um eine der möglichen Varianten, die weiter bearbeitet und entwickelt werden sollte.

Quelldaten

Um die Quelldaten für die Verwendung zu erhalten, verweisen wir auf die Terminaldokumentation. Es gibt zwei Standardfunktionen, die für die Ver-/Entschlüsselungsverfahren zuständig sind:

int  CryptEncode(
   ENUM_CRYPT_METHOD   method,        // conversion method
   const uchar&        data[],        // source array
   const uchar&        key[],         // encryption key
   uchar&              result[]       // destination array
   );

int  CryptDecode(
   ENUM_CRYPT_METHOD   method,        // conversion method
   const uchar&        data[],        // source array
   const uchar&        key[],         // encryption key
   uchar&              result[]       // destination array
   );

Die für die Ver-/Entschlüsselung verwendete spezifische Methode wird durch das Argument method bestimmt. In diesem Fall sind wir an drei Werten interessiert, die das Argument method haben kann: CRYPT_AES128, CRYPT_AES256, CRYPT_DES. Diese drei Werte stellen symmetrische Verschlüsselungsalgorithmen mit unterschiedlichen Schlüssellängen dar.

In diesem Artikel verwenden wir nur einen von ihnen, CRYPT_AES128. Dies ist ein symmetrischer Blockchiffrieralgorithmus mit einem 128-Bit-Schlüssel (16 Byte). Die Verwendung anderer Algorithmen ist ähnlich.

Der AES-Algorithmus (nicht nur der ausgewählte, sondern auch solche mit anderen Schlüssellängen) hat einige wichtige Einstellungen, die in den obigen Funktionen nicht vorgesehen sind. Dazu gehören der Verschlüsselungsmodus und das Padding (Auffüllen mit einer Ziffer). Auf Einzelheiten zu diesen Begriffen werden wir nicht näher eingehen. Das Terminal verwendet also den Verschlüsselungsmodus Electronic Codebook (ECB) und Padding durch Null. Ich möchte mich bei meinen Handelskollegen bedanken, da dies im MQL5-Forum erklärt wurde. Damit wird unsere Aufgabe viel einfacher zu lösen sein.

Entwicklung eines Operationsobjekts

Da wir Ver-/Entschlüsselung als auf die Lizenzierung angewendet betrachten, ist unser Betriebsobjekt die Lizenz. Dies sollte eine Art Konstruktion sein, die Informationen über verschiedene Produkte enthält, für die die Lizenz gilt. Die folgenden Daten sind hier erforderlich:

  1. Ablauf der Lizenz für dieses Produkt.
  2. Name des Produkts.

Lassen Sie uns eine entsprechende Struktur mit den dazugehörigen einfachsten Methoden erstellen:

#define PRODMAXLENGTH 255

struct ea_user 
  {
   ea_user() {expired = -1;}
   datetime expired;                //License expiration (-1 - unlimited)
   int      namelength;             //Product name length
   char    uname[PRODMAXLENGTH];    //Product name
   void SetEAname(string name) 
     {
      namelength = StringToCharArray(name, uname);
     }
   string GetEAname() 
     {
      return CharArrayToString(uname, 0, namelength);
     }
   bool IsExpired() 
     {
      if (expired == -1) 
         return false; // NOT expired
      return expired <= TimeLocal();
     }
  };//struct ea_user


Hier einige Erklärungen:

  • Der Produktname wird als Array mit fester Länge gespeichert.
  • Der Name der maximalen Produktlänge ist auf PRODMAXLENGTH begrenzt.
  • Diese Strategie lässt sich leicht in ein Byte-Array packen — dies werden wir tun, bevor wir das gesamte Objekt verschlüsseln.

Diese Struktur allein reicht jedoch nicht aus. Offensichtlich muss die Lizenz Nutzerangaben enthalten. Die Informationen könnten in die bereits beschriebene Struktur aufgenommen werden, aber das wäre ineffizient, da der Nutzer möglicherweise mehrere Produktlizenzen hat. Eine vernünftigere Lösung ist es, eine separate Struktur für den Nutzer zu erstellen und ihr die erforderliche Anzahl von Produktlizenzen hinzuzufügen. Auf diese Weise hat ein Nutzer eine Lizenz, die Berechtigungen und Einschränkungen für alle lizenzierten Produkte enthält.

Informationen, die in der den Nutzer beschreibenden Struktur enthalten sein können:

  1. Die eindeutige Nutzer-ID. Der Name könnte auch gespeichert werden, aber es erscheint nicht wünschenswert, jedes Mal persönliche Daten zu senden, auch nicht in verschlüsselter Form.
  2. Informationen über die Konten des Nutzers, auf denen die Produkte verwendet werden können.
  3. Das Ablaufdatum der Lizenz des Nutzers. Dieses Feld kann die Nutzung aller vorhandenen Produkte, auch unbegrenzter Art, auf die Dienstzeit des Nutzers als solchen beschränken.
  4. Die Anzahl der lizenzierten Produkte im Nutzerterminal:
#define COUNTACC 5

struct user_lic {
   user_lic() {
      uid       = -1;
      log_count =  0;
      ea_count  =  0;
      expired   = -1;
      ArrayFill(logins, 0, COUNTACC, 0);
   }
   long uid;                       //User ID
   datetime expired;               //End of user service (-1 - unlimited)
   int  log_count;                 //The number of the user's accounts
   long logins[COUNTACC];          //User's accounts
   int  ea_count;                  //The number of licensed products
   bool AddLogin(long lg){
      if (log_count >= COUNTACC) return false;
      logins[log_count++] = lg;
      return true;
   }
   long GetLogin(int num) {
      if (num >= log_count) return -1;
      return logins[num];
   }
   bool IsExpired() {
      if (expired == -1) return false; // NOT expired
      return expired <= TimeLocal();
   }   
};//struct user_lic

Hier sind einige Klarstellungen:

  • Nutzerkonten werden in einem Array mit fester Länge gespeichert. Sie werden durch Kontonummern dargestellt. Die Informationen können jedoch bei Bedarf leicht durch den Server- oder Brokernamen und die Anzahl der Aktivierungen für ein bestimmtes Konto ergänzt werden.

Wie bisher enthält diese Struktur ausreichend Informationen über den Nutzer und die relevanten Produkte. Jedes von ihnen ist ein Typ, von dem eine Instanz durch die Funktion StructToCharArray bearbeitet werden kann.

Nun müssen wir die Strukturdaten in ein Byte-Array serialisieren, das weiter verschlüsselt werden kann. Dies wird wie folgt implementiert:

  • Erzeugen und initialisieren Sie eine Instanz der Struktur user_lic.
  • Serialisieren Sie sie in einem Byte-Array.
  • Erstellen und initialisieren Sie eine oder mehrere Instanzen der Struktur ea_user.
  • Serialisieren Sie sie in demselben Byte-Array, indem Sie dessen Größe erhöhen und das Feld ea_count anpassen.

Erstellen Sie eine Klasse zur Durchführung dieser Operationen:

class CLic {

public:

   static int iSizeEauser;
   static int iSizeUserlic;
   
   CLic() {}
  ~CLic() {}
   
   int SetUser(const user_lic& header){
      Reset();
      if (!StructToCharArray(header, dest) ) return 0;
      return ArraySize(dest);
   }//int SetUser(user_lic& header)

   int AddEA(const ea_user& ea) {
      int c = ArraySize(dest);
      if (c == 0) return 0;
      uchar tmp[];
      if (!StructToCharArray(ea, tmp) ) return 0;
      ArrayInsert(dest, tmp, c);
      return ArraySize(dest);
   }//int AddEA(ea_user& ea)
   
   bool GetUser(user_lic& header) const {
      if (ArraySize(dest) < iSizeUserlic) return false;
      return CharArrayToStruct(header, dest);
   }//bool GetUser(user_lic& header)
   
   //num - 0 based
   bool GetEA(int num, ea_user& ea) const {
      int index = iSizeUserlic + num * iSizeEauser;
      if (ArraySize(dest) < index + iSizeEauser) return false;
      return CharArrayToStruct(ea, dest, index);
   }//bool GetEA(int num, ea_user& ea)
   
   int Encode(ENUM_CRYPT_METHOD method, string key, uchar&  buffer[]) const {
      if (ArraySize(dest) < iSizeUserlic) return 0;
      if(!IsKeyCorrect(method, key) ) return 0;      
      uchar k[];
      StringToCharArray(key, k);
      return CryptEncode(method, dest, k, buffer); 
   }
   
   int Decode(ENUM_CRYPT_METHOD method, string key, uchar&  buffer[]) {
      Reset();
      if(!IsKeyCorrect(method, key) ) return 0;
      uchar k[];
      StringToCharArray(key, k);
      return CryptDecode(method, buffer, k, dest); 
   }   

protected:
   void Reset() {ArrayResize(dest, 0);}
   
   bool IsKeyCorrect(ENUM_CRYPT_METHOD method, string key) const {
      int len = StringLen(key);
      switch (method) {
         case CRYPT_AES128:
            if (len == 16) return true;
            break;
         case CRYPT_AES256:
            if (len == 32) return true;
            break;
         case CRYPT_DES:
            if (len == 7) return true;
            break;
      }
#ifdef __DEBUG_USERMQH__
   Print("Key length is incorrect: ",len);
#endif       
      return false;
   }//bool IsKeyCorrect(ENUM_CRYPT_METHOD method, string key)
   
private:
   uchar dest[];
};//class CLic

   static int CLic::iSizeEauser  = sizeof(ea_user);
   static int CLic::iSizeUserlic = sizeof(user_lic);

Der Klasse wurden zwei Funktionen hinzugefügt, die Ver- und Entschlüsselung ermöglichen, sowie eine geschützte Funktion zur Überprüfung der Schlüssellänge. Sie können aus ihrem Code ersehen, dass z.B. die Schlüssellänge für die Methode CRYPT_AES128 16 Bytes betragen muss. Tatsächlich darf sie nicht weniger als 16 Bytes betragen. Wahrscheinlich ist sie außerdem irgendwie normalisiert, was dem Entwickler verborgen bleibt. Wir werden uns nicht darauf verlassen und werden die erforderliche Schlüssellänge strikt festlegen.

Schließlich ist es bereits möglich, das resultierende Byte-Array zu verschlüsseln und in eine Binärdatei zu speichern. Diese Datei sollte nach den allgemeinen Regeln im Ordner Files des Terminals gespeichert werden. Falls erforderlich, kann sie gelesen und entschlüsselt werden:

bool CreateLic(ENUM_CRYPT_METHOD method, string key, CLic& li, string licname) {
   uchar cd[];
   if (li.Encode(method, key, cd) == 0) return false;
   int h = FileOpen(licname, FILE_WRITE | FILE_BIN);
   if (h == INVALID_HANDLE) {
#ifdef __DEBUG_USERMQH__
      Print("File create failed: ",licname);
#endif    
      return false;
   }
   FileWriteArray(h, cd);
   FileClose(h);  
#ifdef __DEBUG_USERMQH__    
   li.SaveArray();
#endif    
   return true;
}// bool CreateLic(ENUM_CRYPT_METHOD method, string key, const CLic& li, string licname)


bool ReadLic(ENUM_CRYPT_METHOD method, string key, CLic& li, string licname) {
   int h = FileOpen(licname, FILE_READ | FILE_BIN);
   if (h == INVALID_HANDLE) {
#ifdef __DEBUG_USERMQH__
      Print("File open failed: ",licname);
#endif    
      return false;
   }
   uchar cd[];
   FileReadArray(h,cd);
   if (ArraySize(cd) < CLic::iSizeUserlic) {
#ifdef __DEBUG_USERMQH__
      Print("File too small: ",licname);
#endif    
      return false;
   }
   li.Decode(method, key, cd);
   FileClose(h);
   return true;
}// bool ReadLic(ENUM_CRYPT_METHOD method, string key, CLic& li, string licname)

Beide Funktionen sind klar und bedürfen keiner weiteren Erläuterung. Das beigefügte Archiv CryptoMQL.zip enthält zwei Skripte und eine Bibliotheksdatei, die die Ver-/Entschlüsselung implementiert, sowie die verschlüsselte Lizenzdatei lic.txt.

Das Projekt für C#

Lassen Sie uns ein einfaches C#-Projekt erstellen, um den Prozess der Entschlüsselung und Bearbeitung durch ein anderes Programm zu simulieren. Verwenden Sie Visual Studio 2017 und erstellen Sie eine Konsolenanwendung für die Framework-Plattform .NET. Überprüfen Sie die Verbindung von System.Security und dem Raum System.Security.Cryptography. 

Die folgende Frage stellt sich im Kodex: MQL und C# haben unterschiedliche Zeitformate. Problem wurde bereits in diesem Artikel angesprochen und gelöst. Der Autor hat großartige Arbeit geleistet, und wir können seine Klasse MtConverter in unserem Projekt verwenden.

Erstellen Sie zwei Klassen, EaUser und UserLic, mit Feldern, die den Strukturen ea_user und user_lic ähneln. Der Zweck besteht darin, die vom Terminal erstellte Lizenz (die Datei lic.txt) zu entschlüsseln, die empfangenen Daten zu analysieren, die Objekte zu modifizieren und erneut zu verschlüsseln, wobei eine neue Datei erstellt wird. Diese Aufgabe muss einfach zu implementieren sein, wenn Sie die Ver-/Entschlüsselungsmodi sorgfältig einstellen. Hier ist, wie das entsprechende Stück Code aussieht:

            using (Aes aesAlg = Aes.Create())
            {
                aesAlg.Key = Key;
                aesAlg.IV = IV;
                aesAlg.Mode = CipherMode.ECB;
                aesAlg.Padding = PaddingMode.Zeros;
                ..................................

Achten Sie auf die letzten beiden Zeilen, in denen der Verschlüsselungsmodus (ECB) und die Auffüllung eingestellt sind. Wir verwenden die verfügbaren Informationen über die Einstellungen für diese Modi. Die erste Zeile im Block, die die Schlüsselinstallation betrifft, sollte klar sein. Sie verwendet denselben Schlüssel, der für die Verschlüsselung im Terminal verwendet wird, aber diesmal wird er in ein Byte-Array umgewandelt:

            string skey = "qwertyuiopasdfgh";
            byte[] Key  = Encoding.ASCII.GetBytes(s: skey);

Achten Sie auf die Zeile, in der der Parameter "IV" gesetzt ist. Dies ist der sogenannte "Initialisierungsvektor", d.h. eine Zufallszahl, die an allen Verschlüsselungsmodi außer dem ECB-Modus beteiligt ist. Daher erstellen wir an dieser Stelle einfach ein Array der gewünschten Länge:

byte[] iv   = new byte[16];

Beachten Sie außerdem, dass sich die Situation der Schlüssel in C# von der in MQL unterscheidet. Wenn die Schlüssellänge (in diesem Fall die Zeile "qwertyuiopasdfgh") größer als 16 ist, wird eine Ausnahme ausgeworfen. Aus diesem Grund war es eine gute Entscheidung, die Schlüssellänge im MQL-Code unbedingt zu kontrollieren.

Der Rest ist ziemlich einfach. Lesen Sie die Binärdatei -> Entschlüsseln Sie den Stream -> Füllen Sie die erzeugte Klasseninstanz UserLic mit BinaryReader. Wahrscheinlich könnte ein ähnliches Ergebnis erzielt werden, wenn die entsprechenden Klassen serialisierbar gemacht würden. Sie können diese Möglichkeit selbst testen.

Lassen Sie uns ein beliebiges Feld modifizieren, in unserem Fall werden wir die Nutzer-ID ändern. Dann verschlüsseln Sie die Daten auf die gleiche Weise und erstellen eine neue Datei "lic_C#.txt". Die obigen Operationen werden von zwei statischen Funktionen im Projekt durchgeführt, EncryptToFile_Aes und DecryptFromFile_Aes. Zu Debugging-Zwecken habe ich zwei ähnliche Funktionen hinzugefügt, die nicht mit den Dateien, sondern mit Byte-Arrays arbeiten: EncryptToArray_Aes und DecryptFromArray_Aes.

Das Projekt CryptoC#.zip mit allen erforderlichen Dateien ist unten angehängt.

Jeder könnte die folgenden Projektmängel bemerkt haben:

  • Es verfügt nicht über die erforderlichen Prüfungen der Argumente der aufgerufenen Funktionen.
  • Es gibt keine Ausnahmebehandlung.
  • Die Betriebsart ist Single-Stream.

Ich habe die oben genannten Funktionen nicht implementiert, da der Artikel nicht darauf abzielt, eine Anwendung mit vollem Funktionsumfang zu erstellen. Wenn wir alle erforderlichen Teile implementieren würden, wäre der zusätzliche Codeteil zu groß und würde die Aufmerksamkeit von dem Grundproblem ablenken.

Das Projekt für C++

Das nächste Projekt wird in C++ erstellt. Lassen Sie uns eine Konsolenanwendung in der Visual Studio 2017-Umgebung erstellen. Wir haben keine Unterstützung für Ver-/Entschlüsselung "out of the box". Daher müssen wir die bekannte Bibliothek OpenSSL einbinden, indem wir das OpenSSL-Installationspaket herunterladen und installieren. Auf diese Weise können wir alle Bibliotheken und -Includes von OpenSSL verwenden, die mit den erstellten Projekten verbunden werden sollten. Für Einzelheiten darüber, wie Bibliotheken mit einem Projekt verbunden werden können, lesen Sie bitte diesen Artikel. Leider ist die OpenSSL-Dokumentation bei weitem nicht vollständig, aber es gibt nichts Besseres.

Sobald die Bibliotheken eingebunden sind, fahren Sie mit dem Schreiben des Codes fort. Als Erstes müssen die bereits bekannten zwei Strukturen noch einmal beschrieben werden:

constexpr size_t PRODMAXLENGTH = 255;

#pragma pack(1)
typedef struct EA_USER {
        EA_USER();
        EA_USER(std::string name);
        EA_USER(EA_USER& eauser);
        std::time_t  expired;
        long   namelength;
        char eaname[PRODMAXLENGTH];
        std::string GetName();
        void SetName(std::string newName);
        std::string GetTimeExpired();
        std::string ToString();
        size_t ToArray(byte* pbyte);
        constexpr size_t GetSize() noexcept;
        friend std::ostream& operator<< (std::ostream& out, EA_USER& eauser);
        friend std::istream& operator>> (std::istream& in, EA_USER& eauser);
}EAUSER, *PEAUSER;
#pragma pack()

constexpr size_t COUNTACC = 5;

#pragma pack(1)
typedef struct USER_LIC
{
        using PEAUNIC = std::unique_ptr<EAUSER>;

        USER_LIC();
        USER_LIC(USER_LIC&& ul);
        USER_LIC(const byte* pbyte);
        int64_t uid;                  
        std::time_t expired;       
        long log_count;             
        int64_t logins[COUNTACC];     
        long ea_count;             
        std::vector<PEAUNIC> pEa;

        std::string GetTimeExpired();
        std::string ToString();
        size_t ToArray(byte* pbyte);
        void AddEA(EA_USER eau);
        bool AddAcc(long newAcc);
        size_t GetSize();

        friend std::ostream& operator<< (std::ostream& out, USER_LIC& ul);
        friend std::istream& operator>> (std::istream& in, USER_LIC& ul);

	USER_LIC& operator = (const USER_LIC&) = delete;
	USER_LIC(const USER_LIC&) = delete;
} USERLIC, *PUSERLIC;
#pragma pack()


Der Code ist etwas komplexer als in der C#. Es gibt Unterschiede bei bestimmten Feldtypen. Beispielsweise hat in diesem Projekt das Feld mit dem Array von Konten den Arraytyp int64_t und die MQL-Include-Datei hat den Typ long. Dies hängt mit der Größe der entsprechenden Typen zusammen. Wenn Sie solche Merkmale nicht kontrollieren, kann dies zu schwer abzufangenden Fehlern führen. Einige Teile sind einfacher: Zeit muss nicht konvertiert werden.

Außerdem können wir in diesem Projekt mit dem Problem der falschen Schlüssellänge konfrontiert werden. Um dieses Problem zu lösen, nehmen Sie die folgende Funktion in das Projekt auf:

std::string AES_NormalizeKey(const void *const apBuffer, size_t aSize)

Diese Funktion "trimmt" das Array appBuffer auf die erforderliche Länge aSize. Lassen Sie uns auch die folgende Hilfsfunktion schreiben:

void handleErrors(void)
{
        ERR_print_errors_fp(stderr);
}

Diese Funktion liefert Erläuterungen zu Fehlercodes aus der Bibliothek OpenSSL. Die folgenden zwei Funktionen implementieren die Hauptoperationen:

int aes_decrypt(const byte* key, const byte* iv, const byte* pCtext, byte* pRtext, int iTextsz)
int aes_encrypt(const byte* key, const byte* iv, const byte* pPtext, byte* pCtext, int iTextsz)

Die Implementierung der Methoden ist in den beigefügten Dateien enthalten. Ich werde nur einige der wesentlichen Punkte erwähnen:

  • Hier wird kein Initialisierungsvektor verwendet. Wir erstellen ein Array in der gewünschten Größe und übergeben es am Aufrufpunkt.
  • Die Bibliothek bietet keine Vorteile bezüglich des "Paddings". Stellen Sie diesen Modus durch Aufruf ein:
    EVP_CIPHER_CTX_set_padding(ctx.get(), 0);
    
    
    Stellen Sie sicher, dass Sie "0" übergeben, aber nicht auf diese Weise:
    EVP_CIPHER_CTX_set_padding(ctx.get(), EVP_PADDING_ZERO);
    
    
    wie man vermuten könnte. Mit dem Zusatz sind noch weitere Fragen verbunden. Tatsache ist, dass, wenn der Wert für Padding Null ist (wie in unserem Projekt), dann muss der Entwickler darauf achten, dass die Länge des verschlüsselten Objekts ein Vielfaches von BLOCK_SIZE = AES_BLOCK_SIZE ist, nämlich 16 Bytes. Aus diesem Grund ist es notwendig, vor dem Aufruf von aes_encrypt(......) die entsprechende Ausrichtung des zu verschlüsselnden Arrays anzugeben.

Führen Sie die gleiche Abfolge von Aktionen durch, wie wir es im vorherigen Projekt getan haben:

  • Entschlüsseln Sie die resultierende Datei, bearbeiten Sie sie und verschlüsseln Sie sie erneut. Fügen Sie in diesem Fall der Lizenz Informationen über ein weiteres Nutzerkonto hinzu.
  • Nun erhalten wir eine weitere verschlüsselte Datei, lic_С++.txt . Die Dateigröße ist diesmal anders. Dies ist die Blockgröße (16 Byte), die bei der Ausrichtung hinzugefügt wurde.

Alle Quelldateien des Projekts sind in dem unten angehängten Archiv CryptoС++.zip verfügbar.

    Abschließende Prüfungen und Ergebnisse

    Nun gehen wir zum letzten Operationsschritt über. Verschieben Sie die kürzlich verschlüsselte Datei lic_С++.txt in den Dateiordner des MetaTrader Datenverzeichnisses und entschlüsseln Sie sie mit dem zuvor geschriebenen Skript decryptuser.mq5. Wir erhalten das erwartete Ergebnis: die Datei wurde erfolgreich entschlüsselt, trotz der veränderten Längen.

    Was erhalten wir also als Ergebnis? Am wichtigsten ist, dass wir die Ver-/Entschlüsselungsparameter bestimmt haben, mit denen verschlüsselte Dateien von einem Programm in ein anderes übertragen werden können. Natürlich können wir später davon ausgehen, dass, wenn die Ver-/Entschlüsselung fehlschlägt, das Problem durch Fehler in Anwendungsprogrammen verursacht werden kann.

      Hashes

      Die meisten von Ihnen wissen vielleicht, dass Kryptographie nicht nur auf Ver-/Entschlüsselung beschränkt ist. Betrachten wir ein kryptographisches Primitiv - Hashing. Dieser Prozess impliziert das Umwandeln eines beliebigen Arrays in ein Array mit fester Länge. Ein solches Array wird als Hash bezeichnet, und die Konvertierungsfunktion wird als Hash-Funktion bezeichnet. Zwei anfängliche Arrays, die sich voneinander in mindestens einem Bit unterscheiden, erzeugen völlig unterschiedliche Hashes, die zur Identifizierung und zum Vergleich verwendet werden können.

      Hier ist ein Beispiel. Der Nutzer registriert sich auf der Website und gibt seine Identifikationsdaten ein. Die Daten werden in einer sehr geheimen Datenbank gespeichert. Nun versucht derselbe Nutzer, sich auf der Website einzuloggen, indem er Login und Passwort auf der Hauptseite eingibt. Was soll die Website tun? Sie kann einfach das Passwort des Nutzers aus der Datenbank abrufen und es mit dem eingegebenen Passwort vergleichen. Aber das ist nicht sicher. Der sichere Weg ist, den Hash des gespeicherten Passwortes und den Hash des eingegebenen Passwortes zu vergleichen. Selbst wenn der gespeicherte Hash gestohlen wird, bleibt das Passwort selbst sicher. Durch den Vergleich der Hashes können wir feststellen, ob das eingegebene Passwort korrekt ist.

      Hashing ist ein einseitiger Prozess. Mit dem verfügbaren Hash ist es nicht möglich, das Datenfeld zu erhalten, für das der Hash empfangen wird. Hashes sind also für die Kryptographie sehr wichtig. Betrachten wir die Berechnung eines Hashwertes in verschiedenen Umgebungen.

      Unser Zweck ist derselbe: Wir wollen herausfinden, wie sichergestellt werden kann, dass der Hash für dieselben Ausgangsdaten bei der Berechnung im Terminal und in anderen Drittprogrammen derselbe ist.

      In MQL wird der Hash mit der gleichen Bibliotheksfunktion berechnet, die wir zuvor verwendet haben: CryptEncode. Das Funktionsargument method sollte zur Berechnung des Hashes auf einen Wert gesetzt werden. Verwenden wir den Wert CRYPT_HASH_SHA256. In der Dokumentation finden Sie weitere Werte und andere Hash-Typen, so dass Sie sich weiter über dieses Thema informieren können. Verwenden Sie die Zeile des bereits vorhandenen Passworts: "qwertyuiopasdfgh" als Quell-Array. Berechnen Sie seinen Hashwert und schreiben Sie ihn in eine Datei. Der resultierende Code ist sehr einfach; daher fügen wir ihn einfach in die angehängte Skriptdatei decryptuser.mq5 ein, ohne separate Klassen und Funktionen zu erstellen:

      string k = "qwertyuiopasdfgh";
      
      
      uchar key[], result[], enc[];
            StringToCharArray(k, enc);
            int sha = CryptEncode(CRYPT_HASH_SHA256,enc,key,result);   
            string sha256;
            for(int i = 0; i < sha; i++) sha256 += StringFormat("%X ",result[i]);
            Print("SHA256 len: ",sha," Value:  ",sha256);
            int h = FileOpen("sha256.bin", FILE_WRITE | FILE_BIN);
            if (h == INVALID_HANDLE) {
               Print("File create failed: sha256.bin");
            }else {
               FileWriteArray(h, result);
               FileClose(h);            
            }      
      
      

      Das Array key, das früher zur Verschlüsselung verwendet wurde, wird hier nicht verwendet. Schreiben Sie den resultierenden Hash in das Array result, zeigen Sie ihn im Fenster und schreiben Sie in die Datei sha256.bin. Die Länge des resultierenden Hashes ist auf 32 Bytes festgelegt. Sie können die Größe des Quell-Arrays ändern, sagen wir, ein Zeichen lang machen, aber die Hash-Größe wird trotzdem noch 32 Bytes betragen.

      Wiederholen Sie die gleiche Berechnung, indem Sie die erforderliche Funktionalität zu C#- und C++-Projekten hinzufügen. Die Änderungen sind minimal und sehr einfach. Wir verwenden dasselbe Quell-Array aus der Passwort-Zeichenfolge. Fügen Sie ähnliche Codezeilen hinzu. Berechnen und... Enttäuschung, weil es unterschiedliche Ergebnisse gibt! Nun, sie sind "nicht völlig unterschiedlich". Die vom MQL-Skript berechneten Hashes und das C++-Projekt sind die gleichen. Aber das C#-Projekt liefert ein anderes Ergebnis. Versuchen wir, eine andere Zeichenfolge zu verwenden, die aus einem Zeichen besteht, "a". Auch hier führt die Berechnung im C#-Projekt zu einem anderen Hash.

      Das Problem hängt mit dem Funktionsaufruf StringToCharArray zusammen, der eine Zeichenfolge in ein Array umwandelt. Wenn Sie sich das resultierende Array nach dem Aufruf StringToCharArArray ansehen, sehen Sie, dass sich die Größe des Arrays verdoppelt hat. Wenn Sie zum Beispiel eine Funktion mit der Zeichenfolge "a" aufrufen, hat das resultierende Array zwei Elemente. Das zweite Element wird "0" sein. Der Aufruf von Encoding.ASCII.GetBytes im C# vermeidet dies. In diesem Fall wird "0" nicht in das Array eingetragen. 

      Jetzt können wir dem C#-Projekt einen Code-Block hinzufügen, der "0" an das Byte-Array anhängt. Danach können wir dieses Byte-Array zur Berechnung des Hashes verwenden. Jetzt erhalten wir das erwartete Ergebnis. Alle drei Projekte berechnen den gleichen Hash für die gleichen Eingabedaten. Die resultierenden Hashes stehen in den Dateien sha256.bin, sha256_c#.bin, sha256_С++.bin zur Verfügung, die sich im unten angehängten CryptoMQL.zip-Archiv befinden.

      Bitte beachten Sie, dass das obige Beispiel Textdaten betrifft. Wenn es sich um ein anfänglich binäres Array handelt, ist es natürlich nicht notwendig, StringToCharArray und Encoding.ASCII.GetBytes aufzurufen. Und es wird kein Problem mit einer zusätzlichen 0 geben. Eine weitere mögliche Option ist also, die 0 aus einem MQL-Projekt zu entfernen, anstatt sie in C# hinzuzufügen.

      Nichtsdestotrotz haben wir das anfängliche Problem gelöst - wir haben herausgefunden, unter welchen Bedingungen ein bestimmter Objekthash identisch sein wird, selbst wenn er in verschiedenen Umgebungen berechnet wird. Wir haben auch das am Anfang des Artikels angegebene Ziel erreicht. Wir haben festgestellt, welche Ver-/Entschlüsselungsmodi verwendet werden sollten, um die Kompatibilität der Ergebnisse in verschiedenen Umgebungen zu gewährleisten.

      Schlussfolgerung

      Obwohl Verschlüsselungs-/Entschlüsselungsoperationen beim algorithmischen Handel im MetaTrader 5-Terminal nicht häufig verwendet werden, kann diese Aufgabe hilfreich sein, wenn ein solcher Bedarf besteht.

      Was geht über diesen Artikel hinaus? Erstellung von Archiven - eine solche Option steht für die Funktion CrypetEncode zur Verfügung. Der Kodierungsstandard Base64, der ebenfalls verfügbar ist. Ich denke, es besteht keine Notwendigkeit, diese Modi zu berücksichtigen. Es stimmt, dass Passwörter beim Erstellen von Archiven gesetzt werden können, aber:

      • Diese Möglichkeit wird in der Dokumentation nicht erwähnt.
      • Das Erstellen von Archiven, auch von solchen, die durch ein Passwort geschützt sind, hat nichts mit Kryptographie zu tun.
      Eine weitere Möglichkeit ist die Base64-Verschlüsselung. Es gibt einige irreführende Verweise auf diesen Standard im Zusammenhang mit Kryptographie. Dieser Standard darf nicht zur Ver-/Entschlüsselung verwendet werden! Wenn Sie möchten, können Sie mehr über diesen Standard und seine Anwendung in der Praxis erfahren.

      Gegenstand unserer Arbeit ist die Lizenz. Für den Zweck dieses Artikels habe ich den Weg gewählt, der für das Verständnis von Ver-/Entschlüsselungsaufgaben hilfreich sein könnte. Dazu habe ich Byte-Arrays verwendet. Sie werden verschlüsselt, in eine Datei geschrieben, wieder entschlüsselt und so weiter. In einer realen Situation wäre dies äußerst unpraktisch und kann Fehler verursachen. Wenn man die Originalstrukturen in ein Array packt und auspackt, würde ein Ein-Bit-Fehler die gesamte Lizenz beschädigen. Außerdem ist eine solche Situation durchaus möglich, wenn man die Unterschiede in den Größen der verschiedenen Typen berücksichtigt, wie oben gezeigt. Ein weiteres mögliches Format zur Speicherung der Lizenz ist daher der Text. Diese sind xml und json. Eine gute Lösung, die in Betracht gezogen werden sollte, ist die Verwendung des json-Formats, da wir ausgezeichnete existierende Parser für MQL, C# und C++ verwenden können.


      Die Programme dieses Artikels:

       # Name
      Typ
       Beschreibung
      1
      CryptoMQL.zip Archive Archive mit Skripten für Ver- und Entschlüsselung.
      2 CryptoC#.zip Archive C# Projekt für Ver- und Entschlüsselung.
      3 CryptoС++.zip Archive C++ Projekt für Ver- und Entschlüsselung.


      Übersetzt aus dem Russischen von MetaQuotes Software Corp.
      Originalartikel: https://www.mql5.com/ru/articles/8093

      Beigefügte Dateien |
      CryptoMQL.zip (4.43 KB)
      CryptoCc.zip (7.06 KB)
      CryptoCl4.zip (2456.49 KB)
      Gradient Boosting (CatBoost) für die Entwicklung von Handelssystemen. Ein naiver Zugang Gradient Boosting (CatBoost) für die Entwicklung von Handelssystemen. Ein naiver Zugang

      Trainieren des Klassifikators CatBoost in Python und Exportieren des Modells nach mql5, sowie Parsen der Modellparameter und eines nutzerdefinierten Strategietesters. Die Python-Sprache und die MetaTrader 5-Bibliothek werden zur Vorbereitung der Daten und zum Training des Modells verwendet.

      Zeitreihen in der Bibliothek DoEasy (Teil 48): Mehrperioden-Multisymbol-Indikatoren mit einem Puffer in einem Unterfenster Zeitreihen in der Bibliothek DoEasy (Teil 48): Mehrperioden-Multisymbol-Indikatoren mit einem Puffer in einem Unterfenster

      Der Artikel betrachtet ein Beispiel für die Erstellung von Mehrsymbol- und Mehrperioden-Standardindikatoren unter Verwendung eines einzigen Indikator-Puffers für die Konstruktion und die Darstellung im Indikator-Unterfenster. Ich werde die Bibliotheksklassen auf die Arbeit mit Standardindikatoren vorbereiten, die im Hauptfenster des Programms arbeiten und mehr als einen Puffer für die Anzeige ihrer Daten haben.

      Zeitreihen in der Bibliothek DoEasy (Teil 49): Standardindikatoren mit mehreren Puffern für mehrere Symbole und Perioden Zeitreihen in der Bibliothek DoEasy (Teil 49): Standardindikatoren mit mehreren Puffern für mehrere Symbole und Perioden

      Im aktuellen Artikel werde ich die Bibliotheksklassen verbessern, um die Fähigkeit zu implementieren, Standardindikatoren mit mehreren Symbolen und mehreren Perioden zu entwickeln, die mehrere Indikatorpuffer zur Anzeige ihrer Daten benötigen.

      Diskretisierung von Preisreihen, Zufallskomponente und das Rauschen Diskretisierung von Preisreihen, Zufallskomponente und das Rauschen

      Normalerweise analysieren wir den Markt mit Hilfe von Kerzen oder Balken, die die Preisreihen in regelmäßige Intervalle aufteilen. Verzerrt eine solche Diskretisierungsmethode nicht die reale Struktur der Marktbewegungen? Die Diskretisierung eines Audiosignals in regelmäßigen Abständen ist eine akzeptable Lösung, da ein Audiosignal eine Funktion ist, die sich mit der Zeit ändert. Das Signal selbst ist eine Amplitude, die von der Zeit abhängt. Diese Signaleigenschaft ist fundamental.