
MQL5でSHA-256暗号化アルゴリズムをゼロから実装する
MQL5の進化により、サードパーティ製ライブラリを使用することなく暗号化アルゴリズムを効率的に実装できるようになり、セキュリティ、最適化、安定性が大きく向上しました。カスタム実装の利点には、タスクに特化した調整、既知の脆弱性の排除、および異なる環境間での一貫したパフォーマンスの実現が含まれます。現在では、暗号技術はAPI設計、トランザクションの検証、データ整合性の維持において不可欠な要素となっており、開発者にはこれらのアルゴリズムに対する深い理解が求められています。暗号化技術と取引システムの融合は、現代のプラットフォーム開発における必須要件となっています。
API署名の互換性の課題
MQL5におけるカスタムSHA-256実装が求められる主な理由は、MQL5のネイティブなハッシュ関数が、暗号資産取引所が求める署名仕様と根本的に互換性がないためです。この非互換性は、取引操作に直接影響を及ぼす複数の重要な点で顕在化します。
BinanceやBybitといった暗号通貨取引所のAPIを利用する際、取引プラットフォームは厳密に取引所の仕様に一致する暗号署名を生成する必要があります。署名はリクエストの正当性を証明する手段として機能し、注文やその他の機密性の高い操作が認可されたソースから送信されていることを保証する役割を果たします。しかし、MQL5の組み込み暗号関数は、他の一般的なプログラミング言語で生成された署名と一致しないことが多くあります。
この署名の不一致は、取引所が通常、PythonやJavaScriptなどの言語による実装に基づく特定の標準に従った署名を想定しているために生じます。MQL5のネイティブ関数は、一般的な用途には対応しているものの、ハッシュ処理の特定の工程において、次のような点で異なる動作をする場合があります。
- 入力処理時のバイト順序
- パディングの実装
- 文字エンコーディングの処理
- 中間値のメモリ上の表現
- 入力文字列中の特殊文字の処理
こうした違いにより、実稼働環境では重大な運用上の問題が発生します。
まず、署名が期待された値と一致しないために、取引所がAPIリクエストを拒否するという問題があります。この拒否は、取引ロジックが実行される前の認証段階で発生します。たとえば、取引アルゴリズムが収益機会を検出し、注文を出そうとしても、署名が無効であれば、取引所がリクエストを即座に拒否し、その機会を完全に逃すことになります。
次に、認証エラーはAPIのあらゆる操作に一貫して発生します。たとえば、口座残高の取得や注文状況の確認といった基本的な操作でさえ、取引所側がリクエストの正当性を確認できないために不可能となります。これは、取引システム全体の動作に広範囲な影響を及ぼす問題です。
3つ目に、注文の送信が特に深刻な問題となります。注文を出そうとした際に署名が無効だと、取引の実行が妨げられ、次のような事態を引き起こす可能性があります。
- 計画された取引のエントリーポイントを逃す
- 必要なときにポジションを終了できない
- リスク管理操作の失敗
- 取引戦略の不完全な実行
4番目に、メッセージの検証の信頼性にも影響します。多くの取引所では、通知やメッセージの正当性をハッシュベースで検証していますが、ハッシュ出力の不整合により、取引システムがこれらのメッセージを確実に検証できず、重要な情報の見逃しや誤った情報に基づいた処理につながる可能性があります。
これらの互換性の問題の影響は、単なる技術的な問題にとどまりません。取引操作全体に対して、微妙でありながら重大な影響を及ぼします。
- 取引戦略を安定して実行できない
- リスク管理システムが意図どおりに機能しない可能性がある
- システムの監視やログが信頼できなくなる
- 取引所APIとの統合に常に回避策が必要になる
たとえば、暗号通貨取引所への一般的なAPI呼び出しを考えてみましょう。同じ入力データでまったく異なる署名が生成される場合があります。
MQL5組み込み関数の使用:
void OnStart() { // The text to hash string text = "Hello"; string key_text = "key"; // Method 1: Using HashCalculate() - Returns uchar array uchar data[]; uchar key[]; uchar result[]; StringToCharArray(text, data); StringToCharArray(key_text, key); int res = CryptEncode(CRYPT_HASH_SHA256, data, key, result); Print(ArrayToHex(result)); } /*Result D9D3734CD05564A131946ECF9E240E0319CA2F5BA321BD9F87D634A24A29EF4D */
標準実装の使用:
import hashlib import hmac text = "Hello" #message key = "key" #password hash_object = hmac.new(bytes(key, 'utf-8'), text.encode('utf-8'), hashlib.sha256) hex_dig = hash_object.hexdigest() hex_dig ##output >>> "c70b9f4d665bd62974afc83582de810e72a41a58db82c538a9d734c9266d321e"
署名生成における不一致は、有効な取引シグナルや戦略の失敗を引き起こす可能性があります。取引所ごとに署名の要件が異なることもあり、柔軟でカスタマイズ可能な実装が求められます。
カスタムSHA-256の実装により、開発者は以下をできるようになります。
特定の取引所の要件に合わせる
進化する標準に対応する
署名関連の問題をデバッグする
取引システム全体で一貫性を確保する
このような制御は、高頻度取引や複雑な戦略において、信頼性とスピードが極めて重要となる場面で不可欠です。困難ではあるものの、カスタム実装は本番環境の取引システムにおける信頼性と統合性を向上させます。
HMACとSHA-256の実装の違い
- Pythonでは、HMAC (Hash-based Message Authentication Code)を実行するためにhmacライブラリが使用されます。これは単にテキストとキーをハッシュするのではなく、以下のような複合的な処理をおこないます。
- 所定のサイズに合わせてキーを補完する(または切る)
- 2段階のハッシュ処理を実行する:まずキーとメッセージで、次にキーとその中間結果でハッシュする
- 一方、MQL5のコードではCryptEncode()を使ってSHA-256の計算のみをおこなっており、完全なHMACの実装にはなっていません。これはキー付きの通常のテキストハッシュであり、厳密にはHMACではありません。
結論:PythonはHMACを使用しているのに対し、MQL5はSHA-256のみを使用しているため、結果が異なるのは当然です。
パフォーマンスの最適化:詳細
取引環境でSHA-256を実装する際、わずかなミリ秒の遅延が取引結果に影響するため、パフォーマンスの最適化は非常に重要です。カスタム実装により、組み込み関数では不可能な最適化が可能となります。
取引システムでは、暗号化処理に特有のパターンが見られます。たとえば、注文署名はタイムスタンプ、銘柄、数量など似たような要素を含みます。これらのパターンを活用することで、取引関連データ構造に特化したSHA-256の最適化が可能です。
典型的な取引シナリオにおける注文処理について考えてみましょう。各注文には取引ペア、注文タイプ、数量、価格、タイムスタンプなど複数の情報が必要です。一般的な実装では、これらのデータは毎回まったく新しい入力として処理されます。しかし、多くの要素が一定であったり、予測可能なパターンに沿って変化することに着目すれば、この処理を効率化することが可能です。
具体例として、注文の署名生成を考えてみます。注文のデータ構造の多くは共通しています。
baseEndpoint/symbol=BTCUSDT&side=BUY&type=LIMIT&quantity=0.1&price=50000×tamp=1234567890
この文字列の中で、注文ごとに変わるのは主に「数量」「価格」「タイムスタンプ」のみです。こうした部分的な変更を効率的に処理できる実装にすることで、パフォーマンスを大幅に向上させることができます。これには次のようなことが含まれます。
- 取引データの共通構造を効率的に処理する専用の前処理関数を用意する
- 頻繁に使われる要素のバッファ管理を工夫する
- 取引操作によく出現する数値データの解析処理を最適化する
また、MQL5環境特有の制約や特性を踏まえたメモリ管理も重要です。MetaTraderプラットフォームではリソースに限りがあり、非効率なメモリ使用はシステム全体のパフォーマンスに悪影響を与えます。カスタム実装なら、取引の特性に合わせてメモリの割り当てや解放を細かく調整可能です。
さらに、取引操作の時間的な局所性を活かしたキャッシュ戦略を導入できます。高頻度取引の際に特定の取引ペアや注文タイプが繰り返し利用される場合、そうしたパターンの中間ハッシュ状態をキャッシュすることで、後続処理の負荷を軽減できます。
将来性:長期的な運用の持続性の確保
暗号通貨の世界は非常にダイナミックで、取引所は要件やセキュリティプロトコルを頻繁に更新します。こうした変化に柔軟に対応しつつシステムの信頼性を維持するためには、カスタムのSHA-256実装が大きな強みとなります。
時間とともに、取引所の署名要件は次のように変化する可能性があります。
- 署名文字列のパラメータの順序を変更する
- 署名に新しい必須項目を追加する
- 特定の文字や特殊なケースの処理方法を変更する
- 署名の生成方法に影響を与える新たなセキュリティ対策の導入
カスタム実装であれば、これらの変化にも柔軟かつ迅速に対応できます。初期のデータ前処理から最終的な出力フォーマットまで、ハッシュ処理の全工程を自ら制御できるため、MQL5の組み込み関数のアップデートを待たずとも、新しい要件への対応が可能です。
たとえば、署名内でのUnicode文字の扱いに変更があった場合でも、実装を即座に修正して対応できます。複数の取引所がそれぞれ異なる要件を持つことを考えると、この適応性は非常に重要です。
MQL5の組み込み関数から独立することには、もう1つの大きな利点があります。MetaTraderがアップデートを重ねる中で、組み込み関数に微細な変更が加えられる可能性があります。そうした変更が署名生成に影響を与えることもあり得ますが、カスタム実装であれば、MetaTraderのバージョンに左右されず、常に安定した動作を保つことができます。
将来への対応という観点は、署名要件にとどまりません。暗号通貨取引所は、SHA-256を基盤とした新しいセキュリティ機能や認証方式を導入する可能性があります。カスタム実装を持つことで、以下のことが可能になります。
- 基本的なSHA-256の機能を拡張し、新しいセキュリティ機能をサポートする。
- 新しい認証方式で動作するように実装を修正する。
- 追加の暗号操作をシームレスに統合
- 後方互換性を維持しながら新機能を追加
さらに、カスタム実装は、将来的に必要となる可能性のある他の暗号化機能を実装するための基盤となります。SHA-256向けに開発されたコード構造や最適化手法は、他のハッシュ関数や暗号処理を実装する際のテンプレートとして活用できます。
たとえば、取引所が二重ハッシュの要件を導入したり、SHA-256を別のアルゴリズムと組み合わせるようなケースを考えてみましょう。カスタム実装であれば、こうした機能の追加は、組み込み関数の制約に対処するのではなく、既存コードの拡張という形で対応できます。
このような拡張性は、急速に変化する暗号資産取引の世界において非常に重要です。新たな取引手法、セキュリティ要件、技術的進歩は短期間で現れる可能性があり、柔軟かつカスタマイズ可能な実装を備えることで、取引システムもそれに応じて迅速に適応・進化できます。
パフォーマンスの最適化と将来対応性を兼ね備えたカスタムSHA-256実装は、本格的な暗号通貨取引業務において極めて重要な資産となります。急速に変化する市場環境の中で競争力を維持しつつ、長期的な持続性を確保するために必要な制御性、柔軟性、そして効率性を提供します。
SHA-256アルゴリズムを理解する
SHA-256は「Secure Hash Algorithm 256-bit(安全なハッシュアルゴリズム256ビット)」の略であり、任意の長さの入力データを固定サイズの256ビット(32バイト)の出力にマッピングする暗号学的ハッシュ関数です。 これはSHA-2ファミリーに属するアルゴリズムであり、非常に体系的かつ明確に定義された一連のステップに従って処理をおこないます。この特徴により、セキュリティと決定性の両方が実現されています。
MQL5における実装
各構成要素を詳細に確認していきましょう。完全な実装コードは最後のセクションに記載しています。 この実装では、PythonにおけるHMACクラスとSHA256クラスの構成を参考にし、2つのクラスを用います。
以下は、SHA256クラスとHMACクラスの構造です。
class CSha256Class { private: uint total_message[]; uint paddedMessage[64]; void Initialize_H();//This function initializes the values of h0-h7 uint RawMessage[];//Keeps track of the raw message sent in public: //hash values from h0 - h7 uint h0; uint h1; uint h2; uint h3; uint h4; uint h5; uint h6; uint h7; uint K[64]; uint W[64]; CSha256Class(void); ~CSha256Class() {}; void PreProcessMessage(uint &message[], int messageLength); void CreateMessageSchedule(); void Compression(); void UpdateHash(uint &message[], int message_len); void GetDigest(uint &digest[]); string GetHex(); };
class HMacSha256 { private: public: uint k_ipad[64]; uint k_opad[64]; uint K[]; string hexval; HMacSha256(string key, string message); ~HMacSha256() {}; CSha256Class myshaclass; void ProcessKey(string key); };
ステップごとのクラス実装ガイド
このモジュールは、RFC 2104で定義されているHMACアルゴリズムを実装します。
手順1
キーを処理する関数を作成します。
void HMacSha256::ProcessKey(string key) { int keyLength = StringLen(key);//stores the length of the key if(keyLength>64) { uchar keyCharacters[]; StringToCharArray(key, keyCharacters); uint KeyCharuint[]; ArrayResize(KeyCharuint, keyLength); //Converts the keys to their characters for(int i=0;i<keyLength;i++) KeyCharuint[i] = (uint)keyCharacters[i]; //Time to hash the CSha256Class keyhasher; keyhasher.UpdateHash(KeyCharuint, keyLength); uint digestValue[]; keyhasher.GetDigest(digestValue); ArrayResize(K, 64); for(int i=0;i<ArraySize(digestValue);i++) K[i] = digestValue[i]; for(int i=ArraySize(digestValue);i<64;i++) K[i] = 0x00; } else { uchar keyCharacters[]; StringToCharArray(key, keyCharacters); ArrayResize(K, 64); for(int i=0;i<keyLength;i++) K[i] = (uint)keyCharacters[i]; for(int i=keyLength;i<64;i++) K[i] = 0x00; } }
この実装ではキーの長さが考慮され、キーの長さが64より大きい場合は、まずCSha256Classを使用してハッシュされます。
手順2HMACを完全に実装します。
HMacSha256::HMacSha256(string key,string message) { //process key and add zeros to complete n bytes of 64 ProcessKey(key); for(int i=0;i<64;i++) { uint keyval = K[i]; k_ipad[i] = 0x36 ^ keyval; k_opad[i] = 0x5c ^ keyval; } //text chars uchar messageCharacters[]; StringToCharArray(message, messageCharacters); int innerPaddingLength = 64+StringLen(message); uint innerPadding[]; ArrayResize(innerPadding, innerPaddingLength); for(int i=0;i<64;i++) innerPadding[i] = k_ipad[i]; int msg_counts = 0; for(int i=64;i<innerPaddingLength;i++) { innerPadding[i] = (uint)messageCharacters[msg_counts]; msg_counts +=1; } //send inner padding for hashing CSha256Class innerpaddHasher; innerpaddHasher.UpdateHash(innerPadding, ArraySize(innerPadding)); uint ipad_digest_result[]; innerpaddHasher.GetDigest(ipad_digest_result); // merge digest with outer padding uint outerpadding[]; int outerpaddSize = 64 + ArraySize(ipad_digest_result); ArrayResize(outerpadding, outerpaddSize); for(int i=0;i<64;i++) outerpadding[i] = k_opad[i]; int inner_counts = 0; for(int i=64;i<outerpaddSize;i++) { outerpadding[i] = ipad_digest_result[inner_counts]; inner_counts+=1; } CSha256Class outerpaddHash; outerpaddHash.UpdateHash(outerpadding, ArraySize(outerpadding)); hexval = outerpaddHash.GetHex(); }
添付された完全なコードには、他の関数も含まれています。
この実装の中核となるのは、ハッシュ関数であり、CSHa256Classによって処理されます。
ハッシュ関数の処理は、テキストに対する複数のステップの操作を含みます。
手順1:値の前処理
全体のデータが512ビットの倍数になるように、いくつかのパディング操作をおこないます。- メッセージをバイナリ形式に変換します。
- メッセージの末尾に1を追加します。
- 全体のデータ長が448ビット(= 512 - 64)に達するまで0を追加してパディングします。これは、512ビットブロック内にちょうど64ビットを残すためです。言い換えれば、メッセージ長が512で割って448に合同(≡ 448 mod 512)になるようにパディングをおこないます。
- 最後に64ビットを末尾に追加します。の64ビットは、元の入力の長さをバイナリで表したビッグエンディアンの整数です。
手順2:ハッシュ値の初期化
手順3:ラウンド定数の初期化
手順4:全メッセージのビット列を512ビットごとのチャンクに分割し、それぞれのチャンクに対して以下の処理を繰り返し実行します。
int chunks_count = (int)MathFloor(ArraySize(total_message)/64.0); int copied = 0; for(int i=0; i<chunks_count; i++) { uint newChunk[]; ArrayResize(newChunk, 64); ArrayInitialize(newChunk, 0); // Initialize chunk array for(int j=0; j<64; j++) { newChunk[j] = total_message[copied]; copied += 1; } PreProcessMessage(newChunk, ArraySize(newChunk)); CreateMessageSchedule(); Compression(); }
手順5:チャンク配列を新しい配列にコピーし、各要素が32ビットのワードとなるようにメッセージを前処理します。
void CSha256Class::PreProcessMessage(uint &message[],int messageLength) { ArrayInitialize(paddedMessage, 0); for(int i=0; i < messageLength; i++) paddedMessage[i] = message[i]; }手順6:このチャンクに対するメッセージスケジュールを作成します。
void CSha256Class::CreateMessageSchedule() { ArrayInitialize(W, 0); int counts = 0; for(int i=0; i<ArraySize(paddedMessage); i+=4) { //32 bits is equivalent to 4 bytes from message uint byte1 = paddedMessage[i]; uint byte2 = paddedMessage[i+1]; uint byte3 = paddedMessage[i+2]; uint byte4 = paddedMessage[i+3]; uint combined = ((byte1 << 24) | (byte2 << 16) | (byte3 << 8) | byte4); W[counts] = combined & 0xFFFFFFFF; counts += 1; } for(int i=counts; i<64; i++) W[i] = 0x00000000; //preserve previous counts int prev_counts = counts; int left_count = 64-counts; for(int i=counts; i<64; i++) { uint s0 = (RightRotate(W[i-15], 7)) ^ (RightRotate(W[i-15],18)) ^ (W[i-15] >> 3); uint s1 = (RightRotate(W[i-2], 17)) ^ (RightRotate(W[i-2],19)) ^ (W[i-2] >> 10); W[i] = (W[i-16] + s0 + W[i-7] + s1) & 0xFFFFFFFF; } }
手順7: 圧縮ループを適用します。
void CSha256Class::Compression(void) { uint a = h0; uint b = h1; uint c = h2; uint d = h3; uint e = h4; uint f = h5; uint g = h6; uint h = h7; for(int i=0; i<64; i++) { uint S1 = (RightRotate(e, 6) ^ RightRotate(e,11) ^ RightRotate(e,25)) & 0xFFFFFFFF; uint ch = ((e & f) ^ ((~e) & g))& 0xFFFFFFFF; uint temp1 = (h + S1 + ch + K[i] + W[i]) & 0xFFFFFFFF; uint S0 = (RightRotate(a, 2) ^ RightRotate(a, 13) ^ RightRotate(a, 22)) & 0xFFFFFFFF; uint maj = ((a & b) ^ (a & c) ^ (b & c)) & 0xFFFFFFFF; uint temp2 = (S0 + maj) & 0xFFFFFFFF; h = g & 0xFFFFFFFF; g = f & 0xFFFFFFFF; f = e & 0xFFFFFFFF; e = (d + temp1) & 0xFFFFFFFF; d = c & 0xFFFFFFFF; c = b & 0xFFFFFFFF; b = a & 0xFFFFFFFF; a = (temp1 + temp2)&0xFFFFFFFF; } h0 = h0 + a; h1 = h1 + b; h2 = h2 + c; h3 = h3 + d; h4 = h4 + e; h5 = h5 + f; h6 = h6 + g; h7 = h7 + h; }
圧縮ループが完了した後、変更された作業用変数を元のハッシュ値に加算することで、最終的なハッシュ値を更新します。
この加算操作は、メッセージの各512ビットチャンクを処理した後におこなわれ、更新されたハッシュ値は次のチャンクを処理する際の開始点となります。この連鎖メカニズムにより、各ブロックのハッシュ値はそれ以前のすべてのブロックに依存するようになり、最終的なハッシュ値はメッセージ全体に依存することになります。
このクラスの使い方
BinanceやBybitのAPIシグネチャを生成するには、Pythonの実装と同様に、HMAC (Hash-based Message Authentication Code)のインスタンスを作成します。
void OnStart() { // The text to hash string text = "Hello"; string key_text = "key"; HMacSha256 sha256(key_text, text); Print(sha256.hexval); } >>> C70B9F4D665BD62974AFC83582DE810E72A41A58DB82C538A9D734C9266D321E
両方の実装で生成された署名を比較することで、それらが同一の結果を出力していることが確認できます。
結論
暗号通貨を扱うトレーダーは、MetaTrader 5のような取引システムで組み込みの暗号化関数を使用する際に、API署名の互換性、パフォーマンスの最適化、そして将来の拡張性といった課題に直面することがよくあります。この記事では、これらの問題を解決するために、MQL5でSHA-256をゼロから実装する方法を解説しました。結論として、カスタムのSHA-256実装を作成することにより、トレーダーは、取引所との署名互換性の確保、高速化とパフォーマンスの最適化、将来的な仕様変更への柔軟な対応といった利点を得ることができます。これにより、安全かつ効率的な暗号通貨取引を実現する上で、極めて重要な戦略となります。
本番環境に展開する前には、標準的なテストベクトルを用いて実装を定期的に検証し、対象となる取引所で署名が正しく機能するかどうかを確認することを忘れないでください。暗号通貨取引所がセキュリティ要件を進化させ続ける中で、この柔軟なカスタム実装は、迅速な適応と継続的なメンテナンスにおいて極めて有用であることが証明されるでしょう。
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/16357





- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索