【问题标题】:HMAC SHA256 in Python vs. JavaScriptPython 与 JavaScript 中的 HMAC SHA256
【发布时间】:2020-10-19 08:22:53
【问题描述】:

我想用 JavaScript 重新实现某个用 Python 编写的 API 客户端。我无法复制 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 代码相同的行为?

【问题讨论】:

  • 在第三个 sign_string 调用中提供的键在两个示例中是不同的。
  • 你在第三个传递不同的值
  • 好吧,这很尴尬。最后一分钟复制粘贴错误。我现在将示例更改为具有相同的输入,但输出仍然不同。
  • 您可能想研究使用 SubtleCrypto 接口,而不是依赖额外的库。 developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/sign

标签: javascript python hmac sha cryptojs


【解决方案1】:

您尝试提供给 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>

【讨论】:

  • 非常感谢!它确实适用于三个示例,但它不适用于不同的键,即:xTsHZGfWUmnIpSu+TaVraECU88O3j9qVjlwTWGb/C8k=
  • 这是由于 ToHex() 函数中的一个错误,它遗漏了零。现已修复。
【解决方案2】:

在第三个示例中,您在 python 和 JavaScript 版本中使用了不同的参数。 在蟒蛇中: sign_string('xTsHZGfWUmnIpSu+TaVraECU88O3j9qVjlwTWGb/C8k=', "我的消息") 在 JavaScript 中: sign_string('pkmNNJw3alrpIBi5t5Pxuym00M211oN86IhLZVT8', "我的消息")

【讨论】:

  • 好吧,这很尴尬。最后一分钟复制粘贴错误。我现在将示例更改为具有相同的输入,但输出仍然不同。
猜你喜欢
  • 2020-10-18
  • 1970-01-01
  • 2020-08-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-05-25
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多