Python 与 JavaScript 中的 HMAC SHA256
HMAC SHA256 in Python vs. JavaScript
我想重新实现某个API客户端,写在Python,在JavaScript。我无法复制 HMAC SHA256 签名功能。对于某些键,输出是相同的,但对于某些键,输出是不同的。在解码其 Base64 表示后,当密钥由可打印字符组成时,输出似乎是相同的。
Python
#!/usr/bin/env python3
import base64
import hashlib
import hmac
def sign_string(key_b64, to_sign):
key = base64.b64decode(key_b64)
signed_hmac_sha256 = hmac.HMAC(key, to_sign.encode(), hashlib.sha256)
digest = signed_hmac_sha256.digest()
return base64.b64encode(digest).decode()
print(sign_string('VGhpcyBpcyBhIHByaW50YWJsZSBzdHJpbmcuCg==', "my message"))
print(sign_string('dGhlIHdpbmQgb2YgTXQuIEZ1amkK', "my message"))
print(sign_string('pkmNNJw3alrpIBi5t5Pxuym00M211oN86IhLZVT8', "my message"))
JavaScript
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.9-1/crypto-js.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.9-1/hmac-sha256.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.9-1/enc-base64.min.js"></script>
<script>
function sign_string(key_b64, to_sign) {
key = atob(key_b64)
var hash = CryptoJS.HmacSHA256(to_sign, key);
var hashInBase64 = CryptoJS.enc.Base64.stringify(hash);
document.write(hashInBase64 + '<br>');
}
sign_string('VGhpcyBpcyBhIHByaW50YWJsZSBzdHJpbmcuCg==', "my message")
sign_string('dGhlIHdpbmQgb2YgTXQuIEZ1amkK', "my message")
sign_string('pkmNNJw3alrpIBi5t5Pxuym00M211oN86IhLZVT8', "my message")
</script>
产出
Python
TdhfUQfym16HyWQ8wxQeNVvJKr/tp5rLKHYQSpURLpw=
pQ5NzK3KIWjqc75AXBvWgLK8X0kZvjRHXrLAdxIN+Bk=
8srAvMucCd91CWI7DeCFjxJrEYllaaH63wmVlMk0W+I=
JavaScript
TdhfUQfym16HyWQ8wxQeNVvJKr/tp5rLKHYQSpURLpw=
pQ5NzK3KIWjqc75AXBvWgLK8X0kZvjRHXrLAdxIN+Bk=
31QxOpifnpFUpx/sn336ZmmjkYbLlNrs8NP9om6nPeY=
正如你所见,前两个是一样的,而最后一个是不同的。
如何更改 JavaScript 代码使其与 python 代码的行为相同?
在第三个示例中,您在 python 和 JavaScript 版本中使用了不同的参数。
在 python 中:
sign_string('xTsHZGfWUmnIpSu+TaVraECU88O3j9qVjlwTWGb/C8k=', "我的留言")
在 JavaScript 中:
sign_string('pkmNNJw3alrpIBi5t5Pxuym00M211oN86IhLZVT8', "我的留言")
您尝试提供给 CryptoJs 的 base64 编码机密不代表 CryptoJS 需要的有效 UTF-8 字符串。您可以使用 this tool 来检查有效性。 atob()
与编码无关,只是逐字节转换,不检查它是否是有效的 UTF-8。
这里我用 CryptoJS 自己的解码器对 base64 密码进行了解码,它抛出一个错误,说它是无效的 UTF-8:
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.9-1/crypto-js.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.9-1/hmac-sha256.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.9-1/enc-base64.min.js"></script>
<script>
function sign_string(key_b64, to_sign) {
var key = CryptoJS.enc.Base64.parse(key_b64).toString(CryptoJS.enc.Utf8);
var hash = CryptoJS.HmacSHA256(to_sign, key);
var hashInBase64 = CryptoJS.enc.Base64.stringify(hash);
document.write(hashInBase64 + '<br>');
}
sign_string('VGhpcyBpcyBhIHByaW50YWJsZSBzdHJpbmcuCg==', "my message")
sign_string('dGhlIHdpbmQgb2YgTXQuIEZ1amkK', "my message")
sign_string('pkmNNJw3alrpIBi5t5Pxuym00M211oN86IhLZVT8', "my message")
</script>
我还找到了一种使用原始字节作为密钥的方法。这适用于最后一个键,但不适用于前两个键。
var key = CryptoJS.enc.Hex.parse(toHex(atob(key_b64)));
现在,如果您将这两种方法结合起来,您就可以获得真正的解决方案。最终代码给出与 python:
相同的输出
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.9-1/crypto-js.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.9-1/hmac-sha256.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.9-1/enc-base64.min.js"></script>
<script>
function sign_string(key_b64, to_sign) {
try {
var key = CryptoJS.enc.Base64.parse(key_b64).toString(CryptoJS.enc.Utf8);
}
catch {
var key = CryptoJS.enc.Hex.parse(toHex(atob(key_b64)));
}
var hash = CryptoJS.HmacSHA256(to_sign, key);
var hashInBase64 = CryptoJS.enc.Base64.stringify(hash);
document.write(hashInBase64 + '<br>');
}
function toHex(str) {
var result = '';
for (var i=0; i<str.length; i++) {
if (str.charCodeAt(i).toString(16).length === 1) {
result += '0' + str.charCodeAt(i).toString(16);
} else {
result += str.charCodeAt(i).toString(16);
}
}
return result;
}
sign_string('VGhpcyBpcyBhIHByaW50YWJsZSBzdHJpbmcuCg==', "my message")
sign_string('dGhlIHdpbmQgb2YgTXQuIEZ1amkK', "my message")
sign_string('pkmNNJw3alrpIBi5t5Pxuym00M211oN86IhLZVT8', "my message")
sign_string('xTsHZGfWUmnIpSu+TaVraECU88O3j9qVjlwTWGb/C8k=', "my message")
</script>
我想重新实现某个API客户端,写在Python,在JavaScript。我无法复制 HMAC SHA256 签名功能。对于某些键,输出是相同的,但对于某些键,输出是不同的。在解码其 Base64 表示后,当密钥由可打印字符组成时,输出似乎是相同的。
Python
#!/usr/bin/env python3
import base64
import hashlib
import hmac
def sign_string(key_b64, to_sign):
key = base64.b64decode(key_b64)
signed_hmac_sha256 = hmac.HMAC(key, to_sign.encode(), hashlib.sha256)
digest = signed_hmac_sha256.digest()
return base64.b64encode(digest).decode()
print(sign_string('VGhpcyBpcyBhIHByaW50YWJsZSBzdHJpbmcuCg==', "my message"))
print(sign_string('dGhlIHdpbmQgb2YgTXQuIEZ1amkK', "my message"))
print(sign_string('pkmNNJw3alrpIBi5t5Pxuym00M211oN86IhLZVT8', "my message"))
JavaScript
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.9-1/crypto-js.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.9-1/hmac-sha256.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.9-1/enc-base64.min.js"></script>
<script>
function sign_string(key_b64, to_sign) {
key = atob(key_b64)
var hash = CryptoJS.HmacSHA256(to_sign, key);
var hashInBase64 = CryptoJS.enc.Base64.stringify(hash);
document.write(hashInBase64 + '<br>');
}
sign_string('VGhpcyBpcyBhIHByaW50YWJsZSBzdHJpbmcuCg==', "my message")
sign_string('dGhlIHdpbmQgb2YgTXQuIEZ1amkK', "my message")
sign_string('pkmNNJw3alrpIBi5t5Pxuym00M211oN86IhLZVT8', "my message")
</script>
产出
Python
TdhfUQfym16HyWQ8wxQeNVvJKr/tp5rLKHYQSpURLpw=
pQ5NzK3KIWjqc75AXBvWgLK8X0kZvjRHXrLAdxIN+Bk=
8srAvMucCd91CWI7DeCFjxJrEYllaaH63wmVlMk0W+I=
JavaScript
TdhfUQfym16HyWQ8wxQeNVvJKr/tp5rLKHYQSpURLpw=
pQ5NzK3KIWjqc75AXBvWgLK8X0kZvjRHXrLAdxIN+Bk=
31QxOpifnpFUpx/sn336ZmmjkYbLlNrs8NP9om6nPeY=
正如你所见,前两个是一样的,而最后一个是不同的。
如何更改 JavaScript 代码使其与 python 代码的行为相同?
在第三个示例中,您在 python 和 JavaScript 版本中使用了不同的参数。 在 python 中: sign_string('xTsHZGfWUmnIpSu+TaVraECU88O3j9qVjlwTWGb/C8k=', "我的留言") 在 JavaScript 中: sign_string('pkmNNJw3alrpIBi5t5Pxuym00M211oN86IhLZVT8', "我的留言")
您尝试提供给 CryptoJs 的 base64 编码机密不代表 CryptoJS 需要的有效 UTF-8 字符串。您可以使用 this tool 来检查有效性。 atob()
与编码无关,只是逐字节转换,不检查它是否是有效的 UTF-8。
这里我用 CryptoJS 自己的解码器对 base64 密码进行了解码,它抛出一个错误,说它是无效的 UTF-8:
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.9-1/crypto-js.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.9-1/hmac-sha256.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.9-1/enc-base64.min.js"></script>
<script>
function sign_string(key_b64, to_sign) {
var key = CryptoJS.enc.Base64.parse(key_b64).toString(CryptoJS.enc.Utf8);
var hash = CryptoJS.HmacSHA256(to_sign, key);
var hashInBase64 = CryptoJS.enc.Base64.stringify(hash);
document.write(hashInBase64 + '<br>');
}
sign_string('VGhpcyBpcyBhIHByaW50YWJsZSBzdHJpbmcuCg==', "my message")
sign_string('dGhlIHdpbmQgb2YgTXQuIEZ1amkK', "my message")
sign_string('pkmNNJw3alrpIBi5t5Pxuym00M211oN86IhLZVT8', "my message")
</script>
我还找到了一种使用原始字节作为密钥的方法。这适用于最后一个键,但不适用于前两个键。
var key = CryptoJS.enc.Hex.parse(toHex(atob(key_b64)));
现在,如果您将这两种方法结合起来,您就可以获得真正的解决方案。最终代码给出与 python:
相同的输出<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.9-1/crypto-js.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.9-1/hmac-sha256.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.9-1/enc-base64.min.js"></script>
<script>
function sign_string(key_b64, to_sign) {
try {
var key = CryptoJS.enc.Base64.parse(key_b64).toString(CryptoJS.enc.Utf8);
}
catch {
var key = CryptoJS.enc.Hex.parse(toHex(atob(key_b64)));
}
var hash = CryptoJS.HmacSHA256(to_sign, key);
var hashInBase64 = CryptoJS.enc.Base64.stringify(hash);
document.write(hashInBase64 + '<br>');
}
function toHex(str) {
var result = '';
for (var i=0; i<str.length; i++) {
if (str.charCodeAt(i).toString(16).length === 1) {
result += '0' + str.charCodeAt(i).toString(16);
} else {
result += str.charCodeAt(i).toString(16);
}
}
return result;
}
sign_string('VGhpcyBpcyBhIHByaW50YWJsZSBzdHJpbmcuCg==', "my message")
sign_string('dGhlIHdpbmQgb2YgTXQuIEZ1amkK', "my message")
sign_string('pkmNNJw3alrpIBi5t5Pxuym00M211oN86IhLZVT8', "my message")
sign_string('xTsHZGfWUmnIpSu+TaVraECU88O3j9qVjlwTWGb/C8k=', "my message")
</script>