English Русский 中文 Español Deutsch Português
外部アプリケーションで暗号を使用する

外部アプリケーションで暗号を使用する

MetaTrader 5トレーディング | 23 11月 2020, 09:08
553 0
Andrei Novichkov
Andrei Novichkov

イントロダクション

暗号はMQLプログラムではほとんど使われていません。 普段のトレードで暗号を使う機会はそう多くはありません。 例外は、送信されたデータを盗聴されないように保護したいという偏執的なシグナルコピーで、おそらくそれだけです。 データがターミナルから離れない場合、なぜ暗号化・復号化する必要があるのか、考えにくいでしょう。 さらに言えば、余分なターミナル負荷を発生させる可能性があります。

もしかして、トレードに暗号を使う必要はないのではないでしょうか? しかし、実はあるんです。 例えば、ライセンスについて考えてみましょう。 小さな会社でも、人気のある商品を開発している別の開発者であっても構いません。 この場合、ライセンスの問題が関係しているため、ライセンスの暗号化/記述が必要になります。

ライセンスユーザーデータや編集可能なプロダクトリストを指定することができます。 インジケータまたはEAが開始し、指定されたプロダクトのライセンスの可用性と期限をチェックします。 プログラムはサーバーにリクエストを送り、必要に応じてライセンスを更新したり、新しいライセンスを受信したりします。 最も効率的で安全なルートとは言えないかもしれませんが、この記事ではデモンストレーション目的で使用します。 明らかに、この場合、ライセンスは異なるソフトウェアツール(ターミナル、リモートサーバー、制御モジュール、ロギングモジュール)によって読み書きされます。 これらすべて、異なる人種によって、異なる時間に、異なる言語で書かれる可能性ができます。

この記事の目的は、C#やC++のプログラムで暗号化されたオブジェクトをメタトレーダーターミナルで復号化したり、その逆も可能な暗号化/復号化モードを研究することです。

この記事は、中級者向けと初心者向けの両方を対象とします。

タスクの設定

これについては、すでに紹介したとおりです。 プロダクト-インジケータやEA-のライセンスの作成、暗号化、復号化を必要とする、実際の問題に対する解決策をシミュレーションしてみます。 どのプログラムがライセンスを暗号化し、どのプログラムがライセンスを解読するかは重要ではありません。 例えば、ライセンスは開発者のコンピュータで最初に作成され、営業部門で修正され、トレーダーのコンピュータで復号化されるかもしれません。 このプロセスは、不完全に設定されたアルゴリズムに関連したエラーからインプットされなければなりません。

大きな課題の解決とともに、ライセンスという複雑な問題を考えていきます。 すぐに使えるライセンスではなく、可能な亜種の一つであり、さらに編集して開発する必要があります。

ソースデータ

ターミナルのドキュメントを参照して、操作のソースデータを取得してみましょう。 暗号化/復号化のステップを担当する標準関数は2つあります。

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

暗号化/復号化に使用される具体的な方法は、メソッドの引数で決定されます。 この場合、メソッド引数が持つことができる3つの値に注目します。CRYPT_AES128、CRYPT_AES256、CRYPT_DESです。 3つの値は、異なる鍵長を持つ対称暗号化アルゴリズムを表します。

この記事では、そのうちの一つ、CRYPT_AES128 .だけを使います。 128ビット(16バイト)の鍵を持つ対称ブロック暗号アルゴリズムです。 他のアルゴリズムの使い方も似たようなものです。

AES アルゴリズム(選択されたものだけでなく、別のキー長を持つもの)には、上記の機能では提供されていない重要な設定があります。 これらには、暗号化モードとパディングが含まれます。 この条件については、深く掘り下げることはありません。 そのため、ターミナルは電子コードブック(ECB)暗号化モードを使用し、パディングはゼロに等しい。 MQL5フォーラムで説明されていたので、仲間のトレーダーに感謝したいと思います。 課題は、より解決できるようになりました。

オペレーションオブジェクトの開発

暗号化/復号化をライセンスに適用したものと考えているので、操作オブジェクトはライセンスになります。 ライセンスが適用される各種プロダクトに関する情報を含んだある種の構造であるべきです。 ここでは以下のデータが必要です。

  1. 本プロダクトのライセンス期限
  2. 商品名

付属の最も簡単な方法で適切な構造を作成してみましょう。

#define PRODMAXLENGTH 255

structEA_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();
     }
  };//structEA_user


ここに説明があります。

  • 商品名は固定長の配列として格納されます。
  • 商品名の最大長さはPRODMAXLENGTHまでとなります。
  • この戦略はバイト配列に詰めることができます - オブジェクト全体を暗号化する前に行うことです。

しかし、この構造だけでは十分ではありません。 明らかに、ライセンスにはユーザーの詳細が含まれている必要があります。 すでに説明した構造に情報をインクルードすることもできるが、ユーザーが複数のプロダクトライセンスがある可能性があり、非効率的です。 より合理的な解決策は、ユーザーに別の構造を作成し、そこに必要なプロダクトライセンス数を追加することです。 したがって、ユーザーは、すべてのライセンスプロダクトの許可と制限を含む1つのライセンスを持つことになります。

ユーザを記述する構造体に含まれることができる情報。

  1. 固有のユーザーID 名前を保存することもできますが、暗号化された形でも毎回個人情報を送信するのは好ましくないようです。
  2. プロダクトが利用できるユーザーのアカウントに関する情報です。
  3. ユーザーのライセンス期限。 このフィールドは、無制限のものであっても、既存のすべてのプロダクトの使用を、そのようなユーザーのサービスの時間に制限することができます。
  4. ユーザーターミナルのライセンスプロダクトの数。
#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

ここで、いくつか明らかにしておきます。

  • ユーザーアカウントは固定長の配列に格納されます。 口座番号で表されます。 しかし、必要に応じて、情報はサーバーまたはブローカー名と特定のアカウントのアクティブ化の数で補完することができます。

今のところ、この構造には、ユーザーと関連するプロダクトに関する情報が十分に含まれています。 それぞれが型であり、StructToCharArray関数で処理できるインスタンスです。

さて、構造体データをバイト配列にシリアライズする必要がありますが、さらに暗号化することができます。 以下のように実施します。

  • user_lic構造体のインスタンスを作成して初期化します。
  • バイト配列でシリアライズします。
  • ea_user構造体の1つ以上のインスタンスを作成して初期化します。
  • 同じバイト配列でシリアライズし、サイズを大きくし、ea_countフィールドを調整します。

操作を行うクラスを作成します。

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

暗号化・復号化を可能にする関数と、鍵の長さを確認するための保護関数の2つをクラスに追加しました。 そのコードから、例えばCRYPT_AES128メソッドのキーの長さが16バイトに等しくなければならないことがわかります。 実際には、16バイト以下であってはなりません。 おそらく、さらに何となく正規化されていて、開発者には隠されているのではないでしょうか。 必要なキーの長さを厳密に設定します。

最後に、結果のバイト配列を暗号化してバイナリファイルに保存することも可能です。 このファイルは、一般的なルールに従って、ターミナルのFileフォルダに格納する必要があります。 必要に応じて読み込んで復号化することができます。

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)

どちらの関数も明確で、追加の説明は不要です。 添付のCryptoMQL.zip配列には、2つのスクリプトと、暗号化/復号化を実装したライブラリファイル、および暗号化されたライセンスファイルlic.txtがあります。

C# プロジェクト

簡単なC#プロジェクトを作成して、別のプログラムで復号化と編集のプロセスをシミュレートしてみましょう。 Visual Studio 2017を使用して、.NET Frameworkプラットフォーム用のコンソールアプリケーションを作成します。 System.SecurityとSystem.Security.Cryptography空間の接続を確認します。 

コードの中で次のような問題が発生します。MQLとC#では、時間の形式が異なります。 この問題は、この記事ですでに対処・解決されています。 作者は素晴らしい仕事をしてくれたので、彼のMtConverterクラスをプロジェクトで使うことができます。

ea_userとuser_lic構造体に似たフィールドを持つEaUserとUserLicの2つのクラスを作成します。 この目的は、ターミナルが作成したライセンス(lic.txtファイル)を復号化し、受信したデータを解析し、オブジェクトを修正して再暗号化し、新しいファイルを作成することです。 このタスクは、暗号化/復号化モードを慎重に設定すれば、実装できるはずです。 ここでは、適切なコードのピースがどのように見えるかを説明します。

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

直近の2行は暗号化モード(ECB)とパディングが設定されているので注意してください。 モードの設定については、利用可能な情報を利用します。 鍵の取り付けに関するブロックの最初の行が明確であることが必要です。 ターミナルで暗号化に使用する鍵と同じものを使用しますが、今回はバイト配列に変換します。

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

"IV"パラメータが設定されている行に注目してください。 いわゆる「初期化ベクトル」、すなわち、ECBモードを除くすべての暗号化モードに参加する乱数です。 したがって、この時点では、所望の長さの配列を作成するだけです。

byte[] iv   = new byte[16];

また、C#でのキーの状況は、MQLでのキーの状況とは異なるので注意が必要です。 キーの長さ (この場合は "qwertyuiopasdfgh" 行) が 16 よりも大きい場合、例外がスローされます。 これが、MQLコードのキー長を厳密に管理した理由です。

あとは簡単です。 バイナリファイルの読み込み→ストリームの復号化→作成したUserLicクラスのインスタンスを BinaryReaderを使って埋めます。 おそらく、対応するクラスをシリアライズ可能にすることで、同様の結果を得ることができるでしょう。 この可能性を自分で試すことができます。

任意のフィールドを変更してみましょう。 そして、同じようにデータを暗号化して「lic_C#.txt」というファイルを新規作成します。 上記の操作は、プロジェクト内の2つの静的関数、EncryptToFile_AesDecryptFromFile_Aesによって実行されます。 デバッグ目的で、ファイルではなくバイト配列で動作する類似の関数を2つ追加しました。EncryptToArray_AesDecryptFromArray_Aesです。

必要なファイルがすべて入ったCryptoC#.zipプロジェクトを以下に添付します。

次のようなプロジェクトの不備に気付いた人はいないでしょう。

  • 呼び出された関数の引数の必要なチェックがありません。
  • 例外処理がありません。
  • シングルストリームの動作モード。

この記事は関数満載のアプリを作ることを目的としたものではないので、上記の関数を実装していません。 必要な部分をすべて実装してしまうと、余計なコード部分が大きくなりすぎて、基本的な問題から注意が散漫になってしまいます。

C++プロジェクト

次のプロジェクトはC++で作成します。 Visual Studio 2017環境でコンソールアプリケーションを作成してみましょう。 暗号化/復号化のサポートは一切しておりません。 そのため、OpenSSLインストールパッケージをダウンロードしてインストールすることで、よく知られているOpenSSLライブラリを接続する必要があります。 その結果、作成されたプロジェクトに接続されているはずのOpen SSLライブラリとインクルードをすべて使用することができます。 ライブラリをプロジェクトに接続する方法については、この記事 にあります。 残念ながら、OpenSSLのドキュメントは完全ではありませんが、以上のものはありません。

ライブラリが接続されたら、コードを書くタスクに進みます。 まず、既に知られている2つの構造を再び記述することです。

constexpr size_t PRODMAXLENGTH = 255;

#pragma pack(1)
typedef structEA_USER {
        EA_USER();
        EA_USER(std::string name);
        EA_USER(EA_USER&EAuser);
        std::time_t  expired;
        long   namelength;
        charEAname[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];     
        longEA_count;             
        std::vector<PEAUNIC> pEa;

        std::string GetTimeExpired();
        std::string ToString();
        size_t ToArray(byte* pbyte);
        void AddEA(EA_USEREAu);
        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()


C#の時よりもコードが少し複雑になっています。 フィールドの種類によって差があります。 例えば、このプロジェクトでは、アカウントの配列を持つフィールドはint64_t配列型を持ち、MQLインクルードファイルはlong型を持ちます。 適切な種類の大きさと関係します。 このような関数を制御しないと、キャッチしづらいエラーが発生する可能性があります。 このパートは簡単です:ここでは時間を変換する必要はありません。

また、このプロジェクトでは、キーの長さが正しくない問題に直面することがあります。 この問題を解決するためには、以下のような関数をプロジェクトに組み込んでください。

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

この関数は,appBuffer配列を必要なaSizeの長さに "トリミング "します. また、次のような補助関数を書いてみましょう。

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

本関数では、OpenSSLライブラリからのエラーコードの解説を行います。 以下の2つの関数が主な操作を実装します。

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)

メソッドの実装は添付ファイルに記載します。 肝心のポイントだけ触れておきます。

  • ここでは初期化ベクトルは使用しません。 希望のサイズの配列を作成し、呼び出しポイントで渡します。
  • ライブラリでは、パディングに関する特典はありません。 呼び出してこのモードを設定します。
    EVP_CIPHER_CTX_set_padding(ctx.get(), 0);
    
    
    こんな感じではなく、必ず「ゼロ」を渡すようにしましょう。
    EVP_CIPHER_CTX_set_padding(ctx.get(), EVP_PADDING_ZERO);
    
    
    ここでは適切と思われるかもしれません。 追加に関連してさらに問題があります。 実際のところ、パディング値がゼロの場合(プロジェクトのように)、開発者は暗号化オブジェクトの長さがBLOCK_SIZE = AES_BLOCK_SIZEの倍数、すなわち16バイトになるように注意しなければなりません。 そのため、aes_encrypt(......) を呼び出す前に、暗号化される配列の適切な整列を提供する必要があります。

前回のプロジェクトで行ったのと同様の一連のアクションを実行します。

  • 出来上がったファイルを復号化し、編集して再度暗号化します。 この場合、ライセンスにもう一人のユーザーアカウントに関する情報を追加します。
  • これで、もう一つの暗号化されたファイル、lic_С++.txtを受け取りました。 今度はファイルサイズが異なります。 アライメント時に追加されたクロックサイズ(16バイト)です。

プロジェクトのすべてのソースファイルは、以下に添付されているCryptoС++.zipアーカイブにあります。

    最終チェックと結果

    さて、直近の操作ステップに移ります。 最近暗号化されたlic_С++.txtファイルをMetaTraderデータディレクトリのFileフォルダに移動し、先に書いたスクリプトdecryptuser.mq5を使って復号化します。 期待通りの結果が得られました: ファイルの長さが変わったそれにも関わらず、ファイルは正常に復号化されました。

    では、結果として何が得られるのか? 最も重要なことは、暗号化/復号化パラメータを決定し、暗号化されたファイルをあるプログラムから別のプログラムに転送できるようにしたことです。 明らかに、暗号化/復号化に失敗した場合、アプリケーションプログラムのエラーが原因で発生する可能性があることを後から想定することができます。

      ハッシュ

      暗号は暗号化/復号化だけに限らないことは、ほとんどの方がご存知かと思います。 ここでは、暗号の基本要素であるハッシュについて考えてみましょう。 このプロセスは,任意の配列を固定長の配列に変換することを意味します。 このような配列をハッシュと呼び、変換関数をハッシュ関数と呼びます。 少なくとも1ビットで互いに異なる2つの初期配列は、識別や比較に使用できる全く異なるハッシュを生成します。

      ここでは一例を紹介します。 ユーザーはサイトに登録し、本人確認データをインプットします。 データは秘密のデータベースに保存されています。 さて、同じユーザーがメインページでログインとパスワードをインプットしてログインします。 ウェブサイトでは何をすべきか? データベースからユーザーのパスワードを取得し、インプットされたものと比較することができます。 しかし、安全ではありません。 安全な方法は、保存されているパスワードのハッシュ値とインプットされたパスワードのハッシュ値を比較することです。 保存されたハッシュが盗まれても、パスワード自体は安全です。 ハッシュを比較することで、インプットされたパスワードが正しいかどうかを判断することができます。

      ハッシュ化は一方通行です。 利用可能なハッシュを使用すると、ハッシュを受信したデータ配列を取得することができません。 そのため、ハッシュは暗号化にとって重要です。 様々な環境でのハッシュ計算を考えてみましょう。

      今回の目的は同じで、ターミナルと他の第3者のプログラムで計算したときに、同じ初期データのハッシュが同じになるようにする方法を見つけることです。

      MQLでは、先ほどと同じライブラリ関数を使ってハッシュを計算します。 CryptEncode . method関数の引数には、ハッシュを計算するための値を設定する必要があります。 CRYPT_HASH_SHA256の値を使ってみましょう。 ドキュメントには、他の値や他のハッシュ型が記載されていますので、このトピックをさらに読んでみてください。 既に存在するパスワードの行を使用します。"qwertyuiopasdfgh"をソース配列として使用します。 そのハッシュを計算し、ファイルに書き込む。 結果として得られるコードはシンプルです。したがって、個別のクラスや関数を作成することなく、添付のスクリプトファイルdecryptuser.mq5にインクルードするだけです。

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

      以前暗号化に使用していたkey配列はここでは使用していません。 結果のハッシュをresult配列に書き込み、ウィンドウに出力してsha256.binファイルに書き込みます。 結果として得られるハッシュの長さは32バイトに固定されています。 ソース配列のサイズを変更することができますが、1文字の長さにしてもハッシュサイズは32バイトになります。

      C#やC++プロジェクトに必要な関数を追加して、同じ計算を繰り返します。 変更点は最小限に抑えられており、シンプルです。 パスワード文字列と同じソース配列を使用します。 似たようなコード行を追加します。 計算して... ガッカリするほど違う結果が得られます! まあ、「全く違う」わけではありません。 MQL スクリプトと C++ プロジェクトで計算されたハッシュは同じです。 しかし、C#のプロジェクトでは違う結果が出ます。 1文字「a」からなる別の文字列を使ってみましょう。 繰り返しになりますが、C#プロジェクトでの計算では異なるハッシュが生成されます。

      この問題は、文字列を配列に変換する StringToCharArray 関数呼び出しに関連します。 StringToCharArray呼び出し後の結果の配列を見ると、配列のサイズが2倍になっていることがわかります。 例えば、文字列 "a" で関数を呼び出した後、結果の配列は 2 つの要素を持つことになります。 2番目の要素は「0」になります。 C#のEncoding.ASCII.GetBytesの呼び出しで回避できます。 この場合、"0 "は配列に含まれません。 

      これで、バイト配列に "0 "を追加するコードブロックをC#プロジェクトに追加することができます。 その後、このバイト配列を使ってハッシュを計算します。 これで期待した結果が得られました。 3つのプロジェクトはすべて、同じインプットデータに対して同じハッシュを計算します。 結果のハッシュは、ファイル sha256.bin、sha256_c#.bin、sha256_С++.bin で利用できます。

      上記の例はテキストデータに関するものです。 明らかに、初期のバイナリ配列であれば、StringToCharArrayEncoding.ASCII.GetBytesを呼び出す必要はありません。 そして、0が余っていても問題はないでしょう。 そこで、もう一つ考えられるのは、C#で追加するのではなく、MQLプロジェクトから0を削除することです。

      これで、初期の問題を解決しました - 異なる環境で計算された場合でも、あるオブジェクトのハッシュがどのような条件で同一になるかを発見しました。 記事冒頭に記載した目標も達成しました。 異なる環境での結果の互換性を確保するために、どの暗号化/復号化モードを使用すべきかを決定しました。

      結論

      MetaTrader5ターミナルのアルゴリズムトレードでは、暗号化・復号化操作はあまり使われていませんが、このタスクは、そのような必要性が出てきたときに役立つかもしれません。

      この記事の先にあるものは何でしょうか? アーカイブの作成 - CrypetEncode関数では、このようなオプションが利用できます。 Base64 というエンコード規格もあります。 モードを考慮する必要はないと考えています。 確かに、アーカイブを作成するときにパスワードを設定することはできますが。

      • この可能性はドキュメントには記載されていません。
      • アーカイブの作成は、パスワードで保護されているものであっても、暗号とは何の関係もありません。
      もう一つの選択肢はBase64エンコーディングです。 暗号に関連して、この標準規格には誤解を招くような言及がいくつかあります。 この規格は、暗号化/復号化に使用してはいけません! ご希望の方は、この規格と実際の使い方をご覧になってみてください。

      このタスクの対象はライセンスです。 今回の記事では、暗号化・復号化タスクを理解する上で参考になりそうな方法を選んでみました。 そこで、バイト配列を使ってみました。 暗号化されていたり、ファイルに書き込まれていたり、復号化されていたり。 現実の状況では、不便であり、エラーの原因にもなります。 元の構造体を配列にパッキングして解凍する際に、1ビットのエラーが発生するとライセンス全体が破損してしまいます。 さらに、上記のように種類ごとのサイズの差を考えると、そのようなことも十分に考えられます。 したがって、ライセンスを格納するための別の可能な形式は、テキストです。 xmljsonです。 考慮すべき良い解決策は、MQL、C#、C++用の優れた既存のパーサーを使用することができるので、json形式を使用することです。


      記事内で使用したプログラム

       # 名称
      種類
       詳細
      1
      CryptoMQL.zip Archive 暗号化/復号化スクリプトを使ったアーカイブ。
      2 CryptoC#.zip Archive C#の暗号化/復号化プロジェクト。
      3 CryptoС++.zip Archive C++ 暗号化/復号化プロジェクト。


      MetaQuotes Ltdによってロシア語から翻訳されました。
      元の記事: https://www.mql5.com/ru/articles/8093

      添付されたファイル |
      CryptoMQL.zip (4.43 KB)
      CryptoCc.zip (7.06 KB)
      CryptoCl4.zip (2456.49 KB)
      価格系列の離散化、ランダム成分とノイズ 価格系列の離散化、ランダム成分とノイズ
      普段我々はローソク足や、価格シリーズを一定の間隔でスライスした足を使って相場を分析しています。 このような離散化手法は、相場の動きの本当の構造を歪めてしまうのではないでしょうか? オーディオ信号は時間の経過とともに変化する関数であるため、オーディオ信号を一定の間隔で離散化することは、許容される解決策です。 信号自体は時間に依存する振幅です。 この信号特性は基本的なものです。
      DoEasyライブラリの時系列(第47部): 複数銘柄・複数期間標準指標 DoEasyライブラリの時系列(第47部): 複数銘柄・複数期間標準指標
      この記事では、標準指標を操作する方法の開発を開始します。これにより、最終的には、ライブラリクラスに基づいて複数銘柄の複数期間の標準指標を作成できるようになります。さらに、「スキップされたバー」イベントを時系列クラスに追加し、ライブラリ準備関数をCEngineクラスに移動することで、メインプログラムコードからの過度の負荷を排除します。
      トレンドとは何か、相場の構造はトレンドかレンジかで決まるのか? トレンドとは何か、相場の構造はトレンドかレンジかで決まるのか?
      トレーダーはよくトレンドやレンジについて話しますが、トレンドやレンジとは何かを理解している人はほとんどおらず、概念を明確に説明できる人はさらにいません。 基本的な用語について考察することは、多くの場合、偏見や誤解の固まりに悩まされます。 しかし、利益を上げたいのであれば、概念の数学的・論理的な意味を理解する必要があります。 今回は、トレンドとレンジの本質に迫るとともに、相場の構造がトレンドなのか、レンジなのか、何か別のものなのかを定義してみたいと思います。 また、トレンド相場やレンジ相場で利益を出すための最適な戦略についても考えていきたいと思います。
      取引イベントおよびシグナルの音声通知システム 取引イベントおよびシグナルの音声通知システム
      今日では、ナビゲーター、音声検索、翻訳ツールがよく使用され、音声アシスタントは人間の生活において重要な役割を果たしています。本稿では、さまざまな取引イベント、市場の状態、取引シグナルによって生成されるシグナルに対するシンプルでユーザフレンドリーな音声通知システムの開発を試みます。