Ballerina生成HMAC SHA256签名与预期结果不符的问题排查
问题
我有一个供应商通过HMAC SHA256对发送到Webhook的请求做身份验证,但用Ballerina验证签名时始终匹配不上。为排查问题,我用虚构的私钥和payload在CodeBeautify上生成了签名,同时在Ballerina里实现了对应的签名生成逻辑,但运行后两者签名完全不匹配:
- Ballerina生成的签名字节数组:
[33,1,109,19,199,246,135,242,0,50,145,48,243,133,137,180,127,109,85,52,231,239,247,107,22,220,97,67,65,131,121,172] - CodeBeautify生成的签名字节数组:
[57,54,57,52,100,101,101,51,55,101,57,50,50,49,53,52,56,102,48,51,53,97,99,97,54,52,99,52,99,97,51,48,51,97,50,48,98,102,99,57,49,54,54,49,100,55,50,51,51,100,55,97,51,101,53,50,53,99,55,49,48,102,99,52] - CodeBeautify生成的签名字符串:
9694dee37e9221548f035aca64c4ca303a20bfc91661d7233d7a3e525c710fc4
另外尝试转换Ballerina生成的字节数组为字符串时,会报错:error: FailedToDecodeBytes {"message":"array contains invalid UTF-8 byte value"},但CodeBeautify的字节数组可以正常还原为字符串。
附上我的Ballerina代码:
import ballerina/crypto; import ballerina/io; public function main() returns error? { // strGeneratedSignature produced with strHMACKey and jsonHMACMessage on CodeBeautify's HMAC tool string strHMACKey = "f785a0dfd2b63f06caf9205497c67777"; json jsonHMACMessage = {"message": "Hello Ballerina HMAC"}; string strGeneratedSignature = "9694dee37e9221548f035aca64c4ca303a20bfc91661d7233d7a3e525c710fc4"; byte[] generatedSignatureBytes = strGeneratedSignature.toBytes(); string strBody = jsonHMACMessage.toString(); byte[] data = strBody.toBytes(); byte[] hmacKeyBytes = strHMACKey.toBytes(); byte[] hmacSignatureBytes = check crypto:hmacSha256(data, hmacKeyBytes); io:println(`Ballerina-generated signature: `, hmacSignatureBytes); io:println(`CodeBeautify-generated signature: `, generatedSignatureBytes); string strRegeneratedSignature = check string:fromBytes(generatedSignatureBytes); // string strBallerinaSignature = check string:fromBytes(hmacSignatureBytes); io:println(`Regenerated CodeBeautify Signature: `, strRegeneratedSignature); // io:println(`Ballerina Signature: `, strBallerinaSignature); }
解决方案
问题出在签名的编码格式不匹配,核心有两个点:
HMAC签名本质是二进制数据,CodeBeautify返回的是它的十六进制字符串表示
- Ballerina的
crypto:hmacSha256直接返回原始二进制字节数组,这些字节不是合法的UTF-8字符,所以无法直接用string:fromBytes转换。 - 你把CodeBeautify的十六进制字符串直接转成字节数组,相当于把字符串的每个字符(比如'9'、'6')转成对应的ASCII字节,这和原始的HMAC二进制数据完全不是一回事。
- Ballerina的
JSON序列化的格式差异
- Ballerina的
json.toString()生成的字符串可能和CodeBeautify使用的序列化格式有细微差别(比如空格、引号格式),这也会导致HMAC输入数据不一致,最终签名不同。
- Ballerina的
修正后的代码
要让两者匹配,需要做两件事:
- 将Ballerina生成的二进制签名转换为十六进制字符串,再和CodeBeautify的结果对比。
- 确保JSON序列化的格式和CodeBeautify一致(比如去掉多余空格)。
import ballerina/crypto; import ballerina/io; import ballerina/encoding; public function main() returns error? { string strHMACKey = "f785a0dfd2b63f06caf9205497c67777"; json jsonHMACMessage = {"message": "Hello Ballerina HMAC"}; string strGeneratedSignature = "9694dee37e9221548f035aca64c4ca303a20bfc91661d7233d7a3e525c710fc4"; // 确保JSON序列化格式和CodeBeautify一致(无空格紧凑格式) string strBody = check jsonHMACMessage.toJsonString(); byte[] data = strBody.toBytes(); byte[] hmacKeyBytes = strHMACKey.toBytes(); byte[] hmacSignatureBytes = check crypto:hmacSha256(data, hmacKeyBytes); // 将Ballerina生成的二进制签名转为十六进制字符串 string ballerinaSignatureHex = encoding:hexEncode(hmacSignatureBytes); io:println(`Ballerina-generated hex signature: ${ballerinaSignatureHex}`); io:println(`CodeBeautify-generated hex signature: ${strGeneratedSignature}`); // 对比两个签名 if ballerinaSignatureHex == strGeneratedSignature { io:println("签名匹配成功!"); } else { io:println("签名不匹配!"); } }
关键说明
- 使用
encoding:hexEncode()将二进制签名转为十六进制字符串,这才是和CodeBeautify结果对应的格式。 - 用
json.toJsonString()替代toString(),生成紧凑无空格的JSON字符串,确保输入数据和CodeBeautify完全一致。 - 原始二进制签名不是UTF-8字符串,所以不能直接用
string:fromBytes转换,必须用十六进制或Base64这类编码格式来表示。
内容的提问来源于stack exchange,提问作者Bennett Reddin
相关产品推荐
相关产品推荐

