从头开始以 MQL5 实现 SHA-256 加密算法
MQL5 的进步能够无需第三方解决方案,即可高效实现加密算法,从而强化安全、优化和稳定性。自定义实现的益处包括特定于任务的调整、消除漏洞、以及跨环境的一致表现。密码学现在是 API、业务验证、和数据完整性不可或缺的一部分,需要开发人员深入理解这些算法。密码学和交易系统的融合已成为现代平台发展的必经之路。
API 签名兼容性挑战
自定义 SHA-256 实现的主要驱动因素在于 MQL5 的原生哈希函数,与加密货币兑换需求之间的基层不兼容。这种不兼容性表现在若干直接影响交易操作的紧要途径。
当交易平台向 Binance 或 Bybit 等加密货币交易所进行 API 调用时,它们必须生成完全符合交易所期望的加密签名。这些签名作为每个请求的真实性证明,确保订单和其它敏感操作来自授权的源头。然而,MQL5 的内置加密函数生成的签名,往往与其它编程语言的标准实现的签名不同。
发生这种签名不匹配是因为加密货币交易所期望根据特定标准生成签名,往往基于像是 Python 或 JavaScript 等语言的实现。MQL5 的原生函数虽然在一般用途上起作用,但或许处理哈希过程的某些方面时会有差异,譬如:
- 输入字节排序的处理
- 填充实现
- 字符编码处理
- 过渡值的内存表示
- 输入字符串中特殊字符的处理
这些差异会导致生产环境中的若干严重操作问题:
首先,API 请求会被交易所拒绝,因为签名与其预期值不匹配。这种拒绝发生在身份验证层,即在实际交易逻辑开始执行之前。考虑这样一个场景:其中交易算法识别到可盈利机会,并尝试下单。如果签名无效,交易所会立即拒绝请求,导致系统完全错失交易机会。
其二,身份验证失败在不同类型的 API 调用中持续发生。即使是简单操作,如提取账户余额、或检查订单状态也变得不可能,因为交易所无法验证请求的真实性。这会造成一个系统性问题,影响交易系统运作的各个层面。
其三,下单变成棘手问题。当尝试下单时,无效签名会阻止交易执行,潜在会导致:
- 错过计划交易的入场点
- 无法在需要时离场
- 风险管理操作失败
- 交易策略执行不完整
其四,消息验证变得不可靠。许多交易所对各种类型的消息和通知都用到基于哈希的验证。不一致的哈希输出意味着交易系统无法可靠地验证这些消息,可能会错过重要的更新、或依据未经验证的信息采取行动。
这些兼容性问题的影响超出了当下的技术问题。它们以微妙但重要的方式影响整个交易操作:
- 交易策略无法可靠执行
- 风险管理系统或许无法按预期运作
- 系统监控和日志记录变得不那么可靠
- 与交易所 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 函数库,由它执行 HMAC(基于哈希的消息身份验证代码)。这不仅是按密匙对文本进行哈希,还是一个组合过程:
- 将密匙补充(或切割)到所需的尺寸。
- 执行两个哈希步骤:首先按密钥处理消息,然后按密钥处理中间结果。
- 该代码中的 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 的内置函数提供了另一个显著优势。随着 MetaTrader 的演变和更新其平台,内置函数或许会以微妙的方式发生变化,从而影响签名生成。自定义实现在不同的 MetaTrader 版本中维持稳定,确保无论平台如何更新,行为一致。
面向未来的层面不仅是签名需求。加密货币交易所或许会引入基于 SHA-256 的新安全功能,或身份验证方法。而自定义实现,允许我们:
- 扩展基本 SHA-256 功能,以便支持新的安全功能
- 修改实现,以便配合新身份制程工作
- 无缝集成其它加密操作
- 在添加新功能的同时维持向后兼容性
甚至,自定义实现为将来可能需要实现其它加密函数提供了基础。为 SHA-256 开发的代码结构和优化技术,可作为实现其它哈希函数、或加密操作的模板。
研究这样一个场景:一家交易所引入了双哈希的新需求,或将 SHA-256 与另一种算法相结合。配合自定义实现,添加这些功能就变成了扩展现有代码的事情,而非尝试内置函数限制的变通。
这种可扩展性在快速发展的加密货币交易格局中尤其具有价值。新的交易范式、安全需求、和技术进步能够迅速出现,而灵活、可定制的实现可令交易系统随市场适应并演化。
结合性能优化和面向未来的能力,令定制 SHA-256 实现对于严肃的加密货币交易操作价值连城。它提供了在快速变化的市场中保持竞争优势所需的控制、灵活性和效率,同时确保随需求变化而实现长期生存的能力。
理解 SHA-256 算法
SHA-256 代表 256 位安全哈希算法,它是一种加密哈希函数,接受任意输入,并映射到固定大小的 256 位(32 个字节)输出。它属于 SHA-2 算法家族,在其过程中遵循一套系统性、且定义明确的步骤 — 这为安全性和判定性两者达成平衡。
MQL5 实现
我们详细验证每个组件。完整实现是在最后一节中提供,以供参考。在该实现中,我们有两个类,与 Python 实现类似,其中用到 HMAC 和 SHA256 类:
这是 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 进行首次哈希。
第 2 步:全面实现 HMAC。
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(); }
其它功能可在所附的完整代码中找到。
该实现的关键组件是 Hash 函数,它由 CSHa256Class 处理。
哈希函数涉及对文本的若干操作步骤。
第 1 步:数值的预处理。
我们需要确保整个数据是 512 位的倍数,因此我们应用了一些填充操作。- 将消息转换为其二进制形式。
- 将 1 附加到末尾。
- 添加零作为填充,直到数据长度达到 448 位(即 512 - 64 位)。这就确保了我们在 512 位的块中恰好剩下 64 位。换言之,我们填充消息,令其与 448 模 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 签名,创建一个 HMAC(基于哈希的消息验证码)实例,类似于 Python 的实现。
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
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
本文由网站的一位用户撰写,反映了他们的个人观点。MetaQuotes Ltd 不对所提供信息的准确性负责,也不对因使用所述解决方案、策略或建议而产生的任何后果负责。
迁移至 MQL5 Algo Forge(第 4 部分):使用版本和发布
基于LSTM的趋势预测在趋势跟踪策略中的应用
价格行为分析工具包开发(第11部分):基于Heikin Ashi(平均K线)信号的智能交易系统(EA)
MQL5 简介(第 10 部分):MQL5 中使用内置指标的初学者指南
如果 key_text 超过 64 个字符,则 HMacSha256 计算错误。应该如何纠正?
我目前的实现可以适应长度超过 64 个字符的密钥。您是否有任何特定的密钥和信息对无法正常工作?
我目前的实现可适应长度超过 64 个字符的密钥。您是否有任何特定的密钥和信息对无法工作?
字符串 text = "你好";
string key_text = "12345678901234567890123456789012345678901234567890123456789012345678901234";
https://www.devglan.com/online-tools/hmac-sha256-online-> 7558a77ff19ed6cb4777355e4bbc4772759a8130e1bb0913ba62b88411fdbaf8
测试脚本 -> 2025.02.27 22:28:43.792 Sha256TestFile (EURUSD,M5) 6d8ee9dc1d16261fd986fafb97d919584aa206ca76706fb3deccc63ab2b7f6b
字符串 text = "你好";
string key_text = "12345678901234567890123456789012345678901234567890123456789012345678901234";
https://www.devglan.com/online-tools/hmac-sha256-online-> 7558a77ff19ed6cb4777355e4bbc4772759a8130e1bb0913ba62b88411fdbaf8
测试脚本 -> 2025.02.27 22:28:43.792 Sha256TestFile (EURUSD,M5) 6d8ee9dc1d16261fd986fafb97d919584aa206ca76706fb3deccc63ab2b7f6b
我在终端上试了一下,结果与在线哈希工具相同:
2025.02.28 12:37:16.468 hashin_example_code (EURUSD,M5) 7558A77FF19ED6CB4777355E4BBC4772759A8130E1BB0913BA62B88411FDBAF8
您可能需要分享您的代码。
是的,使用原始的 Sha256Algorithm.mqh 可以正常工作。我做了一些改动,也许这就是它不能工作的原因?
很抱歉打扰您!