I am trying to generate an hmac hash in javascript.
here is some python code I want to replicate in Javascript:
mac = hmac.new("33fsfsdgvwrg2g223f4f42gf4f34f43f", digestmod=hashlib.sha1)
mac.update(method)
mac.update(url)
mac.update(data)
mac.update(str(timestamp))
r = requests.request(method, url, data=data, headers={
'Content-Type': 'application/json',
'Authorization': " signature="'mac.hexdigest()'" ",
})
This is what I have so far, and it does not seem to be what I need:
var message = "shah me";
var secret = "33fsfsdgvwrg2g223f4f42gf4f34f43f";
var crypto = CryptoJS.HmacSHA1(message, secret).toString(CryptoJS.enc.Base64);
var shaObj = new jsSHA('shah me', "ASCII");
var jssha = shaObj.getHMAC('33fsfsdgvwrg2g223f4f42gf4f34f43f', "ASCII", "SHA-1", "B64");
It looks like your "current solution" is just a copy paste of jsSHA, CryptoJS and OpenSSL libraries giving different results with your key substituted in.
Anyways, you don't need to use both CryptoJS and jsSHA. You should pick one and stick with it.
According to the docs, the python mac.update function is equivalent to appending data to the message. I believe this is the key to your problems, since neither CryptoJS nor jsSHA have an equivalent update function but instead expect you to have the full message to begin with.
The following Python code and the Javascript code that follows it are equivalent:
import hashlib
import hmac
method = 'method'
url = 'url'
data = 'data'
timestamp = 'timestamp'
mac = hmac.new("33fsfsdgvwrg2g223f4f42gf4f34f43f", digestmod=hashlib.sha1)
mac.update(method)
mac.update(url)
mac.update(data)
mac.update(timestamp)
print mac.hexdigest()
Here is the Javascript:
<script src="sha.js"></script>
<script>
var secret = '33fsfsdgvwrg2g223f4f42gf4f34f43f';
var message = 'methodurldatatimestamp';
var shaObj = new jsSHA(message, "ASCII");
document.write(shaObj.getHMAC(secret, "ASCII", "SHA-1", "HEX"));
</script>
Note that the Javascript code puts the full message ('methodurldatatimestamp') in the jsSHA constructor. I believe this is the key to your problem. Hope this helps!
Related
I'm trying to dynamically generate a security header at Postman pre-request script. To do so, I need to transform the following code snippet from PHP to JS.
$password = "SECRETPASSWORD";
$nonce = random_bytes(32);
date_default_timezone_set("UTC");
$created = date(DATE_ATOM);
$encodedNonce = base64_encode($nonce);
$passwordHash = base64_encode(sha1($nonce . $created . sha1($password, true), true));
(Note the true flag at php's sha1() function, forcing raw output).
I've coded this code snippet so far:
var uuid = require('uuid');
var CryptoJS = require('crypto-js');
var moment = require('moment');
// Generate messageId
var messageId = uuid.v4();
pm.environment.set('messageId', messageId);
// Generate nonce
var nonce = uuid.v4();
var encodedNonce = CryptoJS.enc.Base64.stringify(
CryptoJS.enc.Utf8.parse(nonce)
);
pm.environment.set('nonce', encodedNonce);
// Generate created
var created = moment().utc().format();
pm.environment.set('created', created);
// Generate password hash
var password = 'SECRETPASSWORD';
var rawSha1Password = Buffer.from(CryptoJS.SHA1(password).toString(CryptoJS.enc.Base64), "base64").toString("utf8");
var passwordHash = CryptoJS.SHA1(nonce + created + rawSha1Password).toString(CryptoJS.enc.Base64);
pm.environment.set('passwordHash', passwordHash);
My JS script is almost working, the only problem seems to be the sha1 generation. Taking the following example values:
password: SECRETPASSWORD
nonce: 55d61876-f882-42f0-b390-dc662a7e7279
created: 2021-01-21T18:19:32Z
The output from PHP is:
encodedNonce: NTVkNjE4NzYtZjg4Mi00MmYwLWIzOTAtZGM2NjJhN2U3Mjc5
passwordHash: olI18mUowhmeCwjb1FJNHtTHYDA=
But, the output from JS is:
encodedNonce: NTVkNjE4NzYtZjg4Mi00MmYwLWIzOTAtZGM2NjJhN2U3Mjc5
passwordHash: tk/uYkL/3Uq0oIkYO0nlBGnV/0E=
As you can see, the encodedNonce is built correctly; however the passwordHash value is different. As I'm using Postman, I have a limited JS libraries available.
Taking this into account, how can I get the same result as the PHP one?
In the line
var rawSha1Password = Buffer.from(CryptoJS.SHA1(password).toString(CryptoJS.enc.Base64), "base64").toString("utf8");
the password hash is read into a buffer and then UTF-8 decoded. The UTF-8 decoding generally corrupts the data, see here. A possible solution is to concatenate the WordArrays instead of the strings:
function getPasswordHash(test){
// Generate nonce
var nonceWA = !test ? CryptoJS.lib.WordArray.random(32) : CryptoJS.enc.Utf8.parse('55d61876-f882-42f0-b390-dc662a7e7279');
console.log('nonce (Base64): ' + nonceWA.toString(CryptoJS.enc.Base64));
// Generate created
var created = !test ? moment().utc().format('YYYY-MM-DDTHH:mm:ss[Z]') : '2021-01-21T18:19:32Z';
var createdWA = CryptoJS.enc.Utf8.parse(created);
console.log('created: ' + created);
// Hash password
var pwd = 'SECRETPASSWORD';
var pwdHashWA = CryptoJS.SHA1(pwd);
// Hash nonce + created + pwd
var passwordHash = CryptoJS.SHA1(nonceWA.concat(createdWA).concat(pwdHashWA)).toString(CryptoJS.enc.Base64);
console.log('passwordHash: ' + passwordHash);
}
getPasswordHash(true); // with testdata
getPasswordHash(false); // without testdata
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>
When the code is executed, the first call of getPasswordHash() uses the test data for nonce and date (test=true), the second call applies a random nonce and the current date (test=false) . The call with the test data returns the same result as the PHP code:
nonce (Base64): NTVkNjE4NzYtZjg4Mi00MmYwLWIzOTAtZGM2NjJhN2U3Mjc5
created: 2021-01-21T18:19:32Z
passwordHash: olI18mUowhmeCwjb1FJNHtTHYDA=
CryptoJS implements CryptoJS.lib.WordArray.random() as CSPRNG, which is the counterpart of the PHP method random_bytes(). The use of the uuid library is therefore actually not necessary. I have chosen CryptoJS.lib.WordArray.random() in my example because it is closest to the PHP code.
I'm trying to verify a webhook coming from Plaid in NodeJS by calculating the Sha256 of the webhook body and I'm following a Python code here where the code is showing :
# Compute the has of the body.
m = hashlib.sha256()
m.update(body.encode())
body_hash = m.hexdigest()
What's the alternative of body.encode() in Javascript before passing it to the Sha256 function please ? Note that the body I'm getting is an object containing the following data :
{ error: null, item_id: '4zAGyokJ1XiWP63QNl1RuLZV76o55nudVXzNG',
new_transactions: 0, webhook_code: 'DEFAULT_UPDATE', webhook_type:
'TRANSACTIONS' }
However I'm trying to get this hash :
b05ef560b59e8d8e427433c5e0f6a11579b5dfe6534257558b896f858007385a
So, if the body is JSON (NOT JSON STRING) then you need to stringify it and put it in the .update function As the m.body takes a string. If you have your body as STRING then just put it in as is.
This is from the Crypto Example here:
const crypto = require('crypto');
const hash = crypto.createHash('sha256');
const stringBody = JSON.stringify(body);
hash.update(stringBody);
console.log(hash.digest('hex'));
Edit:
If the hash is not same then maybe you need to correct the newlines or whitespaces. You need to make both bodies exactly the same. Here In the below example I am using same exact string and encoding using Python AND NodeJS.
import hashlib
body = '{"error":null,"item_id":"4zAGyokJ1XiWP63QNl1RuLZV76o55nudVXzNG","new_transactions":0,"webhook_code":"DEFAULT_UPDATE","webhook_type":"TRANSACTIONS"}'
m = hashlib.sha256()
m.update(body.encode())
body_hash = m.hexdigest()
print(body_hash)
Output:
python3 file.py
26f1120ccaf99a383b7462b233e18994d0c06d4585e3fe9a91a449e97a1c03ba
And Using NodeJS:
const crypto = require('crypto');
const hash = crypto.createHash('sha256');
const body = {
error: null,
item_id: '4zAGyokJ1XiWP63QNl1RuLZV76o55nudVXzNG',
new_transactions: 0,
webhook_code: 'DEFAULT_UPDATE',
webhook_type: 'TRANSACTIONS'
}
const stringBody = JSON.stringify(body);
hash.update(stringBody);
console.log(hash.digest('hex'));
Output:
node file.js
26f1120ccaf99a383b7462b233e18994d0c06d4585e3fe9a91a449e97a1c03ba
In a web form the answers (packed in a jsonstring) are encrypted in several steps. First a random key is generated. Second the random key is used for AES encryption of the jsonstring. The random key is encrypted as well. Both are send in the body of a mail.
// Generate Random key
var rand_key = ('0000' + Math.random().toString(36).replace('.', '')).substr(-10);
console.log('rand_key', rand_key)
//var pubkey = "MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBALDjeFwFNhMCjMwcRVVKG1VvfsntEVPR3lNTujJnNk1+iSqZ4Tl5Lwq9GbwO+qlYVwXHNmeqG7rkEhL9uyDIZVECAwEAAQ=="
// rsa_key_public07012016.bin
//var pubkey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCv8FVei4Q2ehmYsSCv/uODSojIOGHwfQe686S1cEH5i/1mGME5ZzNqyy0d+lhMRD0tr7Sje7JoCEC/XRIZaiKJjpl1+3RXotf/Cx3bd9H7WtitshZB1m38ZZFsrX4oigMpUPFbCefMeBS4hvvNnmtl08lQGhfIXdXeflZsgWRHtQIDAQAB";
// my_pub_key.pem
var pubkey ="MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA38gtENP9/hpirSCIsPh6CAVm0UmME4XBlPyK8yhwk079EUJpNzlEhu9HKcA/B7Fxo2lNoY9Tb9e+PYtJ6+VOB4+Y6zgGMX7cchYmumKRTbbQ6FNfBE5Q8XnOAUlgC7gNrs0e5lW7JH1kWlK+eTT4TANT7F3US09aXmym+fZaRInbXmJujGnDIbRIIbzr5FE82EeMpw2TqRWV466wz5EeFWSSQ8EqV1pSox8B1ywb6cnB/Vofs2qR9Zf2efi9TMcSGm/ij/p9IZcbLeep9qfGsv29lbLNMfwNwQyH0JU27eAM4tPdirceZPxfD6iiILmKzN253BMoAeQCp6us53CnGQIDAQAB"
// Make form_data a JSON string
var jsonstring = JSON.stringify(form_data);
// Create AES encrypted object
var aes_encrypted_json = CryptoJS.AES.encrypt(jsonstring, rand_key);
// Encrypt rand_key
var encrypt = new JSEncrypt();
//console.log('encrypt obj', encrypt);
encrypt.setPublicKey(pubkey);
var encrypted_rand_key = encrypt.encrypt(rand_key);
//var encrypted = encrypt.encrypt(jsonstring);
console.log('encypted', encrypted_rand_key);
var mail_body = encrypted_rand_key + aes_encrypted_json
console.log('body', mail_body)
var mailto_string = "mailto:info#xyz.com?subject=FORM&body=" + encodeURIComponent(mail_body);
$('#mailtosend').attr('href', mailto_string);
At the recipient mail server side I want to decrypt the random generated key and the jsonstring using a private key using the pycryptodome package.
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5
from base64 import *
def decrypt(key, text):
if type(key) == str:
key = key.encode()
if type(text) == str:
text = text.encode()
rsakey = RSA.importKey(key)
rsakey = PKCS1_v1_5.new(rsakey)
d = rsakey.decrypt(text, 'bolloux')
return d
# rand_key am2mhiwwmi
text = "ZvcrluUmZLY3lRRw01W9mQnhMn7zzpIWn1Bo3csM/ZZ0pWY/8H2dCB9fZDi9/cmp0UtIqDXhLd7SIwyxqrFgPcHUuEHlZl0WQcjSty8PjadG2Abulk1XqEQV4u0Gb/bFGDBMcf5tV1G0d4FFcBPE8r8inrxUjSj2CSffVL8gIGq3ZfY5g7t5FOZV8npBCEONgOLKYnzIiHrHUuXWsOaMAqxMFOLd5DTDLKAkyMybDClsLW9ka+CvWd5fnZBCvO2ziehFp7b9PG4QPSnQpdC8jNLGZB2h0FI8YQD6IyUwmVluUbAlPMqwd6A2CBdGCbfbMChaA5R7bJgKkYhPOQTjaQ=="
text = b64decode(text.encode())
with open('my_priv_key.pem', 'rb') as f:
key = f.read()
decrypt(key, text)
I run into a encoding problem. "UnicodeDecodeError: 'ascii' codec can't decode byte 0xf7 in position 1: ordinal not in range(128)" The encoding is complicating the issue beyond my capabilities.
My questions:
1. How can I resolve the encoding problem ?
2. How can I make the decryption work ?
Thanks
The issue is more than likely caused by b64decode(text) returning a str that contains values such as \xf7 and then attempting to .encode() those values within your decrypt function. encode will use the default encoding which in this case is ascii. I would personally remove the calls to encode unless you specifically have a reason you are doing so.
Can someone help me convert the following Fantom code to Javascript?
// compute salted hmac
hmac := Buf().print("${username}:${userSalt}").hmac("SHA-1", password.toBuf).toBase64
// now compute login digest using nonce
digest := "${hmac}:${nonce}".toBuf.toDigest("SHA-1").toBase64
I've been able to compute the hmac variable using CryptoJS:
var hash = CryptoJS.HmacSHA1("alice:6s6Q5Rn0xZP0LPf89bNdv+65EmMUrTsey2fIhim/wKU=", "secret");
var hmac = hash.toString(CryptoJS.enc.Base64);
But I'm still struggling with the digest.
If you post an example, here are the variables I'm using in testing:
username : "alice"
password : "secret"
userSalt : "6s6Q5Rn0xZP0LPf89bNdv+65EmMUrTsey2fIhim/wKU="
nonce : "3da210bdb1163d0d41d3c516314cbd6e"
hmac : "z9NILqJ3QHSG5+GlDnXsV9txjgo="
digest : "B2B3mIzE/+dqcqOJJ/ejSGXRKvE="
This answer uses CryptoJS as you've already had some success with it:
var username = "alice";
var password = "secret";
var userSalt = "6s6Q5Rn0xZP0LPf89bNdv+65EmMUrTsey2fIhim/wKU=";
var nonce = "3da210bdb1163d0d41d3c516314cbd6e";
var hmac = CryptoJS.HmacSHA1(username + ":" + userSalt, password).toString(CryptoJS.enc.Base64);
var digest = CryptoJS.SHA1(hmac + ":" + nonce).toString(CryptoJS.enc.Base64);
console.log(hmac); // --> z9NILqJ3QHSG5+GlDnXsV9txjgo=
console.log(digest); // --> B2B3mIzE/+dqcqOJJ/ejSGXRKvE=
Note it uses the following CryptoJS files:
/rollups/sha1.js
/rollups/hmac-sha1.js
/components/enc-base64-min.js
You can see a live example in this JS paste bin:
https://jsbin.com/luvinayoyi/edit?js,console
New to JS, I'm also learning to use crypto libraries. I don't understand why signing/encoding the same message with the same secret yields differing results.
I'm using jsSHA 1.3.1 found here, and CryptoJS 3.0.2 described here trying to create a base64 sha-1 encoded hmac signature. Here's the code:
In html...
<script src="lib/jsSHA/src/sha1.js"></script>
<script src="http://crypto-js.googlecode.com/svn/tags/3.0.2/build/rollups/hmac-sha1.js"></script>
And in js...
var message = "shah me";
var secret = "hide me";
var crypto = CryptoJS.HmacSHA1(message, secret).toString(CryptoJS.enc.Base64) + '=';
var shaObj = new jsSHA(message, "ASCII");
var jssha = shaObj.getHMAC(secret, "ASCII", "B64") + '=';
return "crypto answer is " + crypto + " jssha answer is " + jssha;
Can you help me explain why these results differ?
crypto answer is 3e929e69920fb7d423f816bfcd6654484f1f6d56= jssha
answer is PpKeaZIPt9Qj+Ba/zWZUSE8fbVY=
What's more, both of these differ with the signature I'm generating in rails, like this...
digest = OpenSSL::Digest::Digest.new('sha1')
raw_signature = OpenSSL::HMAC.digest(digest, "hide me","shah me")
b64_signature = Base64.encode64(raw_signature).strip
(would have liked to supply a fiddle, which seems to be a very good common practice, but that, too, is new to me and I was unable to get one working for this question).
Thanks in advance.
There are 3 errors in your code :)
You're missing the enc-base64-min.js for crypto-js. Without it, CryptoJS.enc.Base64 will be undefined
You're missing a parameter when calling .getHMAC(). It's .getHMAC(secret, secret_type, hash_type, output_encoding)
With 1+2 adding a = isn't necessary (nor right)
<script src="lib/jsSHA/src/sha1.js"></script>
<script src="http://crypto-js.googlecode.com/svn/tags/3.0.2/build/rollups/hmac-sha1.js"></script>
<script src="http://crypto-js.googlecode.com/svn/tags/3.0.2/build/components/enc-base64-min.js"></script>
var message = "shah me";
var secret = "hide me";
var crypto = CryptoJS.HmacSHA1(message, secret).toString(CryptoJS.enc.Base64);
var shaObj = new jsSHA(message, "ASCII");
var jssha = shaObj.getHMAC(secret, "ASCII", "SHA-1", "B64");
return "crypto answer is " + crypto + " jssha answer is " + jssha;
Example