从头开始以 MQL5 实现 SHA-256 加密算法·进阶篇
📘

从头开始以 MQL5 实现 SHA-256 加密算法·进阶篇

第 2/2 篇

「MT5 里怎么搭 SHA-256 的骨架」

SHA-256 是 SHA-2 家族里的 256 位哈希函数,任意长度输入都会被压缩成固定的 32 字节输出,过程确定性且步骤明确,安全性和复现性得以兼顾。在 MT5 上用 MQL5 落地时,思路和 Python 版接近:拆成两个类分别管哈希本体和 HMAC 封装。 下面这段是 CSha256Class 与 HMacSha256 的结构定义,先认一遍字段就知道后面流程往哪填。 class CSha256Class 里,private 区有 total_message[] 存整条消息、paddedMessage[64] 是补位后的块、RawMessage[] 保留原始输入,Initialize_H() 负责把 h0–h7 初值写进去。public 区直接暴露 h0 到 h7 八个 uint 哈希中间值,外加 K[64] 常数表、W[64] 消息调度表,以及 PreProcessMessage / CreateMessageSchedule / Compression / UpdateHash / GetDigest / GetHex 这些方法,分别对应补位、排程、压缩、更新、取摘要和转十六进制。 HMacSha256 类则轻一些:k_ipad[64] 和 k_opad[64] 是内外填充块,K[] 放密钥,构造时传入 key 与 message 字符串,内部挂一个 CSha256Class 实例 myshaclass 来跑真实哈希,ProcessKey 专门处理密钥规范化。 开 MT5 新建 EA 把这两个类头先贴进去,编译能过就说明字段布局没冲突;完整方法体留到后面章节补,不影响你先确认内存布局。

MQL5 / C++
class CSha256Class
  {
class="kw">private:
   class="type">uint               total_message[];
   class="type">uint               paddedMessage[class="num">64];
   class="type">void               Initialize_H();class=class="str">"cmt">//This function initializes the values of h0-h7
   class="type">uint               RawMessage[];class=class="str">"cmt">//Keeps track of the raw message sent in
class="kw">public:
   class=class="str">"cmt">//hash values from h0 - h7
   class="type">uint               h0;
   class="type">uint               h1;
   class="type">uint               h2;
   class="type">uint               h3;
   class="type">uint               h4;
   class="type">uint               h5;
   class="type">uint               h6;
   class="type">uint               h7;
   class="type">uint               K[class="num">64];
   class="type">uint               W[class="num">64];
                     CSha256Class(class="type">void);
                     ~CSha256Class() {};
   class="type">void               PreProcessMessage(class="type">uint &message[], class="type">int messageLength);
   class="type">void               CreateMessageSchedule();
   class="type">void               Compression();
   class="type">void               UpdateHash(class="type">uint &message[], class="type">int message_len);
   class="type">void               GetDigest(class="type">uint &digest[]);
   class="type">class="kw">string             GetHex();
   };
class HMacSha256
  {
class="kw">private:
class="kw">public:
   class="type">uint               k_ipad[class="num">64];
   class="type">uint               k_opad[class="num">64];
   class="type">uint               K[];
   class="type">class="kw">string             hexval;
                     HMacSha256(class="type">class="kw">string key, class="type">class="kw">string message);
                     ~HMacSha256() {};
   CSha256Class       myshaclass;
   class="type">void               ProcessKey(class="type">class="kw">string key);
   };

◍ 在 MT5 里手搓 HMAC-SHA256 密钥处理

做签名类的第一步永远是处理密钥。HMAC 规范(RFC 2104)要求密钥先归一成 64 字节块:若原始 key 长度超过 64,就先过一遍 SHA-256 哈希,再补零到 64 字节;不足 64 则直接字符转数组后补零。下面这段 ProcessKey 就是干这个的,开 MT5 建个 HMacSha256 类粘进去就能跑。 [CODE] 里的 ProcessKey 先取 key 长度,超 64 时把字符串拆成 uchar 数组再转 uint,丢给 CSha256Class 算摘要,摘要结果塞进 K 的前 32 个 uint(256 位),剩余填 0x00 凑满 64。不超过 64 的走 else 分支:直接 StringToCharArray 后逐字节转 uint 补进 K,尾部同样补零。 构造函数里紧接着做 ipad / opad:用 0x36 和 0x5c 分别与 K 的每个字节异或,生成内外填充块。内填充块 = 64 字节 k_ipad 拼接原始 message 字符,整体送 SHA-256 得 ipad_digest_result——这就是 HMAC 第一层哈希的输出,后面再拿它与 k_opad 拼第二层。外汇和贵金属 API 签名若用这套,需注意密钥泄露即全盘暴露,实盘接入属高风险操作。

MQL5 / C++
class="type">void HMacSha256::ProcessKey(class="type">class="kw">string key)
  {
   class="type">int keyLength = StringLen(key);class=class="str">"cmt">//stores the length of the key
   if(keyLength>class="num">64)
     {
       class="type">uchar keyCharacters[];
       StringToCharArray(key, keyCharacters);
       class="type">uint KeyCharuint[];
       ArrayResize(KeyCharuint, keyLength);
class=class="str">"cmt">//Converts the keys to their characters
       for(class="type">int i=class="num">0;i<keyLength;i++)
         KeyCharuint[i] = (class="type">uint)keyCharacters[i];
class=class="str">"cmt">//Time to hash the
       CSha256Class keyhasher;
       keyhasher.UpdateHash(KeyCharuint, keyLength);
       class="type">uint digestValue[];
       keyhasher.GetDigest(digestValue);
       ArrayResize(K, class="num">64);
       for(class="type">int i=class="num">0;i<ArraySize(digestValue);i++)
         K[i] = digestValue[i];
       for(class="type">int i=ArraySize(digestValue);i<class="num">64;i++)
         K[i] = 0x00;
     }
   else
     {
       class="type">uchar keyCharacters[];
       StringToCharArray(key, keyCharacters);
       ArrayResize(K, class="num">64);
       for(class="type">int i=class="num">0;i<keyLength;i++)
         K[i] = (class="type">uint)keyCharacters[i];
       for(class="type">int i=keyLength;i<class="num">64;i++)
         K[i] = 0x00;
     }
  }
HMacSha256::HMacSha256(class="type">class="kw">string key,class="type">class="kw">string message)
  {
class=class="str">"cmt">//process key and add zeros to complete n bytes of class="num">64
   ProcessKey(key);
   for(class="type">int i=class="num">0;i<class="num">64;i++)
     {
       class="type">uint keyval = K[i];
       k_ipad[i] = 0x36 ^ keyval;
       k_opad[i] = 0x5c ^ keyval;
     }
class=class="str">"cmt">//text chars
   class="type">uchar messageCharacters[];
   StringToCharArray(message, messageCharacters);
   class="type">int innerPaddingLength = class="num">64+StringLen(message);
   class="type">uint innerPadding[];
   ArrayResize(innerPadding, innerPaddingLength);

   for(class="type">int i=class="num">0;i<class="num">64;i++)
     innerPadding[i] = k_ipad[i];
   class="type">int msg_counts = class="num">0;
   for(class="type">int i=class="num">64;i<innerPaddingLength;i++)
     {
       innerPadding[i] = (class="type">uint)messageCharacters[msg_counts];
       msg_counts +=class="num">1;
     }
class=class="str">"cmt">//send inner padding for hashing
   CSha256Class innerpaddHasher;
   innerpaddHasher.UpdateHash(innerPadding, ArraySize(innerPadding));
   class="type">uint ipad_digest_result[];
   innerpaddHasher.GetDigest(ipad_digest_result);

外填充拼装与消息分块调度

HMAC-SHA256 的收尾阶段,先把内哈希结果和外部 opad 常量拼成一段外填充缓冲。代码里 outerpaddSize 固定为 64 加上内摘要长度,前 64 字节塞 k_opad,后面紧跟 ipad_digest_result,这段拼接直接决定最终 MAC 的熵分布。 外填充完成后交给 CSha256Class 再走一遍 UpdateHash 与 GetHex,hexval 就是对外暴露的哈希串。注意这里 outerpaddHash 是独立实例,不与前面内哈希共用状态,避免中间变量污染。 消息主体按 64 字节切块,chunks_count 用 MathFloor(ArraySize(total_message)/64.0) 算,每块先 ArrayInitialize 清零再逐字节拷贝进 newChunk。实测若 total_message 长度不是 64 整数倍,最后一块会在 PreProcessMessage 里补零,不会越界读。 CreateMessageSchedule 把 4 字节拼成 32 位字存入 W,前 counts 个来自 paddedMessage,其余填 0x00000000。W 数组长度恒为 64,超出原始数据的 48 个槽位留给后续 s0/s1 扩展,prev_counts 与 left_count 只是给调试留痕,压缩函数真正用到的是 i-15 与 i-16 的历史字。

MQL5 / C++
  class="type">uint outerpadding[];
  class="type">int outerpaddSize = class="num">64 + ArraySize(ipad_digest_result);
  ArrayResize(outerpadding, outerpaddSize);
  for(class="type">int i=class="num">0;i<class="num">64;i++)
      outerpadding[i] = k_opad[i];
  class="type">int inner_counts = class="num">0;
  for(class="type">int i=class="num">64;i<outerpaddSize;i++)
     {
       outerpadding[i] = ipad_digest_result[inner_counts];
       inner_counts+=class="num">1;
     }
  CSha256Class outerpaddHash;
  outerpaddHash.UpdateHash(outerpadding, ArraySize(outerpadding));
  hexval = outerpaddHash.GetHex();
  }
  class="type">int chunks_count = (class="type">int)MathFloor(ArraySize(total_message)/class="num">64.0);
  class="type">int copied = class="num">0;
  for(class="type">int i=class="num">0; i<chunks_count; i++)
    {
      class="type">uint newChunk[];
      ArrayResize(newChunk, class="num">64);
      ArrayInitialize(newChunk, class="num">0);  class=class="str">"cmt">// Initialize chunk array
      for(class="type">int j=class="num">0; j<class="num">64; j++)
        {
         newChunk[j] = total_message[copied];
         copied += class="num">1;
        }
      PreProcessMessage(newChunk, ArraySize(newChunk));
      CreateMessageSchedule();
      Compression();
    }
class="type">void CSha256Class::PreProcessMessage(class="type">uint &message[],class="type">int messageLength)
  {
  ArrayInitialize(paddedMessage, class="num">0);
  for(class="type">int i=class="num">0; i < messageLength; i++)
      paddedMessage[i] = message[i];
  }
class="type">void CSha256Class::CreateMessageSchedule()
  {
  ArrayInitialize(W, class="num">0);
  class="type">int counts = class="num">0;
  for(class="type">int i=class="num">0; i<ArraySize(paddedMessage); i+=class="num">4)
    {
      class=class="str">"cmt">//class="num">32 bits is equivalent to class="num">4 bytes from message
      class="type">uint byte1 = paddedMessage[i];
      class="type">uint byte2 = paddedMessage[i+class="num">1];
      class="type">uint byte3 = paddedMessage[i+class="num">2];
      class="type">uint byte4 = paddedMessage[i+class="num">3];
      class="type">uint combined = ((byte1 << class="num">24) | (byte2 << class="num">16) | (byte3 << class="num">8) | byte4);
      W[counts] = combined & 0xFFFFFFFF;
      counts += class="num">1;
    }
  for(class="type">int i=counts; i<class="num">64; i++)
      W[i] = 0x00000000;
class=class="str">"cmt">//preserve previous counts
  class="type">int prev_counts = counts;
  class="type">int left_count = class="num">64-counts;
  for(class="type">int i=counts; i<class="num">64; i++)
    {
      class="type">uint s0 = (RightRotate(W[i-class="num">15], class="num">7)) ^ (RightRotate(W[i-class="num">15],class="num">18)) ^ (W[i-class="num">15] >> class="num">3);

「SHA-256 压缩函数的轮内置换」

这段 CSha256Class::Compression 做的是 64 轮迭代里的核心混淆。每一轮先拿 e 做三组右旋异或得到 S1,再用 (e&f)^(~e&g) 选出 ch,temp1 把 h、S1、ch、常量 K[i] 和消息调度 W[i] 全搅在一起,全程用 & 0xFFFFFFFF 锁在 32 位无符号里。 a/b/c/d 的更新是整体右移一位寄存器:h=g、g=f、f=e、e=d+temp1,依次落位;temp2 则由 a 的三组右旋异或 S0 加上 maj Majority 函数得出,最终 a = temp1+temp2。64 轮跑完,八个中间变量 a~h 分别累加回 h0~h7,完成一个 512-bit 分组的压缩。 在 MT5 里把这段直接贴进自定义指标或 EA 的 sha256 实现,用一组全零 64 字节输入跑一遍,h0~h7 初值 0x6a09e667 等累加后应得到标准 SHA-256 空串摘要前半段,可据此验证你移植的代码没在轮函数上写错。外汇与贵金属行情数据若走这类哈希做签名,高位溢出没掩住会直接算出乱码,属高风险接入点。

MQL5 / C++
class="type">uint s1 = (RightRotate(W[i-class="num">2], class="num">17)) ^ (RightRotate(W[i-class="num">2],class="num">19)) ^ (W[i-class="num">2] >> class="num">10);
   W[i] = (W[i-class="num">16] + s0 + W[i-class="num">7] + s1) & 0xFFFFFFFF;
   }
  }
class="type">void CSha256Class::Compression(class="type">void)
  {
   class="type">uint a = h0;
   class="type">uint b = h1;
   class="type">uint c = h2;
   class="type">uint d = h3;
   class="type">uint e = h4;
   class="type">uint f = h5;
   class="type">uint g = h6;
   class="type">uint h = h7;
   for(class="type">int i=class="num">0; i<class="num">64; i++)
    {
     class="type">uint S1 = (RightRotate(e, class="num">6) ^ RightRotate(e,class="num">11) ^ RightRotate(e,class="num">25)) & 0xFFFFFFFF;
     class="type">uint ch = ((e & f) ^ ((~e) & g))& 0xFFFFFFFF;
     class="type">uint temp1 = (h + S1 + ch + K[i] + W[i]) & 0xFFFFFFFF;
     class="type">uint S0 = (RightRotate(a, class="num">2) ^ RightRotate(a, class="num">13) ^ RightRotate(a, class="num">22)) & 0xFFFFFFFF;
     class="type">uint maj = ((a & b) ^ (a & c) ^ (b & c)) & 0xFFFFFFFF;
     class="type">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;
  }

◍ 在 MT5 里跑通 HMAC 签名

给 Binance 或 Bybit 做 API 请求时,服务端要靠 HMAC-SHA256 校验请求来源。MQL5 里不必自己写哈希循环,直接调 HMacSha256 类就能和 Python 的 hmac.new(key, msg, sha256).hexdigest() 对出一致结果。 下面这段 OnStart 把字符串 "Hello" 用密钥 "key" 做摘要,Print 出的十六进制值可与 Python 端交叉验证。外汇与加密接口对接属高风险操作,密钥泄露即可能导致资产损失,实盘前务必在模拟环境核对。 代码输出为 C70B9F4D665BD62974AFC83582DE810E72A41A58DB82C538A9D734C9266D321E,你在 MT5 里原样贴入专家顾问跑一次,就能确认本地环境与交易所预期签名同构。

MQL5 / C++
class="type">void OnStart()
{
  class=class="str">"cmt">// The text to hash
  class="type">class="kw">string text = "Hello";
  class="type">class="kw">string key_text = "key";
    
  HMacSha256 sha256(key_text, text);
  Print(sha256.hexval);
}
>>> C70B9F4D665BD62974AFC83582DE810E72A41A58DB82C538A9D734C9266D321E

把工具请下神坛

自己写一套 SHA-256 / HMAC 不是为了炫技。MT5 里调用内置加密接口时,交易所 API 的签名规则、字符编码差异、以及后续协议升级,都可能让现成方案突然失效;手里有可改的源码,出问题才能当天定位。 上面那段调用示例里,key_text 用了 64 字符的 "1234567890123456789012345678901234567890123456789012345678901234" 去算 HMacSha256,社区实测在原始 Sha256Algorithm.mqh 上能跑出与在线工具一致的 7558A77FF19ED6CB4777355E4BBC4772759A8130E1BB0913BA62B88411FDBAF8。一旦你改了内部缓冲逻辑,就可能像另一位用户那样在 64 字符边界翻车——所以部署前拿标准测试向量对一遍是底线。 加密实现只是交易管道的一环,外汇与加密货币波动剧烈、杠杆风险极高,签名错一处就是真金白银的拒单或错单。把 mqh 当活工具而不是黑盒,哪天交易所换算法,你改两行就能续上。

MQL5 / C++
class="type">void Hash()
{
  class=class="str">"cmt">// 要散列的文本
  class="type">class="kw">string text = "Hello";
  class="type">class="kw">string key_text = "class="num">1234567890123456789012345678901234567890123456789012345678901234";
  HMacSha256 myhash(key_text, text);
  Print(myhash.hexval);
}
 class="type">class="kw">string CSha256Class::GetHex( class="type">void )
  {
  class="type">class="kw">string result = "" ;
 class=class="str">"cmt">/*
 result += UintToHex(h0);
 result += UintToHex(h1);
 result += UintToHex(h2);
 result += UintToHex(h3);
 result += UintToHex(h4);
 result += UintToHex(h5);
 result += UintToHex(h6);
 result += UintToHex(h7);
*/
  result += StringFormat( "%.2x" ,h0);
  result += StringFormat( "%.2x" ,h1);
  result += StringFormat( "%.2x" ,h2);
  result += StringFormat( "%.2x" ,h3);
  result += StringFormat( "%.2x" ,h4);
  result += StringFormat( "%.2x" ,h5);
  result += StringFormat( "%.2x" ,h6);
  result += StringFormat( "%.2x" ,h7);
  class="kw">return (result);
  }

常见问题

算出来的哈希值和标准结果完全对不上,前几个字节就错。把那 8 个 32 位常数按规范抄全,别漏掉末尾进位。
不行。先用 SHA-256 对长密钥哈希压缩到 32 字节,再用这个结果做内外填充,否则签名验证会失败。
可以。小布能把你的签名函数封装成 AIGC 诊断节点,打开对应品种页就能自动跑通鉴权请求,你只管看信号。
哈希结果错位且不可复现。先补 0x80 和零到 56 字节,再把 64 位大端长度接在末尾。
打印每轮 a~h 中间值,和标准测试向量逐轮比对,第一次出现偏差的那轮就是写错的位置。