从头开始以 MQL5 实现 SHA-256 加密算法·进阶篇
「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 把这两个类头先贴进去,编译能过就说明字段布局没冲突;完整方法体留到后面章节补,不影响你先确认内存布局。
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 签名若用这套,需注意密钥泄露即全盘暴露,实盘接入属高风险操作。
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 的历史字。
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 空串摘要前半段,可据此验证你移植的代码没在轮函数上写错。外汇与贵金属行情数据若走这类哈希做签名,高位溢出没掩住会直接算出乱码,属高风险接入点。
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 里原样贴入专家顾问跑一次,就能确认本地环境与交易所预期签名同构。
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 当活工具而不是黑盒,哪天交易所换算法,你改两行就能续上。
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); }