我有使用以下逻辑验证哈希的 C# 服务器代码。我无法控制服务器代码,也无法更改它。
string key = "xZBn34L4myg[...]cebvr7A==";
var hmacSha256 = new System.Security.Cryptography.HMACSHA256 { Key = Convert.FromBase64String(key) };
byte[] hashPayLoad = hmacSha256.ComputeHash(System.Text.Encoding.UTF8.GetBytes(payLoad));
string signature = Convert.ToBase64String(hashPayLoad);
对于消息“信息“上面的代码计算JbE9u3X+bKwYizXNcrWTImjooqg+a4Lh9hj4yQHMoHs=
为签名。
在客户端的 bash 脚本中,我想使用 OpenSSL 创建一条服务器应该验证的消息:
signature=$(echo -n "message" | openssl dgst -sha256 -hmac xZBn34L4myg[...]cebvr7A== -binary | base64)
该操作的结果是 的签名bFHvntwvCI6eJqTdoyryxgtPSwyUw/+a79rSvvKs5vE=
。
这与服务器版本不同。原因是服务器使用Convert.FromBase64String()
来获取密钥的字节,但 OpenSSL 对“-hmac”参数的解释不同。如果我可以控制服务器,我可以将其更改为Key = UTF8.GetBytes(key)
,最终会得到与客户端相同的哈希值。但遗憾的是,这是不可能的。
所以我的问题是:如何更改 bash 脚本以使其产生服务器的散列结果?
答案1
我简直不敢相信……我找到了答案。经过几个小时的尝试!
OpenSSL 将输入的密钥字符串视为二进制数据。然而,服务器将其解释为 base64 编码数据,对其进行解码,并使用生成的字节作为密钥。
这意味着,客户端还必须将密钥视为 base64 并对其进行解码。可以使用以下方法完成此操作:
echo -n "xZBn34L4myg[...]cebvr7A==" | base64 --decode > key.dat
该命令会将解码后的数据写入文件。
有趣的是,现在可以将文件内容传递给 openssl,并且显然它不会抱怨潜在的不可打印字符:
echo -n "message" | openssl dgst -sha256 -hmac "$(<./key.dat)" -binary | base64
或者,也可以不使用文件绕行来实现:
hashedSignature=$(echo -n "message" | openssl dgst -sha256 -hmac "$(echo -n "xZBn34L4myg[...]cebvr7A==" | base64 --decode)" -binary | base64)
根据@user1686的评论,上述解决方案可能会有问题:
请记住,命令行参数是 C 空终止字符串,因此最好确保 -hmac 键没有任何 0x00 字节,否则它会在该位置被截断。使用 -macopt hexkey: 可避免此问题
为了解决这个问题,这里有一个使用六角扳手的版本:
hashedSignature=$(echo -n "message" | openssl dgst -sha256 -mac hmac -macopt hexkey:$(echo -n "xZBn34L4myg[...]cebvr7A==" | base64 --decode | hexdump -v -e '/1 "%02x"') -binary | base64)
我必须说,这并没有变得更具可读性:-) 我推测,0x00 字节的问题也适用于基于文件的解决方案。
全部将生成预期的哈希值JbE9u3X+bKwYizXNcrWTImjooqg+a4Lh9hj4yQHMoHs=