MQL5入門(第36回):MQL5のAPIとWebRequest関数の習得(X)
はじめに
連載「MQL5入門」の第36回へようこそ。第31回ではBinanceのような外部プラットフォームとの通信方法について解説しましたが、その時点では通信は単純なものであり、機密情報のやり取りは含まれていませんでした。 本記事では、API接続におけるセキュリティ面に焦点を当てます。具体的には、リクエストの認証、リクエストの完全性の検証、そして改ざん防止に用いられる署名、HMAC (Hash-based Message Authentication Code)、SHA256ハッシュといった重要な概念を扱います。発注や取引管理など、重要なAPI呼び出しにおいて、これらのセキュリティ機構は不可欠です。
なお、本記事で扱うのはあくまで理論と構成要素のみです。現時点では実際のリクエストとして機密情報を送信しません。これらの概念を統合し、安全にサーバーと通信する方法については次回の記事で扱います。本記事が終わるころには、HMAC、SHA256、署名がどのように組み合わさり、安全なMQL5 API呼び出しの基盤を形成するかについて、基本的な理解を得ることができます。
署名とは?
APIにおいて署名は、各リクエストに付与される特別な証明印のような役割を果たします。この署名によりサーバーは2つの点を検証できます。すなわち、そのリクエストが確かに送信者本人からのものであること、そして送信途中で内容が改ざんされていないことです。言い換えると、署名は、このリクエストが信頼できることをサーバーに対して保証します。署名が存在しない場合、第三者がリクエストを偽装できる可能性があり、特に注文発注や取引管理のような機密性の高い処理においては重大なリスクとなります。
リクエストの正当性を検証するためには署名が不可欠です。サーバーは受信したリクエストが正しいものであるかを検証する必要があります。これはHMAC-SHA256などの暗号化アルゴリズムを用い、リクエストデータと秘密鍵から署名を生成することで実現されます。この署名はリクエストとともにサーバーへ送信され、サーバー側でも同じ計算をおこないます。その結果が一致することで、リクエストの正当性が確認されます。 理解の補助として、署名付きの手紙をイメージすると分かりやすいです。手紙の末尾にあなたの署名があれば、それが本人によって書かれたものであることは明確です。一方で、署名なしで誰かが偽造した場合、受け取った側はその手紙の真正性を疑うことになります。同様に、APIにおける署名は、リクエストが送信者本人から発信されたものであり、通信途中で改ざんされていないことを保証します。
HMAC-SHA256の理解
HMACについて説明する前に、まずMACが何を意味するかを理解することが重要です。MACはMessage Authentication Code(メッセージ認証コード)の略です。MACは、メッセージと秘密鍵を用いて生成される小さなデータです。その主な目的は、通信が意図した送信者から送信されたものであること、そして送信途中で改ざんされていないことを同時に証明することです。サーバーはメッセージを受信した後、同じ秘密鍵を用いてMACを再計算します。その結果が一致すれば、その通信は正当であると判断されます。 MACにはいくつかの種類が存在します。ハッシュ関数を用いて生成されるものもあれば、ブロック暗号を用いるものもあります。いずれの方式も目的は共通しており、メッセージの認証と完全性の検証です。その中でもHMACは最も広く使用されている安全な方式の一つです。
HMACは、APIリクエストが正当であり、途中で改ざんされていないことを検証するための署名として利用されます。発注のような機密性の高いリクエストでは、Binanceのようなプラットフォームでもこの仕組みが用いられます。本記事では、HMAC-SHA256の内部的な数学的詳細は扱いません。その代わり、MQL5でAPIリクエストを正しく生成し、署名するために必要な要素のみに焦点を当てます。ここでは暗号理論の完全な理解よりも、実務的な理解が重要です。 この仕組みの中核は、メッセージ認証の仕組みとSHA256ハッシュアルゴリズムです。最終的な出力は署名と呼ばれる固定長の値になります。この署名は、その後WebRequest関数を通じてリクエストURLに付加され送信されます。
SHA256は入力長に関係なく、常に256ビットの固定長出力を生成する暗号ハッシュ関数です。入力がどのようなものであっても、出力サイズは常に一定です。また、同じ入力に対しては常に同じ出力を返すという決定論的性質を持ちます。 重要な特性として「雪崩効果」があります。入力の1文字を変更しただけで、出力のハッシュ値は完全に異なるものになります。このためSHA256はデータ改ざんの検出に非常に有効です。ただし、SHA256単体ではデータの送信者を証明することはできません。誰でも同じデータのハッシュを生成できるためです。ここでHMACが役割を持ちます。
HMACはSHA256に秘密鍵を組み合わせて拡張した仕組みです。この秘密鍵は、あなたとあなたのサーバー(たとえばBinanceとMetaTrader 5環境の間)でのみ共有されるものです。メッセージと秘密鍵を組み合わせてSHA256でハッシュ化することで、その秘密鍵を知っている人だけが生成できる署名が作られます。 つまり、攻撃者がメッセージを確認できたとしても、秘密鍵がなければ正しい署名を生成することはできません。本ケースでは、内部的なハッシュ処理の詳細を理解する必要はありません。重要なのは、同じメッセージと秘密鍵を使用すれば常に同じ署名が生成され、どちらか一方でも変更すれば結果は完全に変わるという点です。
実際には、署名対象となる「メッセージ」は通常、timestampなどのリクエスト要素から構成されるクエリ文字列です。この文字列とAPIシークレットキーがHMAC-SHA256の処理に渡され、その結果として署名が生成されます。 この生成された署名は、リクエストURLの追加パラメータとして付与されます。サーバー側では、リクエスト受信後に同じ暗号方式と秘密鍵を用いて署名を再計算します。再計算された署名と送信された署名が一致すればリクエストは承認され、一致しなければ拒否されます。この仕組みは高度な暗号理論に基づいていますが、通常のMQL5 API利用においてはその詳細を理解する必要はありません。本記事では、パディング、ブロックサイズ、ハッシュラウンドといった内部メカニズムには踏み込まず、署名を正しく生成し、その役割を理解するために必要な要点にのみ焦点を当てています。
このように絞った理解を前提とすることで、次のセクションでは実際にHMAC-SHA256を用いてAPIリクエストに署名し、MetaTrader 5のWebRequest関数を使って安全に送信する手順へ進むことができます。 BinanceのAPI署名生成には2つの重要な要素があります。1つ目はtimestampであり、リクエストが送信された正確な時刻を示すもので、常に更新されます。2つ目は秘密鍵であり、これはBinanceから提供されるもので、厳密に秘匿する必要があります。他のパラメータが同一であっても、timestampによって各リクエストは常に一意になります。
Binanceはリクエスト送信時に秘密鍵そのものを受け取ることはありません。その代わりに、システム側で秘密鍵とリクエストデータ(timestampを含む)を用いてHMAC-SHA256による署名を生成します。その後、その署名を付与した状態でリクエストが送信されます。受信側であるBinanceは、あらかじめ保持している秘密鍵を用いて同じ計算をおこないます。生成された署名が一致した場合にのみ、そのリクエストは承認されます。

このプロセスにより、リクエストがネットワーク上で送信される途中で改ざんされていないことが保証されます。たとえリクエストパラメータにわずかな変更が加えられた場合でも、結果として生成される署名は完全に異なるものとなるため、検証は失敗します。これはセキュリティ上非常に重要であり、特に発注のようなデリケートな処理においては不可欠です。

もう一つの重要な点は、リクエストに秘密鍵そのものが含まれないということです。秘密鍵がネットワーク上で送信されたならば、途中で傍受される可能性があります。そのため、暗号署名方式では実際に送信されるのは導出された署名のみであり、秘密鍵はローカル環境に安全に保持されます。したがって、リクエストが傍受されたとしても、それだけでは攻撃者が正規のリクエストを偽造したり、秘密鍵を再現したりすることはできません。
比喩的な説明
多くの郵便配達員が行き交う街の中で、あなたが信頼できる友人に重要な手紙を送る場面を想像してください。その手紙が確かにあなたから送られたものであり、途中で内容が改ざんされていないことを相手に確実に伝える必要があります。そのために、あなたと友人だけが作成できる秘密の蝋印を使用します。この蝋印は秘密鍵と同様の役割を持ち、常にあなたの手元にあり、他人が複製することはできません。次に、送るすべての手紙には正確な作成時刻が刻まれていると想像してください。このタイムスタンプは重要であり、同じ内容の手紙であっても送信時刻が異なれば別のものとして扱われます。タイムスタンプがなければ、誰かが過去の手紙を再送して偽装しようとする可能性があります。タイムスタンプはその瞬間を示す指紋のようなものであり、各手紙を一意にします。
あなたは手紙を送る前に、このタイムスタンプと秘密の蝋印を組み合わせて署名を作成します。この組み合わせによって、手紙にはその瞬間における固有の認証情報が付与されます。このマークはHMAC-SHA256の署名と同様に、秘密の蝋印と時間情報によって生成されるため、そのメッセージとその時点に固有のものになります。受信者は、あなたの蝋印のコピーを用いてマークを検証し、さらにタイムスタンプを確認してその手紙が最新のものであることを確認します。マークが正しく、かつ時間的にも妥当であれば、その手紙は真正なものとして受理されます。そうでない場合は即座に拒否されます。
この仕組みの優れている点は、秘密の蝋印が決して外部に出ないことです。手紙にはマーク(署名)のみが付与されて送信されます。たとえ巧妙な郵便配達人がその手紙とマークを目にしたとしても、シールそのものを複製したり、新たな手紙を偽造したりすることはできません。秘密の蠟印とタイムスタンプの組み合わせによって、すべての手紙は安全かつ真正で、かつ一意に保たれます。BinanceのAPIリクエストにおいても同様に、秘密鍵とタイムスタンプを用いて署名が生成されます。タイムスタンプは同一リクエストの再利用(リプレイ)を防ぎ、秘密鍵はリクエストが正当な送信者から発行されたものであることを保証します。その後、Binanceは署名とtimestampを検証することで、リクエストの真正性と完全性を確認します。
MQL5におけるAPI署名の生成
MetaTrader 5におけるHMAC-SHA256を用いたAPI署名生成のプロセスは、これまで学んできた理論が実用的な形で統合される段階です。ここでの目的は明確であり、取引所APIのような外部サービスに対して「このリクエストは確かにあなた自身から送信され、かつ通信途中で改ざんされていない」ことを証明する署名を生成することです。この処理はすべてローカル環境(MQL5内部)でおこなわれ、その後WebRequest関数を通じてリクエストが送信されます。
まず重要なのは「何を署名しているのか」を正確に理解することです。Binanceを含む多くのAPIでは、ランダムな入力から署名を生成するわけではありません。代わりに、リクエストパラメータを文字列として連結したものを署名対象とします。この文字列には、タイムスタンプ、注文情報、数量、その他エンドポイントに必要なパラメータが含まれます。タイムスタンプは特に重要です。これは常に変化するため、他のすべてのパラメータが同一であっても各リクエストを一意にします。もしタイムスタンプなしで有効なリクエストが記録され、それが後で再送信(リプレイ)された場合、それは重大なセキュリティリスクになります。
このパラメータ文字列が準備できると、それは秘密鍵と組み合わされます。APIプロバイダから提供される秘密鍵は絶対に共有してはならず、ネットワーク上に送信されることもありません。この秘密鍵はMetaTrader 5内部でHMAC-SHA256処理の入力としてのみ使用されます。実際の鍵そのものは一切外部に出ません。代わりに、それは最終的な出力に影響を与える「見えない構成要素」として機能します。同じ秘密鍵を知っている者だけが、同一メッセージから同一の署名を生成できます。
MQL5では、このメッセージと秘密鍵に対してハッシュ処理が適用されます。ここでは内部計算の詳細を意識する必要はありません。重要なのは結果であり、出力は一見ランダムに見えますが、常に固定長の文字列になります。この文字列が署名です。同じメッセージと同じ秘密鍵を用いれば常に同じ署名が生成され、どちらかが異なれば結果は完全に異なります。
生成された署名は、通常URLの追加パラメータとしてリクエストに付与されます。サーバー側では受信後、自身が保持している秘密鍵と受信パラメータを用いて同じ計算をおこないます。生成された署名が一致すれば、データが通信途中で改ざんされておらず、かつ正規の秘密鍵所有者によるリクエストであることが確認されます。署名およびハッシュ生成にはMetaTrader 5のCryptEncode関数が使用されます。この関数はSHA256のようなハッシュ、またはHMACベースの署名を生成できる組み込み暗号機能の一つです。本質的には、CryptEncodeは入力データを受け取り、暗号アルゴリズムに基づいて変換し、必要に応じてキーを組み込み、バイナリ値として出力します。このバイナリ値がそのまま署名またはハッシュとして使用されます。
メッセージと秘密鍵のバイトデータへの変換
署名生成の第一段階は、メッセージと秘密鍵を暗号処理可能なバイト形式へ変換することです。通常の文字列(人間が読めるテキスト)は、MQL5のハッシュやHMAC処理に直接使用することはできません。これらは未加工のバイトデータを扱うためです。つまり、いかなる暗号処理を開始する前に、人間が読める形式のメッセージと秘密鍵は低レベル表現へと変換される必要があります。保護対象となるデータは通常、リクエストパラメータから構成されるクエリ文字列などのメッセージです。各リクエストを一意にするため、この文字列にはしばしばタイムスタンプが含まれます。APIプロバイダから提供される秘密の値は秘密鍵と呼ばれます。同一の入力から常にサーバー側とクライアント側で同じ署名が生成されるようにするためには、両方の値が慎重かつ一貫した方法で変換されなければなりません。
この変換処理では、標準的なテキストエンコーディングを用いて、メッセージおよび秘密鍵の各文字を対応するバイト値へ変換します。UTF-8は広くサポートされており、複数のシステム間で一貫した解釈を保証するため、よく使用されます。この段階の結果として、メッセージ用と秘密鍵用の2つのバイト列が生成されます。暗号処理は正確なバイト列に依存するため、この変換は不可欠です。エンコーディングや表現にわずかな違いがあるだけでも、署名は完全に異なるものになります。制御された予測可能な方法でメッセージと秘密鍵をバイト形式に変換することにより、その後の暗号処理の正確性と、サーバー側で同一署名を再現して検証できることが保証されます。
このステップが完了すると、メッセージと秘密鍵はHMAC-SHA256処理で使用可能な適切な形式になります。以降のすべての処理はこのバイトレベル表現上で実行され、秘密鍵を公開することなく安全な署名生成が可能となります。
例://+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- string message = "71772777"; string key = "ABCDER2y8VAzqopxzLVEhVYABCDEV2AxIYLueComud3aSEez8Z8fvgHPZTXABCDE"; uchar uMsg[], uKey[]; StringToCharArray(message, uMsg, 0, StringLen(message), CP_UTF8); StringToCharArray(key, uKey, 0, StringLen(key), CP_UTF8); }
解説:
最初のステップは、メッセージと秘密鍵を人間が読める形式で定義することです。署名対象となるデータはメッセージとして表現され、実際のAPI利用ではこれは多くの場合タイムスタンプ、あるいはリクエストパラメータの組み合わせになります。秘密鍵はプラットフォームから提供される秘密の値であり、自分のシステムとサーバーのみが知っているべきものです。この段階では両者ともプレーンテキストとして存在しており、人間には扱いやすいものの、暗号処理には適していません。
次のステップは、未加工のバイトデータを格納できるストレージの準備です。テキストは抽象的な表現であるため、暗号処理は直接テキストを扱いません。代わりにバイト列を必要とします。そのため、秘密鍵とメッセージの変換後データを保持するための専用のバイトコンテナが準備されます。このコンテナは初期状態では空であり、変換処理の中でデータが格納されていきます。その後、メッセージはテキストからバイト表現へと変換されます。この変換にはUTF-8エンコーディングが使用され、一貫性が確保されます。UTF-8により、同じ文字は常に同じバイト列へ変換されます。これは非常に重要であり、わずかなバイトの違いでも最終的な署名が完全に異なるものになる可能性があります。
続いて同じ変換処理が秘密鍵にも適用されます。秘密鍵の文字列はバイト列へ変換され、別のコンテナに格納されます。この時点でも秘密鍵は依然として機密情報であり外部には共有されません。暗号関数が扱える形式に整形されただけです。両方の変換が完了すると、メッセージと秘密鍵はバイト配列としてシステム内に格納されます。この時点では、まだ署名やハッシュ処理はおこなわれていません。このステップの目的は準備であり、HMAC-SHA256処理が次の段階で正しくかつ検証可能な署名を生成できるようにデータ形式を整えることにあります。
比喩的な説明
秘密鍵とメッセージを2つの手書きメモとして考えます。一方のメモには、タイムスタンプやリクエスト内容など、伝えたい情報が書かれています。もう一方のメモには、あなたと受信者だけが知っている特別なコードが記されています。この時点ではどちらも手書きのプレーンな状態であり、人間には読めますが、機械的なセキュリティ検証には適していません。次に、空の封筒を2つ用意します。この封筒はまだ郵送されるものではなく、機械が扱える形式でメモを格納するための入れ物です。現時点ではどちらの封筒も空のままです。ここで、最初の手書きメモの内容を取り出し、あらかじめ決められた標準的な表現に変換します。
これは、各文字が固定の対応を持つタイプライター文字のような統一された表現へ変換することにあたります。これにより、メッセージは常に同じ形で解釈され、曖昧さがなくなります。次に同じ処理を秘密鍵にも適用します。秘密鍵も同じ標準形式に変換され、それぞれ別の封筒に格納されます。ただし秘密鍵は依然として非公開であり、外部には出ません。ここでおこなわれているのは、単に次のステップの準備にすぎません。最終的に、変換済みの秘密鍵が入った封筒と、変換済みのメッセージが入った封筒の2つが揃います。どちらもまだ封印されておらず、次のステップで安全な署名を生成するために使用される準備段階にあるだけです。
署名用のインナーパッドとアウターパッドの準備
この段階の目的は、秘密鍵を2つの異なる作業用形式へ変換することです。これらは署名処理において安全にキーとメッセージを組み合わせるために使用されます。この2種類は一般にインナーパッド(内側のパディング)およびアウターパッド(外側のパディング)と呼ばれます。これらはどこかへ送信されるものではなく、また任意に生成されるものでもありません。署名処理の内部でのみ存在する一時的な構造です。このプロセスは、同じマスターキーから2種類の異なる蝋印を作成する作業に例えることができます。両者は同じ秘密から派生していますが、検証プロセスにおいて異なる役割を果たすためにわずかに変形されています。処理は一方のスタンプから始まり、もう一方へと続きます。この二重構造により追加のセキュリティ層が形成され、単一のキーのみを使用する場合と比較して署名の偽造が大幅に困難になります。
ここで重要なのは固定サイズの概念です。HMACは固定長のブロック単位で処理をおこない、SHA256の場合はその長さが64バイトになります。この方式では、秘密鍵をこの正確なサイズに適合させ、その後2つの異なる方法で組み合わせることにより、一貫性とセキュリティを保証します。元の秘密鍵の長さに関係なく、この処理は常に同じ予測可能な構造に従って実行されます。
例://+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- string message = "71772777"; string key = "ABCDER2y8VAzqopxzLVEhVYABCDEV2AxIYLueComud3aSEez8Z8fvgHPZTXABCDE"; uchar uMsg[], uKey[]; StringToCharArray(message, uMsg, 0, StringLen(message), CP_UTF8); StringToCharArray(key, uKey, 0, StringLen(key), CP_UTF8); int blockSize = 64; uchar ipad[], opad[]; ArrayResize(ipad, blockSize); ArrayResize(opad, blockSize); for(int i = 0; i < blockSize; i++) { ipad[i] = uKey[i] ^ 0x36; opad[i] = uKey[i] ^ 0x5C; } }
解説:
この段階では、まずSHA-256ハッシュ処理におけるブロックサイズを設定します。SHA-256は64バイトの固定サイズブロック単位でデータを処理します。このブロックサイズは、HMACで使用されるインナーパッド(ipad)およびアウターパッド(opad)の長さを決定する基準になります。ブロックサイズを指定することで、両パッドがHMAC計算に適した正しい長さになることを保証します。次に、インナーパッドとアウターパッドを格納するための2つの配列(ipadおよびopad)を用意します。これらの配列は64バイトのブロックサイズに合わせて調整されます。各配列の各位置には、秘密鍵から取得したバイト値を元に変換された値が格納されます。
この段階ではループ処理を用いて、秘密鍵の各バイトを1つずつ処理します。それぞれのバイトは固定の定数と組み合わされ、インナーパッドおよびアウターパッドの準備のために変換されます。これにより、元の秘密鍵を変更することなく、ハッシュ処理に適した形へと整形されます。本質的には、この処理はハッシュ計算のために秘密鍵を一貫した方法で変換する工程になります。インナーパッドとアウターパッドを区別するために、異なる2つの定数が使用されます。この区別は重要であり、インナーパッドとアウターパッドはそれぞれ異なるハッシュ処理の段階で使用され、最終的に安全な署名を構成する役割を果たします。
この段階が完了すると、2つのパッドが準備された状態になります。一方はインナー用、もう一方はアウター用です。どちらも秘密鍵に基づいていますが、ハッシュ処理のためにわずかに変換されています。まずメッセージとインナーパッドを結合し、その結果をハッシュ化します。その後、そのハッシュ結果とアウターパッドを結合し、再度ハッシュ処理をおこないます。この二段階の処理により、署名は秘密鍵に固有のものとなり、改ざんが困難になります。
比喩的な説明
非常に重要な書類を送るために、セキュアな郵送システムを使用しているとします。このシステムには厳格な制約があり、すべての書類は処理される前に必ず同一サイズの封筒に封入されていなければなりません。ここでいう64バイトというブロックサイズは、この固定された封筒サイズにあたります。どれほどメッセージが長くても短くても、この正確なサイズに収まるように事前に整形されている必要があります。次に、秘密鍵を「あなたから送られたことを証明するための固有の証拠」として考えます。この秘密の情報を使って、書類を封印する前に2つの異なる保護層を作成する必要があります。この2つの層がインナーパッドとアウターパッドです。
これらは、メッセージを異なる検証段階で包み込むための、特別に設計された2種類のセキュリティスリーブに例えることができます。それぞれのスリーブは、封筒サイズに一致するように正確に作られています。ここでカスタマイズの工程がおこなわれます。秘密鍵の一部が、各スリーブの各位置に対して定められたパターンと組み合わされます。外側のスリーブには内側のスリーブとは異なるパターンが適用されます。この混合プロセスは、2種類の異なるスタンプパターンを用いて、それぞれのスリーブにあなた固有の印を刻み込む操作に似ています。最終的な結果として、パターンは固定され、かつ秘密の鍵によってのみ生成可能な状態になります。そのため、この仕組みはあなただけと受信者だけが再現できるものになります。
署名の生成
この段階では、先ほど生成したインナーパッドとアウターパッドを用いて実際の署名を生成します。処理はインナーハッシュとアウターハッシュという2つの主要なハッシュステップで構成されます。まず、署名対象のメッセージとインナーパッドを結合し、そのデータをハッシュ化します。この結果がインナーハッシュになります。次に、このインナーハッシュとアウターパッドを結合し、新しいデータとして再度ハッシュ処理をおこないます。最終的に得られる値がファイナルハッシュであり、これが署名として機能します。この値は、元のメッセージと秘密鍵を一意に表現する安全な識別子になります。
最後に、このハッシュ値はバイト列から可読形式へ変換されます。通常は16進数形式が用いられます。これは秘密のコードを文字列として表現可能な形に変換する操作にあたります。この文字列がAPIリクエストの署名として使用されます。サーバー側はこの署名を用いて、リクエストが本当に秘密鍵の保持者から送信されたものであるか、また通信途中で改ざんされていないかを検証します。このプロセスにより、メッセージと秘密鍵が結び付けられた安全な「デジタルシール」が完成します。
例:
//+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- string message = "71772777"; string key = "ABCDER2y8VAzqopxzLVEhVYABCDEV2AxIYLueComud3aSEez8Z8fvgHPZTXABCDE"; uchar uMsg[], uKey[]; StringToCharArray(message, uMsg, 0, StringLen(message), CP_UTF8); StringToCharArray(key, uKey, 0, StringLen(key), CP_UTF8); int blockSize = 64; uchar ipad[], opad[]; ArrayResize(ipad, blockSize); ArrayResize(opad, blockSize); for(int i = 0; i < blockSize; i++) { ipad[i] = uKey[i] ^ 0x36; opad[i] = uKey[i] ^ 0x5C; } uchar innerData[], innerHash[]; ArrayCopy(innerData, ipad); ArrayCopy(innerData, uMsg, blockSize); CryptEncode(CRYPT_HASH_SHA256, innerData, uKey, innerHash); uchar outerData[], finalHash[]; ArrayCopy(outerData, opad); ArrayCopy(outerData, innerHash, blockSize); CryptEncode(CRYPT_HASH_SHA256, outerData, uKey, finalHash); string signature = ""; for(int i = 0; i < ArraySize(finalHash); i++) signature += StringFormat("%02x", finalHash[i]); Print(signature); }
解説:
まず最初のセクションで作成した2つの配列に、インナーデータとインナーハッシュの結果が保存されます。インナーデータは、インナーパッドと署名対象のメッセージを結合することで作成されます。インナーパッドを新しい配列へコピーした直後、メッセージを追加するためにArrayCopyメソッドが使用されます。これにより、メッセージがインナーパッドの後ろに正しいバイト順で配置されることが保証されます。この順序は非常に重要であり、1バイトでも誤ると完全に異なるハッシュ結果が生成されます。インナーデータの準備が完了すると、CryptEncode関数とSHA256アルゴリズム、そして秘密鍵を用いてインナーハッシュが計算されます。このインナーハッシュは、メッセージとインナーパッドを組み合わせた状態の安全な「指紋」として機能し、秘密鍵と強く結び付けられています。これはHMAC-SHA256の第一段階のハッシュ処理であり、最終的な署名がメッセージと秘密鍵の両方に依存することを保証します。
次に、アウターデータを準備します。この処理はインナーデータの準備と同様であり、まず配列を作成し、ArrayCopyを使用してアウターパッドをその配列へコピーします。その後、同じ配列の末尾にインナーハッシュを追加します。この時点で、アウターパッドとインナーハッシュが最終ハッシュ計算の入力データとなります。この順序もHMACの正しい動作のために厳密に維持される必要があります。アウターデータの準備が完了すると、再びCryptEncode関数を使用し、SHA256と秘密鍵を用いて最終ハッシュが計算されます。この最終ハッシュがHMAC-SHA256の署名となります。これは固定長のバイト配列であり、秘密鍵とメッセージを安全に結び付けた結果です。サーバーは同じ処理を再現することで、メッセージが秘密鍵を知る正当な送信者によって作成され、かつ改ざんされていないことを検証できます。
この最終ハッシュは、そのままインターネット上に送信できる形式ではありません。そのためプログラムは、署名という空のコンテナを用意し、利用可能な形に変換します。ハッシュの各バイトは2桁の16進数表現に変換され、その結果がコンテナに順番に格納されます。この処理をすべてのバイトに対して繰り返すことで、APIリクエストに付加可能な単一の整形された16進数文字列として署名が生成されます。最後に、この16進数形式の署名が出力されます。この署名をAPIリクエストに付加することで、リクエストの正当性が保証されます。秘密鍵は常にローカル環境に保持され、外部へ送信されることはありません。そのため、正しい秘密鍵を持つ者だけが一致する署名を生成できます。また、署名は逆算して秘密鍵を復元することができないため、ネットワーク上で傍受された場合でも安全性が保たれます。
比喩的な説明
このプロセスは、友人に「鍵付きの宝箱」を送る状況として考えることができます。最初のステップは箱の内部準備です。メッセージを入力した後、秘密鍵に依存するパディング(内側のパッド)を適用して処理します。これはインナーハッシュを生成する処理にあたり、メッセージとインナーパッドが秘密と結び付けられ、偽造できない形になります。その後、メッセージとインナーパッドを用いて、特別なハッシュパターンを生成することでデータを固定化します。インナーハッシュは、このロックパターンとして表現されます。複雑な鍵と同様に、メッセージやパッドのいずれかにわずかな変更が加えられるだけで、まったく異なるパターンが生成されるため、誰かがこっそり内容を改ざんすることは不可能になります。
インナーロックが配置された後、箱の外側部分が準備されます。外側には、追加のセキュリティ層として秘密鍵から生成されたカバーが加えられ、その後にインナーロックが組み込まれます。これはアウターデータの生成と同様の考え方であり、この二重構造によって、たとえ内部構造が見えていたとしても、秘密鍵なしでロックを再現することはできません。外側のロックを封印する工程、すなわちアウターハッシュの計算が最終段階になります。これにより署名が完成し、それは箱があなたから送られたものであり、内容が改ざんされていないことを証明する固有のマークとなります。この署名は安全に友人へ送ることができます。受け取った側は、自分が持つ秘密鍵を用いてロックを検証します。すべてが一致すれば、その箱が正当なものであり、途中で改ざんされていないことを確認できます。
最後に、このロックパターンはそのままでは扱いにくいため、誰でも利用できる形式、例えばラベル上のコードのような形に変換されます。これは英数字の列として表現されるため、そのままでは読み取りにくいためです。これが署名の16進数形式です。このコードをAPIリクエストに付加します。友人が箱の封印を確認して、それが本当にあなたから送られたものであるかを確かめるのと同様に、サーバーはこのコードを自身の計算結果と照合することで、正当性を検証します。
結論
本記事では、MQL5におけるAPI署名の作成方法について解説しました。インナーパッドとアウターパッドの仕組みを用いながら、メッセージと秘密鍵をハッシュ値へと変換し、機密情報を公開することなくリクエストの正当性を検証できる署名を生成する方法を学びました。また、各リクエストを一意にし、改ざんを防ぐために、タイムスタンプと秘密鍵が不可欠であることも確認しました。次回の記事の実装では、実際に署名を生成し、それを用いてBinanceへ機密データのリクエストを送信します。
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/20938
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。
ラリー・ウィリアムズの『市場の秘密』(第7回):Trade Day of the Week概念の実証研究
古典的な戦略を再構築する(第21回):ボリンジャーバンドとRSIのアンサンブル戦略の発見
Market Memory Zonesインジケーターの開発:価格が戻りやすい領域
プライスアクション分析ツールキットの開発(第55回):CPIミニローソク足オーバーレイによるバー内圧力の可視化
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索