HMAC SHA256 in Python vs. JavaScript

Dimitri Schachmann :

I want to re-implement a certain API client, which is written in Python, in JavaScript. I fail to replicate the HMAC SHA256 signing function. For some keys the output is identical, but for some it is different. It appears that the output is the same when the key consists of printable characters after decoding its Base64 representation.

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>

Outputs

Python

TdhfUQfym16HyWQ8wxQeNVvJKr/tp5rLKHYQSpURLpw=
pQ5NzK3KIWjqc75AXBvWgLK8X0kZvjRHXrLAdxIN+Bk=
8srAvMucCd91CWI7DeCFjxJrEYllaaH63wmVlMk0W+I=

JavaScript

TdhfUQfym16HyWQ8wxQeNVvJKr/tp5rLKHYQSpURLpw=
pQ5NzK3KIWjqc75AXBvWgLK8X0kZvjRHXrLAdxIN+Bk=
31QxOpifnpFUpx/sn336ZmmjkYbLlNrs8NP9om6nPeY=

As you can see the first two are the same, while the last is different.

How can I change the JavaScript code to behave the same as the python code?

JerMah :

The base64 encoded secret you are trying to give to CryptoJs does not represent a valid UTF-8 string, which CryptoJS requires. You can use this tool to check for validity. atob() is encoding agnostic and just converts byte by byte, and does not check if it's valid UTF-8.

Here I did the decoding of the base64 secret with CryptoJS's own decoder and it throws an error saying it's invalid 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>

I also found a way you can use raw bytes for the key. This works for the last key but not for the first two.

var key = CryptoJS.enc.Hex.parse(toHex(atob(key_b64)));

function toHex(str) {
    var result = '';
    for (var i=0; i<str.length; i++) {
      result += str.charCodeAt(i).toString(16);
    }
    return result;
}

Now if you combine these two methods you can have a real solution. This final code gives identical output as 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>

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related

TOP Ranking

HotTag

Archive