English
preview
Reine Implementierung der RSA-Verschlüsselung in MQL5

Reine Implementierung der RSA-Verschlüsselung in MQL5

MetaTrader 5Integration |
20 0
Vahid Irak
Vahid Irak

Stellen Sie sich eine häufige Situation vor: Ihr Expert Advisor muss Signale, Schlüssel, Anmeldedaten oder andere wichtige Daten an einen Server senden. Sie können HTTP oder sogar HTTPS verwenden und die Verbindung als einigermaßen sicher betrachten. Aber früher oder später stellt man fest, dass die eigentliche Schwachstelle gar nicht in der Verbindung liegt, sondern im Code selbst.

In MQL5 gibt es Momente, in denen Sie einfach keine andere Wahl haben, als sensible Werte direkt in den EA einzubetten. Algorithmus-Parameter, private Schlüssel, Passwörter – all das kann in die Datei kompiliert werden. Und auch wenn der Code kompiliert und verschleiert ist, kann jemand mit genügend Geschick diese Werte extrahieren. Dies führt zu einem erheblichen Sicherheitsproblem: Ihr Transport mag verschlüsselt sein, doch der Expert Advisor selbst bleibt ein potenzielles Leck, da er intern Geheimnisse speichern muss. Gleichzeitig ist die Verwendung von DLLs für die Verschlüsselung für Market-Produkte verboten. MQL5 bietet standardmäßig kein RSA, und leichtgewichtige Tricks einer „Verschlüsselung“ wie XOR bieten keinen echten Schutz. Viele Entwickler stecken an diesem Punkt fest: Sie benötigen eine Möglichkeit, die Kommunikation zu sichern und ihre Schlüssel zu schützen, aber die verfügbaren Tools sind entweder eingeschränkt oder einfach nicht stark genug.

Der vorliegende Artikel zielt darauf ab, diese Einschränkungen zu beseitigen. Es wird erklärt, wie Sie sicherstellen, dass Ihr Expert Advisor (EA) keine kritischen Daten mehr intern speichert, sondern sie stattdessen sicher von einem vertrauenswürdigen Server in verschlüsselter Form abruft. Sie werden auch lernen, wie man eine geeignete hybride Verschlüsselung implementiert – unter Verwendung von RSA für den Austausch von Schlüsseln und AES für schnelle Nachrichtenübermittlung – vollständig in MQL5, ohne auf DLLs oder externe Bibliotheken angewiesen zu sein.


Einführung in RSA

Der Name „RSA“ stammt von den Initialen von Ron Rivest, Adi Shamir und Leonard Adleman, die den Algorithmus 1977 während ihrer Arbeit am MIT vorstellten. Ihre Arbeit war die erste praktische Umsetzung eines Kryptosystems mit öffentlichem Schlüssel, das auf mathematischen Einwegfunktionen basierte und aus einer theoretischen Idee eine verwendbare Technologie machte. Obwohl das Konzept der Public-Key-Kryptografie bereits 1976 von Diffie und Hellman vorgeschlagen wurde, war es die RSA-Konstruktion, die einen konkreten Mechanismus für sichere Verschlüsselung und digitale Signaturen lieferte.

Im Kern beruht RSA auf der mathematischen Schwierigkeit, große zusammengesetzte Zahlen zu faktorisieren – ein Problem, das für ausreichend große Schlüsselgrößen rechnerisch unlösbar bleibt. Diese Eigenschaft macht RSA zu einer grundlegenden Technologie in der modernen Kryptografie. Seit Jahrzehnten wird es zur Sicherung des Internetverkehrs, zur Authentifizierung digitaler Dokumente, zum Austausch von Schlüsseln und zum Schutz sensibler Daten über unzählige Plattformen und Protokolle hinweg eingesetzt.

Heute ist RSA nach wie vor einer der am weitesten verbreiteten asymmetrischen Verschlüsselungsalgorithmen und bildet einen wichtigen Bestandteil von Standards wie SSL/TLS, PGP, SSH und vielen sicheren Kommunikationssystemen. Trotz des Aufkommens der Kryptographie mit elliptischen Kurven (ECC) und anderer moderner Alternativen wird RSA weiterhin wegen seiner Einfachheit, Robustheit und seines seit Langem etablierten Sicherheitsmodells geschätzt.



Die Mathematik dahinter 

RSA ist ein einfacher und eleganter Algorithmus:

So funktioniert er: Sie verschlüsseln Ihre Nachricht mit einem öffentlichen Schlüssel, und nur der passende private Schlüssel kann sie entschlüsseln und die ursprüngliche Nachricht wiederherstellen. Der Kern von RSA basiert auf der nachstehenden Formel. Wenn Sie sich fragen, wer sie entdeckt hat, so geht der Verdienst an den brillanten Mathematiker Leonhard Euler. Das Euler'sche Theorem (auch bekannt als Fermat-Euler-Theorem oder Euler'sche Phi-Funktion) ist die Grundlage des RSA-Systems. Mehr darüber können Sie in diesem Artikel lesen.

Hauptformel

Die Bedeutung dieser Buchstaben ist folgende:

  • m: ist Ihre Nachricht (nur als Zahl geschrieben).
  • und n: bilden zusammen den öffentlichen Schlüssel.
  • d: ist der private Schlüssel.
  • n: wird als Modulo bezeichnet.

Schauen wir uns die Hauptgleichung genauer an. Selbst mit begrenztem mathematischem Hintergrundwissen ist es möglich zu erkennen, dass bestimmte mathematische Operationen auf die Nachricht (m) angewendet werden und dass das Ergebnis der Operation die ursprüngliche Nachricht wiederherstellt. Dies ermöglicht die Aufteilung der Gleichung in zwei asymmetrische Teile unter Verwendung der distributiven Eigenschaft von Modulo-Operationen. Die Nachricht m kann verschlüsselt werden, indem sie mit der Potenz von e modulo n verschlüsselt wird, was einen Geheimtext ergibt, Z (die verschlüsselte Nachricht).

Verschlüsselungsformel

Und die verschlüsselte Nachricht Z kann mit dem privaten Schlüssel entschlüsselt werden d

Entschlüsselungsformel

Wir wollen dieses Konzept anhand eines einfachen Zahlenbeispiels mit m = 2, e = 3, d = 3 und n = 15 veranschaulichen. Nach der Verschlüsselungsformel ergibt sich:

Z = (m ^ emod n = 2^3 mod 15 = 8 

Der verschlüsselte Wert kann nun mit dem privaten Schlüssel d = 3 entschlüsselt werden:

m = (Z^d) mod n = 8^3 mod 15 2

Wir können sogar ein kleines MQL5-Programm erstellen, um das obige numerische Beispiel zu implementieren:

//+------------------------------------------------------------------+
//| Simple RSA Numerical Example (for educational purposes only)     |
//+------------------------------------------------------------------+

// Fast modular exponentiation: computes (base^exp) % mod
int ModPow(int base, int exp, int mod)
{
   long result = 1;
   long b = base % mod;

   while(exp > 0)
   {
      if(exp & 1)
         result = (result * b) % mod;

      b = (b * b) % mod;
      exp >>= 1;
   }
   return (int)result;
}

// RSA "encryption": c = m^e % n
int EncryptRSA(int m, int e, int n)
{
   return ModPow(m, e, n);
}

// RSA "decryption": m = c^d % n
int DecryptRSA(int c, int d, int n)
{
   return ModPow(c, d, n);
}

//+------------------------------------------------------------------+
//| Example based on small numbers (not secure!)                     |
//+------------------------------------------------------------------+
void OnStart()
{
   int m = 2;     // message
   int e = 3;     // public exponent
   int d = 3;     // private exponent
   int n = 15;    // modulus

   Print("Original message: ", m);

   int encrypted = EncryptRSA(m, e, n);
   Print("Encrypted: ", encrypted, "   // 2^3 % 15 = 8");

   int decrypted = DecryptRSA(encrypted, d, n);
   Print("Decrypted: ", decrypted, "   // 8^3 % 15 = 2");

   // Verifying the main RSA concept:
   // (m^e)^d % n = m
   int check = ModPow(ModPow(m, e, n), d, n);
   Print("Check (m^e)^d % n = ", check);
}

Tipps und Tricks: 

Zunächst müssen wir den Text in Zahlen umwandeln, bevor wir ihn verschlüsseln können. Hier kommen ASCII oder Unicode ins Spiel – jeder Buchstabe, jedes Leerzeichen oder Symbol wird zu einer Zahl. Nehmen wir zum Beispiel „Hello world!“. Man kann sie als eine große Zahl ausschreiben: dezimal → 22405534230753963835153736737 oder in hex → 0x48656c6c6f20776f726c6421. Der Modulo kann jedoch keine kleine Zahl wie 15 sein; ein solcher Wert ist für echte Nachrichten viel zu klein. Der RSA-Modulo muss größer sein als die numerische Darstellung der Nachricht, sonst funktioniert die Verschlüsselungsmathematik nicht richtig. Die RSA-Verschlüsselung ist rechenintensiv. Die Verschlüsselung großer Zahlen mit großen Exponenten, wie z. B. 65537, erfordert viele Multiplikationen. Ohne Optimierung kann die CPU stark belastet werden.

Was die Sicherheit betrifft, so ist die einfache RSA-Verschlüsselung allein nicht ausreichend. Sie ist anfällig für Replay-Attacken und andere potenzielle Angriffe. Um dem entgegenzuwirken, ist ein Padding (Polsterung) erforderlich. Beim Padding durch PKCS#1 werden beispielsweise Zufallsbytes vor der eigentlichen Nachricht hinzugefügt, um die Sicherheit zu erhöhen, indem vorhersehbare Muster der Chiffretexte verhindert werden.
[0x00][0x02][random bytes][0x00][message]

Durch das Padding werden beim mehrmaligen Senden derselben Nachricht unterschiedliche verschlüsselte Ergebnisse erzielt. Dadurch wird es für einen Angreifer erheblich schwieriger, die Verschlüsselung zu kompromittieren. Ohne ordnungsgemäßes Padding und korrekte Schlüsselverwaltung ist die RSA-Verschlüsselung für mehrere Angriffsklassen anfällig. Im folgenden Schritt demonstrieren wir, wie man in der Praxis ein geeignetes Schlüsselpaar erzeugt.

Schlüsselerzeugung mit OpenSSL:

Vergewissern Sie sich zunächst, dass OpenSSL auf Ihrem Betriebssystem installiert ist. RSA-Schlüssel können mit dem folgenden OpenSSL-Befehl erzeugt werden (Windows-Umgebung):
//bash
openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048
Öffentliche Zertifikate enthalten den öffentlichen Exponenten und den Modulo, während private Schlüssel den privaten Exponenten enthalten. Der folgende Befehl zeigt sowohl die Komponenten des öffentlichen als auch die des privaten Schlüssels an:
//bash
openssl rsa -in private_key.pem -text -noout
openssl rsa -in public_key.pem -pubin -text -noout
Nachdem Sie diese Befehle ausgeführt haben, erhalten Sie eine Ausgabe ähnlich der folgenden Abbildung.

generierte private und öffentliche Schlüssel

So handhaben die Browser sichere Verbindungen mit SSL oder TLS. Zusammenfassend kann gesagt werden, dass RSA eine sichere Kommunikation auch über unsichere Kanäle ermöglicht. Bei der Verwendung ist es wichtig, dass Sie einen ausreichend großen Schlüssel wählen – 2048 Bit oder mehr. Tools wie OpenSSL vereinfachen die Schlüsselerzeugung und -verwaltung. Sie können auch andere Online-Tools für die RSA-Verschlüsselung und -Entschlüsselung oder sogar für die Schlüsselgenerierung finden und diese für die Entwicklung verwenden, ihnen aber niemals für die Produktion vertrauen. Obwohl Windows die RSA-Funktionalität über die CryptoAPI bereitstellt, führt der Aufruf externer DLLs zu zusätzlicher Komplexität und potenziellen Sicherheitsrisiken, insbesondere für Expert Advisors (EAs). Wenn Kunden Ihren EA verwenden und sehen, dass er DLL-Zugriff erfordert, zögern sie möglicherweise oder verlieren sogar das Vertrauen in Ihr Produkt.


Algorithmusentwurf (Schritt für Schritt)

Der Verschlüsselungsprozess besteht aus mehreren Schritten, und um ihn leichter nachvollziehen zu können, wurde die Erklärung des Codes in drei Teile unterteilt: Teil 1 behandelt die Kernkonzepte und die Initialisierung der Klassenanforderungen. In Teil 2 werden die notwendigen arithmetischen Funktionen für große ganze Zahlen vorgestellt. In Teil 3 wird die endgültige Implementierung des RSA-Verschlüsselungsverfahrens vorgestellt.

      Teil 1

      Erstellen der RSA-Klasse:

          Die Hauptklasse MQL5_RSA kapselt den gesamten Arbeitsablauf der Verschlüsselung und enthält Funktionen für Datenformatkonvertierungen, wie Base64 und HexToByte. Das implementiert die Arithmetik großer Zahlen, das Padding mit PKCS#1 v1.5, um Zufälligkeit in die Nachricht einzubringen, und einen optionalen Debug-Modus zur Verfolgung von Operationen.
            Wir beginnen mit der Definition einer RSA-Klasse, die den Modulo, den Exponenten und die Hilfsmethoden speichert. Außerdem wird der Zufallszahlengenerator einmal initialisiert, was wichtig ist, da PKCS#1 v1.5 Zufallsbytes ungleich Null erfordert.
            public class MQL5_RSA
            {
            private:
               uchar modulus[];  //will store the RSA modulus (n) as a big-endian byte array.
               int   exponent;   // holds the public exponent (usually 65537).
               bool  debugMode;  //toggles console logging.
            
            public:
               MQL5_RSA(bool debug=false)
               {
                  debugMode = debug;
                  MathSrand((int)TimeLocal());   // seed RNG once
               }
            };
            Initialisierung:

              Wir laden den Modulo (öffentlicher Schlüssel) als Hex-String, konvertieren ihn in Bytes, normalisieren ihn und speichern den öffentlichen Exponenten (normalerweise 65537).

              void Init(string modulusHex, int e)
              {
                 ArrayResize(modulus, 0);  // clear previous values
              
                 if(debugMode)
                    PrintFormat("Init: modulus hex len=%d, e=%d", StringLen(modulusHex), e);
              
                 HexToBytes(modulusHex, modulus);  // convert hex → bytes
                 Normalize(modulus);               // remove leading zeros (important!)
                 exponent = e;
              
                 if(debugMode)
                    PrintFormat("Init completed: modulus bytes=%d", ArraySize(modulus));
              }
              

              Korrigieren und Schreiben von HexToBytes():

              Diese Funktion ist von entscheidender Bedeutung, da der ursprüngliche MetaQuotes-Code einen Fehler aufwies; er fügt Zeichencodes an, keine Hexpaare. Wir schreiben eine korrekte und geforderte Version:

              void HexToBytes(string hex, uchar &out[])
              {
                 string clean = "";
                 int L = StringLen(hex);
              
                 // keep only valid hex characters
                 for(int i = 0; i < L; i++)
                 {
                    ushort c = StringGetCharacter(hex, i);
                    if((c >= '0' && c <= '9') ||
                       (c >= 'A' && c <= 'F') ||
                       (c >= 'a' && c <= 'f'))
                       clean += StringSubstr(hex, i, 1);
                 }
              
                 // ensure even number of hex chars (pad if necessary)
                 if(StringLen(clean) % 2 == 1)
                    clean = "0" + clean;
              
                 int n = StringLen(clean) / 2;
                 ArrayResize(out, n);
              
                 for(int i = 0; i < n; i++)
                 {
                    ushort c1 = StringGetCharacter(clean, i*2);
                    ushort c2 = StringGetCharacter(clean, i*2+1);
              
                    int high = HexNibble(c1);   // convert 1 hex char -> 0..15
                    int low  = HexNibble(c2);
              
                    out[i] = (uchar)((high << 4) | low); // combine two nibbles → byte
                 }
              }
              

              Normalisierung großer Ganzzahlen:

              RSA-Zahlen sind Big-Endian-Arrays. Das bedeutet, dass Index 0 das höchstwertige Byte ist. Wir wollen niemals führende Null-Bytes, also schneiden wir sie ab:

              void Normalize(uchar &a[])
              {
                 int size = ArraySize(a);
                 int leading = 0;
              
                 // count leading zero bytes
                 while(leading < size && a[leading] == 0)
                    leading++;
              
                 if(leading == 0)
                    return;  // already normalized
              
                 int newSize = size - leading;
                 if(newSize <= 0)
                 {
                    ArrayResize(a, 0);
                    return;
                 }
              
                 uchar temp[];
                 ArrayResize(temp, newSize);
              
                 // copy only the non-zero part
                 ArrayCopy(temp, a, 0, leading, newSize);
              
                 ArrayResize(a, newSize);
                 ArrayCopy(a, temp);
              }
              

              An dieser Stelle bietet die RSA-Klasse die wesentlichen Kernfunktionen. Die Klasse kann den Debug-Status speichern, Modulo akzeptieren und normalisieren, hexadezimale Werte korrekt in Bytes umwandeln, führende Nullen entfernen und den Exponentenwert speichern. Diese Grundlage ist unerlässlich, bevor Sie arithmetische Operationen mit großen Zahlen (Big-Integer) wie Subtraktion, Vergleich und Multiplikation implementieren, gefolgt von modularer Arithmetik und dem Padding durch PKCS#1. Mit diesen Funktionen kann der RSA-Verschlüsselungsprozess ausgeführt werden.

              Teil 2

              Aufbau der Big-Integer-Maschine: Das Einmaleins der RSA

              Als Nächstes bauen wir das für RSA erforderliche Big-Integer-Arithmetiksystem auf. MQL5 bietet keinen nativen Big-Integer-Typ, sodass RSA große Ganzzahlen mit Arrays von Bytes simulieren muss. In diesem Abschnitt wird das mathematische Grundgerüst entwickelt, das das Padding von PKCS#1 und modulare Potenzierung unterstützt. Die folgenden Funktionen werden implementiert:

              1. Compare();
              2. SubtractInPlace();
              3. LeftShiftBytes();
              4. Multiplizieren();
              5. Mod();
              6. MulMod();

              Zusammen emulieren diese Funktionen echte Arithmetikoperationen mit großen Ganzzahlen, den „big-integer“

              1.Compare() – Bestimmt, welche große ganze Zahl größer ist:

              Bevor man Subtraktions-, Divisions- oder Modulo-Operationen durchführen kann, muss man in der Lage sein, zwei Zahlen zu vergleichen.
              a > b
                or
              a < b
                or
              a == b
              int Compare(const uchar &a_in[], const uchar &b_in[])
              {
                 // copy to avoid modifying original arrays
                 uchar a[];
                 ArrayCopy(a, a_in);
                 uchar b[];
                 ArrayCopy(b, b_in);
              
                 Normalize(a);
                 Normalize(b);
              
                 int na = ArraySize(a), nb = ArraySize(b);
              
                 // first compare lengths
                 if(na > nb) return 1;
                 if(na < nb) return -1;
              
                 // lengths equal → compare byte by byte
                 for(int i=0; i<na; i++)
                 {
                    if(a[i] > b[i]) return 1;
                    if(a[i] < b[i]) return -1;
                 }
              
                 return 0; // equal
              }
              

              2. SubtractInPlace() – Big-Endian-Subtraktion

              Diese Funktion führt a = a – b aus. Die Operation wird byteweise durchgeführt, beginnend von rechts, da das letzte Byte den niederwertigsten Teil der Zahl darstellt. Diese Funktion wird in Mod() während der Reduktionsphase der „langen“ Division ausgiebig verwendet und erscheint auch in den Zwischenschritten der Multiplikation.

              void SubtractInPlace(uchar &a[], const uchar &b[])
              {
                 // assume a >= b (Compare() must ensure this)
                 int na = ArraySize(a);
                 int nb = ArraySize(b);
                 int borrow = 0;
              
                 for(int i = 0; i < na; i++)
                 {
                    int ai = (int)a[na - 1 - i];               // rightmost byte of a
                    int bi = (i < nb) ? (int)b[nb - 1 - i] : 0; // rightmost byte of b
              
                    int diff = ai - bi - borrow;
              
                    if(diff < 0)
                    {
                       diff += 256;  // wrap around as unsigned byte
                       borrow = 1;
                    }
                    else
                    {
                       borrow = 0;
                    }
              
                    a[na - 1 - i] = (uchar)diff;
                 }
              
                 Normalize(a); // remove leading zeros
              }
              

              3. LeftShiftBytes() – Multiplizieren mit 256ⁿ:

              Bei „langen“ Divisionsoperationen ist es oft notwendig, eine Zahl nach links zu verschieben, um sie mit Potenzen von 256 zu multiplizieren (d. h. Verschiebung um ganze Bytes).
              void LeftShiftBytes(const uchar &in[], int shiftBytes, uchar &out[])
              {
                 int n = ArraySize(in);
                 if(n == 0 || shiftBytes == 0)
                 {
                    ArrayCopy(out, in);
                    return;
                 }
                 ArrayResize(out, n + shiftBytes);
              
                 // copy original bytes
                 for(int i=0; i<n; i++)
                    out[i] = in[i];
                 // append zeros on the right (least significant side)
                 for(int i=n; i<n+shiftBytes; i++)
                    out[i] = 0;
              }
              Bei der Berechnung des Moduls kann der Divisor verschoben werden, bis er mit dem Dividend übereinstimmt.

              Dies ist der Kern des Algorithmus der „lange“ Division.

              4. Multiply() – Schulbuch Multiplikation:

              Die Standard-Multiplikationsfunktion wird nun mit der klassischen „Schulbuch-Methode“ im Folgenden implementiert:
              void Multiply(const uchar &a[], const uchar &b[], uchar &result[])
              {
                 int na = ArraySize(a);
                 int nb = ArraySize(b);
              
                 if(na==0 || nb==0)
                 {
                    ArrayResize(result, 0);
                    return;
                 }
              
                 int nRes = na + nb;
                 int temp[];
                 ArrayResize(temp, nRes);
                 ArrayInitialize(temp, 0);  // accumulator array (int for safety)
              
                 // classic schoolbook multiplication
                 for(int i = na - 1; i >= 0; i--)
                 {
                    int carry = 0;
              
                    for(int j = nb - 1; j >= 0; j--)
                    {
                       int prod = (int)a[i] * (int)b[j] + temp[i + j + 1] + carry;
                       temp[i + j + 1] = prod % 256;
                       carry = prod / 256;
                    }
              
                    temp[i] += carry;
                 }
              
                 ArrayResize(result, nRes);
                 for(int i=0; i<nRes; i++)
                    result[i] = (uchar)temp[i];
              
                 Normalize(result);
              }
              

              Diese Multiplikation ist der langsamste Teil der reinen MQL5-RSA-Implementierung, aber sie ist für die Funktionen MulMod() und ModExp() notwendig. Diese Funktionen werden später im Detail besprochen.

              5. Mod(), Big-Integer Modulo mit „langer“ Division:

              Dies ist die wichtigste ganzzahlige Routine, die wir in einer vereinfachten „langen“ Division implementieren. Wir beginnen m it dem Rest als leeres Array und verschieben dann für jedes Byte der Dividende den Rest nach links, hängen das nächste Byte an und subtrahieren den Modulo, wobei der Rest ≥ Modulo ist. Der endgültige Rest wäre das gewünschte Ergebnis.

              bool Mod(const uchar &a_in[], const uchar &m_in[], uchar &result[])
              {
                 if(ArraySize(m_in) == 0) return false;
              
                 uchar dividend[]; ArrayCopy(dividend, a_in); Normalize(dividend);
                 uchar modv[];     ArrayCopy(modv, m_in);   Normalize(modv);
              
                 // if dividend < modulus → done
                 if(Compare(dividend, modv) < 0)
                 {
                    ArrayCopy(result, dividend);
                    return true;
                 }
              
                 int m = ArraySize(dividend);
                 uchar rem[];
                 ArrayResize(rem, 0);
              
                 for(int i = 0; i < m; i++)
                 {
                    // shift remainder left by 1 byte
                    int rlen = ArraySize(rem);
                    ArrayResize(rem, rlen + 1);
                    rem[rlen] = dividend[i];
                    Normalize(rem);
              
                    // subtract modulus while remainder >= modulus
                    while(Compare(rem, modv) >= 0)
                    {
                       SubtractInPlace(rem, modv);
                    }
                 }
              
                 Normalize(rem);
                 ArrayCopy(result, rem);
                 return true;
              }
              

              6. MulMod() – Multiplikation, gefolgt von Mod:

              Das einfach zusammengefügt:

              Multiply(a, b) → temp
              Mod(temp, m) → out

              bool MulMod(const uchar &a[], const uchar &b[], const uchar &m[], uchar &out[])
              {
                 uchar product[];
                 Multiply(a, b, product);
                 return Mod(product, m, out);
              }

              Teil 3

              Wir haben nun alle notwendigen Werkzeuge für die RSA-Arithmetik. Zunächst muss die Sicherheit der Nachricht durch das Padding mit PKCS#1 v1.5 vor der Verschlüsselung erhöht werden. Anschließend wird die Funktion ModExp() implementiert, um Folgendes zu berechnen: Ergebnis = base^exp mod n. Dies ist die Kernoperation von RSA, die den Chiffretext erzeugt. Schließlich kann die Ausgabe mit der Hilfsfunktion Base64Encode() standardisiert werden, sodass der Chiffretext bequem angezeigt werden kann.

              Modulare Potenzierung (ModExp) – Das Herzstück von RSA:

                Die Funktion ModExp() berechnet effizient das Ergebnis = base^exp mod n für große Exponenten durch wiederholtes Quadrieren und modulare Multiplikation. Es stützt sich auf MulMod() (multiplizieren und dann reduzieren), damit alle Zwischenwerte klein genug bleiben, um sie zu verarbeiten.

                bool ModExp(const uchar &base[], int exp, const uchar &modn[], uchar &result[])
                {
                   if(debugMode)
                      PrintFormat("ModExp: exp=%d", exp);
                
                   // Work on a copy to avoid mutating caller arrays
                   uchar baseCopy[];
                   ArrayCopy(baseCopy, base);
                   Normalize(baseCopy);
                
                   // Reduce base modulo modn first: base = base % modn
                   if(Compare(baseCopy, modn) >= 0)
                   {
                      if(!Mod(baseCopy, modn, baseCopy))
                         return false; // mod failed
                   }
                
                   // Initialize result = 1 (big-int representation)
                   uchar res[];
                   ArrayResize(res, 1);
                   res[0] = 1;
                   Normalize(res);
                
                   // basePow holds current power of base (base^(2^i))
                   uchar basePow[];
                   ArrayCopy(basePow, baseCopy);
                
                   int e = exp; // local copy so we can shift it
                
                   // Square-and-multiply loop
                   while(e > 0)
                   {
                      // If current LSB is 1 → multiply into result
                      if((e & 1) == 1)
                      {
                         uchar tmp[];
                         if(!MulMod(res, basePow, modn, tmp))
                            return false; // MulMod failed
                         ArrayCopy(res, tmp); // res = (res * basePow) % modn
                      }
                
                      // Move to next bit
                      e >>= 1;
                
                      // If still bits left, square basePow: basePow = (basePow * basePow) % modn
                      if(e > 0)
                      {
                         uchar tmp2[];
                         if(!MulMod(basePow, basePow, modn, tmp2))
                            return false; // MulMod failed
                         ArrayCopy(basePow, tmp2);
                      }
                   }
                
                   Normalize(res);
                   ArrayCopy(result, res); // return result via out param
                   return true;
                }
                

                Padding (PKCS#1 v1.5) und Verschlüsselung (EncryptPKCS1v15 ):

                Zuerst sollten wir die „aufgepolsterte“ Nachricht EM = 0x00 || 0x02 || PS || 0x00 || M erstellen, wobei PS ein Zufallsbyte ungleich Null ist, und sicherstellen, dass die Nachrichtenlänge kleiner als k – 11 ist (k = Modulo-Länge in Bytes), da Overhead-Bytes für die endgültige Nachrichtenlänge mitgezählt werden.

                bool EncryptPKCS1v15(uchar &plain[], uchar &cipher[])
                {
                   int k = ArraySize(modulus);      // modulus size (bytes)
                   int mlen = ArraySize(plain);     // message length (bytes)
                
                   if(debugMode)
                      PrintFormat("Encrypt: key bytes=%d, plain=%d, max=%d", k, mlen, k-11);
                
                   // PKCS#1 v1.5 requires at least 11 bytes of overhead
                   if(mlen > k - 11)
                   {
                      if(debugMode) Print("Error: data too large for key");
                      return false;
                   }
                
                   // Build the encoded message EM (k bytes)
                   uchar em[];
                   ArrayResize(em, k);
                   ArrayInitialize(em, 0);    // default everything to 0
                
                   em[0] = 0x00;              // leading zero by spec
                   em[1] = 0x02;              // block type 2 (encryption)
                
                   int psLen = k - mlen - 3;  // length of padding string PS
                
                   // Fill PS with non-zero random bytes
                   for(int i=0; i<psLen; i++)
                   {
                      uchar b = 0;
                      // loop until rand byte is non-zero (spec requirement)
                      do
                      {
                         b = (uchar)(MathRand() % 256);
                      }
                      while(b == 0);
                
                      em[2 + i] = b;
                   }
                
                   // Separator before message
                   em[2 + psLen] = 0x00;
                
                   // Copy message into EM at the right position
                   ArrayCopy(em, plain, 3 + psLen, 0, mlen);
                
                   if(debugMode) Print("EM constructed");
                
                   // Modular exponentiation: C = EM^e mod n
                   uchar cBig[];
                   if(!ModExp(em, exponent, modulus, cBig))
                   {
                      if(debugMode) Print("ModExp failed");
                      return false;
                   }
                
                   // Ensure ciphertext byte array has exact length k
                   int clen = ArraySize(cBig);
                   ArrayResize(cipher, k);
                   if(clen < k)
                   {
                      // left-pad with zeros
                      ArrayInitialize(cipher, 0);
                      ArrayCopy(cipher, cBig, k - clen, 0, clen);
                   }
                   else
                   {
                      ArrayCopy(cipher, cBig); // already k bytes or longer (shouldn't be longer normally)
                   }
                
                   if(debugMode) Print("Encryption finished");
                   return true;
                }
                

                Die Paddings-Zeichenfolge (PS) muss aus Zufallsbytes bestehen, die nicht Null sind. In dieser Implementierung wird MathRand() verwendet, das im Konstruktor eingesetzt wird. Dies ist zwar eine Verbesserung gegenüber einem unbestückten Zufallszahlengenerator, aber kryptografisch nicht sicher. Produktionssysteme sollten einen kryptographisch sicheren RNG verwenden. PKCS#1 v1.5 wird weitgehend unterstützt, hat aber bekannte Schwachstellen, wie z. B. Padding-Orakel-Angriffe. Für neue Systeme wird RSAES-OAEP bevorzugt. Die Polsterfunktion gibt false zurück, wenn die Nachricht zu lang für den gewählten Schlüssel ist.

                Base64 Output Helper (Base64Encode):

                Chiffretexte sind binäre Daten. Für die Protokollierung, den HTTP-Transport oder die textbasierte Speicherung bietet Base64 eine praktische und standardisierte Darstellung.

                string Base64Encode(uchar &data[])
                {
                   string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
                   int len = ArraySize(data);
                   string out = "";
                
                   // process 3 bytes => 4 base64 chars
                   for(int i = 0; i < len; i += 3)
                   {
                      int b0 = data[i];
                      int b1 = (i + 1 < len) ? data[i + 1] : 0;
                      int b2 = (i + 2 < len) ? data[i + 2] : 0;
                      int val = (b0 << 16) | (b1 << 8) | b2;
                
                      // append 4 chars, using '=' padding for missing bytes
                      out += StringSubstr(chars, (val >> 18) & 0x3F, 1);
                      out += StringSubstr(chars, (val >> 12) & 0x3F, 1);
                      out += (i + 1 < len) ? StringSubstr(chars, (val >> 6) & 0x3F, 1) : "=";
                      out += (i + 2 < len) ? StringSubstr(chars, val & 0x3F, 1) : "=";
                   }
                
                   return out;
                }
                


                Alles zusammenfügen 

                Um die Lesbarkeit zu erhalten, ist der vollständige Quellcode der RSA-Klasse hier nicht enthalten. Die vollständige Implementierung befindet sich in der Bibliothek RSA.mqh, die am Ende der Seite zum Download bereitsteht. Die Bibliothek enthält alle in diesem Artikel besprochenen Komponenten sowie ein kommentiertes Beispiel, das die Verwendung der Klasse demonstriert. Es kann für eine schnelle Integration verwendet oder erweitert und verändert werden, sobald die interne Funktionsweise verstanden wurde. Falls Fehler auftreten oder ein Teil der Umsetzung geklärt werden muss, sind die Leser aufgefordert, unten einen Kommentar zu hinterlassen.

                Verwendungsbeispiele

                Rohe RSA-Implementierung:

                Dieses einfache Beispiel zeigt, wie die RSA-Verschlüsselung implementiert werden könnte. Erstellen wir zunächst eine Instanz der RSA-Klasse und bereiten dann unsere Nachricht (die Daten, die wir verschlüsseln möchten) als uchar[] vor, wobei jedes Element ein Byte ist. Schließlich rufen wir EncryptPKCS1v15 (plain, cipher) auf, um die Verschlüsselung durchzuführen. Das Ergebnis ist ein char-Array und wird in cipherData[] gespeichert:

                void OnStart()
                {
                   MQL5_RSA rsa;
                   string modulusHex = "A1B2C3D4E5F6..."; // Your modulus in hex
                   int exponent = 65537; // exponent value
                
                   rsa.Init(modulusHex, exponent);
                
                   string plainText = "Hello RSA!"; //Data that you want to be encrypted.
                   uchar plainData[];
                   StringToCharArray(plainText, plainData, 0, StringLen(plainText));
                
                   uchar cipherData[];
                   if(rsa.EncryptPKCS1v15(plainData, cipherData))
                   {
                      string base64Cipher = rsa.Base64Encode(cipherData);
                      Print("Encrypted: ", base64Cipher);
                   }
                   else
                   {
                      Print("Encryption failed!");
                   }
                }

                Verwendung von RSA + AES in einem echten Projekt:

                AES-RSA-Hybridverschlüsselung

                In realen kryptografischen Systemen – einschließlich sicherer Handelsplattformen, Webservern und verteilter APIs – wird RSA selten zur direkten Verschlüsselung großer Daten verwendet. Die RSA-Verschlüsselung ist rechenintensiv und durch die Schlüsselgröße begrenzt, sodass sie sich nicht für große Nutzdaten eignet. Um sowohl hohe Leistung als auch starke Sicherheit zu erreichen, wird RSA in der Regel mit AES in einem hybriden Verschlüsselungsmodell kombiniert, das die Stärken beider Algorithmen nutzt.

                Der Arbeitsablauf bei der hybriden Verschlüsselung folgt im Allgemeinen diesen Schritten:

                1. Wir erzeugen einen zufälligen AES-Sitzungsschlüssel (z. B. 128 oder 256 Bit),
                2. verschlüsseln diesen AES-Schlüssel mit dem öffentlichen RSA-Schlüssel – nur der Besitzer des entsprechenden privaten Schlüssels kann sie später entschlüsseln –,
                3. verschlüsseln die eigentlichen Nutzdaten mit AES – einer schnellen, symmetrischen Verschlüsselung, die sich ideal für große Nachrichten oder Dateien eignet – und
                4. übertragen beide Komponenten gemeinsam:
                  • die AES-verschlüsselten Daten (schnell und kompakt) und
                  • den RSA-verschlüsselte AES-Schlüssel (klein, aber sicher).
                Dieser Ansatz kombiniert die Vorteile beider Algorithmen. RSA ermöglicht einen sicheren Schlüsselaustausch zwischen zwei Parteien ohne vorheriges gemeinsames Geheimnis, während AES eine Hochgeschwindigkeitsverschlüsselung für die eigentlichen Daten bietet. In diesem Modell fungiert RSA als Schlüsselschutz und AES als Datenschutz. Zusammen bilden sie die Grundlage für moderne, sichere Kommunikationsprotokolle wie SSL/TLS, HTTPS und VPN-Tunnel.

                Im Zusammenhang mit MQL5 ermöglicht diese Strategie Entwicklern, die Kommunikation zwischen Expert Advisors, Indikatoren und externen Servern zu sichern, auch wenn sie Standard-HTTP- oder Socket-Verbindungen verwenden. Die in diesem Artikel implementierte RSA-Klasse kann zur Verschlüsselung des AES-Schlüssels verwendet werden, während die integrierten MQL5-Funktionen CryptEncode() und CryptDecode() die AES-Verschlüsselung und -Entschlüsselung der eigentlichen Nachricht übernehmen.

                Dies ermöglicht eine vollständig eigenständige Sicherheitsschicht innerhalb von MetaTrader 5, wodurch eine leichtgewichtige Version von „HTTPS über HTTP“ entsteht. Sie kann zum Schutz sensibler Daten wie Handelsbefehle, Authentifizierungsdaten oder Konfigurationsnachrichten verwendet werden, ohne auf externe Verschlüsselungsbibliotheken oder DLLs angewiesen zu sein.

                Schritt 1 – der Server generiert den öffentlichen RSA-Schlüssel:

                Wir verwenden Python, Java, OpenSSL oder eine beliebige Backend-Umgebung, um den Modulo (Hexadezimalzeichenfolge), den Exponenten (normalerweise 65537) und den privaten Schlüssel (der auf Ihrer Serverseite gespeichert wird) zu erzeugen.

                Die EA wird nur die öffentlichen Werte verwenden. Geben Sie den privaten Schlüssel nicht an die Client-Seite weiter.

                modulusHex = "A1B2C3…";
                exponent   = 65537;

                Schritt 2 – der EA erstellt eine RSA-Instanz und lädt den Schlüssel:

                #include <RSA.mqh>  
                
                MQL5_RSA rsa;  
                rsa.Init(modulusHex, exponent);

                Schritt 3 – der EA bereitet eine Anforderungsnachricht vor:

                Ein Beispiel für eine Nutzlast kann eine Login-Anfrage sein, die Folgendes enthält: EA-ID, Kontonummer und Zeitstempel. Erstellen Sie einen geeigneten JSON-String für Ihre Anfrage. Sie können eine beliebige Bibliothek eines Drittanbieters verwenden, um den JSON-String zu erstellen. Der folgende Ausschnitt zeigt, wie die erwartete JSON-Zeichenfolge aussehen sollte:

                string json =
                   "{\"cmd\":\"login\","
                   "\"account\":" + IntegerToString(AccountInfoInteger(ACCOUNT_LOGIN)) + ","
                   "\"ts\":" + IntegerToString((int)TimeCurrent()) + ","
                   "}"; 
                uchar plain[];
                StringToCharArray(json, plain, 0, StringLen(json));

                Schritt 4 – der EA verschlüsselt den AES-Sitzungsschlüssel mit RSA:

                Der AES-Schlüssel, der für den Rest der Sitzung verwendet wird:

                uchar aesKey[];
                GenerateAESKey(aesKey);   // 16 random bytes (AES-128)
                uchar encryptedAes[];
                rsa.EncryptPKCS1v15(aesKey, encryptedAes);
                string encryptedAesBase64 = rsa.Base64Encode(encryptedAes);
                Schritt 5 – der EA verschlüsselt die eigentlichen Daten mit AES:
                uchar encryptedPayload[];
                CryptEncode(CRYPT_AES128, plain, aesKey, encryptedPayload);
                string payloadBase64 = CryptBase64Encode(encryptedPayload);
                

                Schritt 6 – der EA sendet alles an den Server:

                string body =
                   "{\"key\":\"" + encryptedAesBase64 + "\","
                   + "\"data\":\"" + payloadBase64 + "\"}";
                
                string result;
                char headers[];
                int status = WebRequest("POST", url, headers, 5000, body, result);

                Schritt 7 – serverseitige Entschlüsselung (konzeptionell):

                Führen Sie auf dem Server zunächst eine base64-Dekodierung für den RSA-verschlüsselten AES-Schlüssel durch und entschlüsseln Sie dann den AES-Schlüssel mit dem privaten RSA-Schlüssel. Sobald der AES-Schlüssel wiederhergestellt ist, kann er zur Entschlüsselung der Nutzdaten verwendet werden. Da der AES-Schlüssel für jede Sitzung nach dem Zufallsprinzip erzeugt wird, bleibt jede Nachricht eindeutig und sicher, auch wenn ein Angreifer den Datenverkehr abfängt. Mit diesem Mechanismus wird effektiv eine sichere Sitzung aufgebaut, die einem vereinfachten HTTPS-Tunnel ähnelt.

                Schritt 8 – Die Client-Seite entschlüsselt die Server-Antwort:

                uchar responseCipher[];
                CryptBase64Decode(result, responseCipher);
                uchar responsePlain[];
                CryptDecode(CRYPT_AES128, responseCipher, aesKey, responsePlain);
                
                string serverReply = CharArrayToString(responsePlain);
                Print("Server replied: ", serverReply);
                
                Wenn Sie diese Schritte befolgen, kann die Kryptographie direkt in die MetaTrader 5-Umgebung implementiert werden, was zu einer zuverlässigen und vollständig portablen Lösung für Endnutzer führt.


                Schlussfolgerung

                In diesem Artikel wurde eine vollständige und funktionale RSA-Verschlüsselungsbibliothek vorgestellt, die komplett in MQL5 geschrieben wurde. Sie zeigt, dass selbst komplexe mathematische Algorithmen direkt in der MetaTrader 5-Umgebung ohne externe Abhängigkeiten implementiert werden können. Durch diese Arbeit haben wir gezeigt, dass MQL5 nicht auf die Automatisierung des Handels beschränkt ist, sondern auch fortgeschrittene Rechenaufgaben wie modulare Arithmetik und Kryptographie mit öffentlichen Schlüsseln durchführen kann.

                Mit dieser Grundlage verfügen Entwickler nun über ein praktisches Toolkit zum Aufbau sicherer Messaging-Systeme, zum Schutz von Konfigurationsdateien und zur Unterstützung von sicherem Schlüsselaustausch und verschlüsselter Kommunikation direkt in MetaTrader 5. Dadurch können Expert Advisors und Indikatoren sicher mit Remote-Diensten kommunizieren, Datenquellen authentifizieren und verschlüsselte Handelsbefehle austauschen, ohne sensible Informationen im Netzwerk preiszugeben.

                Die Implementierung unterstreicht auch ein wichtiges Konzept: Kryptographie muss nicht auf externe DLLs oder Bibliotheken von Drittanbietern angewiesen sein, um effektiv zu sein. Wenn Algorithmen nativ in MQL5 implementiert werden, bleibt der Code transparent, portabel und kann vom Entwickler vollständig kontrolliert werden. Dies gewährleistet sowohl die Überprüfbarkeit als auch die Einhaltung der Sicherheitsrichtlinien von MetaQuotes, was insbesondere bei der Entwicklung von kommerziellen Handelsinstrumenten, die über den Markt vertrieben werden, von Bedeutung ist.

                Für Entwickler, die an einer Erweiterung dieser Arbeit interessiert sind, gibt es mehrere Möglichkeiten. Die RSA-Bibliothek kann mit digitalen Signaturen kombiniert werden, um die Authentizität und Integrität von Nachrichten zu überprüfen, oder mit modernen Auffüllverfahren wie OAEP erweitert werden, um den Widerstand gegen Kryptoanalyse zu erhöhen. Ebenso kann die Integration von AES in authentifizierte Modi wie GCM oder CBC-HMAC sowohl Verschlüsselung als auch Manipulationserkennung in einem einzigen Schritt bieten. Solche Ergänzungen würden die Implementierung näher an moderne Standards wie TLS und PGP heranführen, und zwar vollständig innerhalb der MQL5-Umgebung.

                Aus praktischer Sicht ermutigt dieser Artikel die Entwickler, MQL5 nicht nur als eine Skriptsprache für den Handel zu betrachten, sondern als eine vollwertige Programmierumgebung, in der Sicherheit, Netzwerke und Berechnungen nebeneinander bestehen. Durch die Kombination von mathematischer Präzision und sorgfältigem Design können MQL5-Entwickler sichere, in sich geschlossene Systeme erstellen, die zuverlässig mit externen APIs, Brokern oder Cloud-Diensten kommunizieren können.

                Zusammenfassend kann gesagt werden, dass Kryptographie in MQL5 sowohl möglich als auch leistungsstark ist. Entwickler haben jetzt die Möglichkeit, mit sicheren Kommunikationsmechanismen zu experimentieren, sie zu erweitern und sie direkt in ihre Projekte zu integrieren. In dem Maße, in dem sich das Handelsökosystem hin zu stärker vernetzten und datengesteuerten Architekturen entwickelt, wird die Fähigkeit, Verschlüsselung und Authentifizierung innerhalb des Terminals zu handhaben, immer wertvoller. Auf dieser Grundlage kann jeder Entwickler sinnvolle Schritte unternehmen, um sicherere, intelligentere und widerstandsfähigere Handelssysteme zu entwickeln.

                Autoren und Programmierer


                Referenzen und weiterführende Literatur

                Für Leser, die an den mathematischen Grundlagen und modernen Anwendungen der RSA- und AES-Verschlüsselung interessiert sind, bieten die folgenden Referenzen zuverlässige, ausführliche Erklärungen:

                • Wikipedia – PKCS #1: RSA Cryptography Standard
                  Erläutert den offiziellen RSA-Standard, einschließlich Padding-Schemata wie PKCS#1 v1.5 und OAEP, die für Verschlüsselung und Signaturen verwendet werden.
                  Die offizielle MQL5-Dokumentation für CryptEncode(), CryptDecode() und andere integrierte Funktionen für AES und Hashing.
                • Paar, C. & Pelzl, J. (2024). Understanding Cryptography – A Textbook for Students and Practitioners, Chapter 7: The RSA Cryptosystem. Springer. Link
                • Mollin, R. A. (2023). RSA and Public-Key Cryptography. Routledge. Link
                • National Institute of Standards and Technology. FIPS 197: Advanced Encryption Standard (AES). 2001 (aktualisiert). Link
                • Tuo, Z. (2023). “A Comparative Analysis of AES and RSA Algorithms and Their Integrated Application.” Theoretical and Natural Science, Vol 25, pp. 28-35. Link
                • “RSA-AES Hybrid Encryption: Combining the Strengths of Symmetric & Asymmetric Algorithms.” IJRAR, 2023. Link
                • Ganesh, R., Khan, A. R., et al. (2025). “A Panoramic Survey of the Advanced Encryption Standard (AES)”. International Journal of Information Security. Link
                • Einfach erklärte Funktionsweise der RSA-Nachrichtenverschlüsselung/-entschlüsselung (Englisch)


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

                Beigefügte Dateien |
                RSA.mqh (24.79 KB)
                Die Übertragung der Trading-Signale in einem universalen Expert Advisor. Die Übertragung der Trading-Signale in einem universalen Expert Advisor.
                In diesem Artikel wurden die verschiedenen Möglichkeiten beschrieben, um die Trading-Signale von einem Signalmodul des universalen EAs zum Steuermodul der Positionen und Orders zu übertragen. Es wurden die seriellen und parallelen Interfaces betrachtet.
                Die Grenzen des maschinellen Lernens überwinden (Teil 9): Korrelationsbasierte Lernen von Merkmalen im selbstüberwachten Finanzwesen Die Grenzen des maschinellen Lernens überwinden (Teil 9): Korrelationsbasierte Lernen von Merkmalen im selbstüberwachten Finanzwesen
                Selbstüberwachtes Lernen ist ein leistungsstarkes Paradigma des statistischen Lernens, das nach Überwachungssignalen sucht, die aus den Beobachtungen selbst generiert werden. Mit diesem Ansatz werden schwierige Probleme des unüberwachten Lernens in vertrautere überwachte Probleme umgewandelt. Diese Technologie hat Anwendungen für unser Ziel als Gemeinschaft von algorithmischen Händlern übersehen. Unsere Diskussion zielt daher darauf ab, dem Leser eine leicht verständliche Brücke in das offene Forschungsgebiet des selbstüberwachten Lernens zu schlagen und bietet praktische Anwendungen, die robuste und zuverlässige statistische Modelle der Finanzmärkte ohne Überanpassung an kleine Datensätze liefern.
                Eine alternative Log-datei mit der Verwendung der HTML und CSS Eine alternative Log-datei mit der Verwendung der HTML und CSS
                In diesem Artikel werden wir eine sehr einfache, aber leistungsfähige Bibliothek zur Erstellung der HTML-Dateien schreiben, dabei lernen wir auch, wie man eine ihre Darstellung einstellen kann (nach seinem Geschmack) und sehen wir, wie man es leicht in seinem Expert Advisor oder Skript hinzufügen oder verwenden kann.
                Automatisieren von Handelsstrategien in MQL5 (Teil 45): Inverse Fair Value Gap (IFVG) Automatisieren von Handelsstrategien in MQL5 (Teil 45): Inverse Fair Value Gap (IFVG)
                In diesem Artikel erstellen wir ein System zur Erkennung der inversen Fair-Value-Gaps (IFVG) in MQL5, das Auf-/Abwärts-FVGs auf den letzten Balken mit einer Filterung der Mindestlückengröße identifiziert, ihre Zustände als normal/gemildert/invertiert auf der Grundlage von Preisinteraktionen verfolgt (Abschwächung bei Durchbrüchen auf der Gegenseite, Rücksetzer bei Wiedereintritt, Inversion des Schlusskurses auf der Gegenseite von innen) und Überschneidungen ignoriert, während es die verfolgten FVGs begrenzt.