Rust实现HMAC-SHA512签名无法得到Kraken API预期结果
问题原因
你的签名逻辑有3个核心错误,全部出在HMAC计算前的预处理环节,和HMAC库本身无关:
- 多余的URL编码操作:传入的
params已经是符合接口要求的x-www-form-urlencoded格式表单字符串,你对整个字符串做urlencode会把=、&这类合法表单字符转义为%3D、%26,完全改变了待哈希的内容 - nonce重复拼接:测试用例传入的
params已经包含了nonce=1616492376594字段,你额外把nonce的纯数字值拼在参数最前面,导致待哈希内容里nonce重复出现 - SHA256结果格式错误:你使用的
sha256::digest函数默认返回十六进制编码的字符串(长度64字节),但接口要求使用SHA256的原始32字节二进制摘要拼接URI,用十六进制字符串拼接会完全改变待HMAC内容,且二进制字节直接和UTF8字符串拼接还可能出现隐式编码错误
修正方案
- 移除对params的全局urlencode逻辑,表单参数按传入的原始格式使用即可
- 不要额外拼接独立nonce值,直接使用完整的POST表单字符串(包含nonce字段)作为SHA256输入
- 调用SHA256接口时获取原始二进制字节结果,不要用十六进制字符串
- 全程使用字节数组拼接待签名内容,不要强制转成UTF8字符串,避免非法二进制字节导致的内容错误
修正后可运行代码
现有Cargo.toml依赖不需要改动,sha256 1.0.3版本已提供digest_bytes方法返回原始字节数组:
[dependencies] urlencoding = "2.1.0" base64 = "0.13.0" ring = "0.16.20" sha256 = "1.0.3"
签名代码修正如下:
use ring::hmac; use sha256::digest_bytes; pub fn api_sign( private_key: Option<String>, nonce: u64, // 修正原拼写错误nonse -> nonce params: Option<String>, uri: String, ) -> hmac::Tag { let private_key = private_key.expect("Private key is not provided"); // 处理POST参数:如果params没带nonce就补上,带了直接用,不要做全局urlencode let post_data = match params { Some(p) => { if p.starts_with("nonce=") { p } else { format!("nonce={}&{}", nonce, p) } }, None => format!("nonce={}", nonce), }; // 计算SHA256原始字节,不要转十六进制字符串 let sha256_hash = digest_bytes(post_data.as_bytes()); // 拼接URI字节 + SHA256原始字节,全程用u8数组不要转String let mut hmac_data = uri.as_bytes().to_vec(); hmac_data.extend_from_slice(&sha256_hash); // 私钥base64解码后做HMAC-SHA512 let key = base64::decode(private_key).unwrap(); let key = hmac::Key::new(hmac::HMAC_SHA512, &key); let mut s_ctx = hmac::Context::with_key(&key); s_ctx.update(&hmac_data); s_ctx.sign() } #[cfg(test)] mod tests { use super::*; #[test] fn test_api_sign_0() { assert_eq!( base64::encode(api_sign( Some("kQH5HW/8p1uGOVjbgWA7FunAmGO8lsSUXNsu3eow76sz84Q18fWxnyRzBHCd3pd5nE9qa99HAZtuZuj6F1huXg==".to_string()), 1616492376594, Some("nonce=1616492376594&ordertype=limit&pair=XBTUSD&price=37500&type=buy&volume=1.25".to_string()), "/0/private/AddOrder".to_string() ).as_ref()), "4/dpxb3iT4tp/ZCVEwSnEsLxx0bqyhLpdfOpc6fn7OR8+UClSV5n9E6aSS8MPtnRfp32bAb0nmbRn6H8ndwLUQ==" ) } }
补充说明
如果后续需要对单个参数值做urlencode,只需要在拼接表单键值对的时候单独编码参数值即可,不要对拼接完成的整个表单字符串做编码,否则会破坏key=value&key2=value2的表单结构。
内容的提问来源于stack exchange,提问作者kek_mek
相关产品推荐
相关产品推荐

