1
votes

I realize there are a ton of questions on here about this, but after looking through a good portion of them I haven't really seen anything addressing my issue.

Using SHA256 on the following input I get the correct output:

var canonString = 'GET\n'+
                    '/\n'+
                    'Action=ListUsers&Version=2010-05-08\n'+
                    'content-type:application/x-www-form-urlencoded; charset=utf-8\n'+
                    'host:iam.amazonaws.com\n'+
                    'x-amz-date:20150830T123600Z\n'+
                    '\n'+
                    'content-type;host;x-amz-date\n'+
                    'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855';

console.log(CryptoJS.SHA256(canonString).toString()); //returns the expected value of f536975d06c0309214f805bb90ccff089219ecd68b2577efef23edd43b7e1a59

So SHA256 is working properly on that. Similarly, using the Hmac-SHA256 on the following input I get the correct response:

var kDate = CryptoJS.HmacSHA256("20150830", "AWS4wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY");
var kRegion = CryptoJS.HmacSHA256('us-east-1', kDate);
var kService = CryptoJS.HmacSHA256('iam', kRegion);
var kSigning = CryptoJS.HmacSHA256("aws4_request", kService);

console.log(kSigning.toString()); //returns the expected value of c4afb1cc5771d871763a393e44b703571b55cc28424d1a5e86da6ed3c154a4b9

So this Hmac-SHA256 function works correctly on this input. However, on the following input, Hmac-SHA256 DOES NOT return the expected output.

var stringToSign = 'AWS4-HMAC-SHA256\n'+
                '20150830T123600Z\n'+
                '20150830/us-east-1/iam/aws4_request\n'+
                CryptoJS.SHA256(canonString).toString();

CryptoJS.HmacSHA256(kSigning.toString(), stringToSign); //Returns 8a96b6691875490d30d05731cc9aa26be1fd64cf611ed929753b6498075aa886
//Expected value is 5d672d79c15b13162d9279b0855cfba6789a8edb4c82c400e06b5924a6f2b5d7
//Trying in opposite order just in case
CryptoJS.HmacSHA256(stringToSign, kSigning.toString()); //Returns fe52b221b5173b501c9863cec59554224072ca34c1c827ec5fb8a257f97637b1
//Still not expected value which is 5d672d79c15b13162d9279b0855cfba6789a8edb4c82c400e06b5924a6f2b5d7

So, something is clearly going wrong with my stringToSign, and I don't know what it is. I was thinking that the newline character is being interpreted as two different characters and not just a single character. However, escaping it like '\\n' did not fix it either! I am at a loss here. Here are the two docs I've been following (doc1 doc2). Does anyone know why I can't get the expected output?

1
You're missing a crucial part: those are the correct SHA256 prints given the input you provided, so what makes you conclude they are wrong? Where are you pulling the value that they "should" be from? (e.g. "why should we trust the code comment to be correct, when the code is the only thing we know is correct?")Mike 'Pomax' Kamermans
I am following the documentation that I linked at the bottom for learning to sign some AWS requests. The documentation provides sample input values and what the expected output for those values is. Therefore, I know what my first two outputs ARE correct, but something goes wrong in the third part because it does not return the expected output. The only thing I can reasonably think it would be is my stringToSign because it is the only thing that doesn't have a SHA256 value I can check against in the docs.Ben Dickinson
the real problem is that the way you call CryptoJS, you're converting the sha256 digest to "something that is not a sha256 digest" and then use that as HMAC secret. If you ensure that you're using the sha256 digest except in places where you have to convert, you'll be fine. See answer.Mike 'Pomax' Kamermans

1 Answers

3
votes

Remember that the sha256 digest is a byte sequence: it is not a "normal string". It looks like CryptoJS is converting the true sha256 digest to something else for convenience, so make it not do that and you're good to go.

Using Node's crypto library (which is a built-in API) rather than CryptoJS (which has absolutely terrible documentation, so using it is kind of questionable):

const crypto = require("crypto");

function HMAC(key, text) {
    return crypto.createHmac("sha256", key).update(text).digest();
}

And then we form the canonical hash:

const canonString = [
    'GET',
    '/',
    'Action=ListUsers&Version=2010-05-08',
    'content-type:application/x-www-form-urlencoded; charset=utf-8',
    'host:iam.amazonaws.com',
    'x-amz-date:20150830T123600Z',
    '',
    'content-type;host;x-amz-date',
    'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'
].join('\n');

// note: plain hash, not a secret-key-seeded hash
const canonHash = crypto.createHash("sha256").update(canonString).digest();
console.log("Canonical hash is   :", canonHash.toString('hex'));

This yields f536975d06c0309214f805bb90ccff089219ecd68b2577efef23edd43b7e1a59, but only because we logged it as hexadecimal string using .toString('hex'): the real value is still a byte sequence.

We then continue:

const kSecret = "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY";
const kDate = HMAC("AWS4" + kSecret,"20150830");
const kRegion = HMAC(kDate,"us-east-1");
const kService = HMAC(kRegion,"iam");
const kSigning = HMAC(kService,"aws4_request");
console.log("kSigning hash is    :", kSigning.toString('hex'));

Which yields c4afb1cc5771d871763a393e44b703571b55cc28424d1a5e86da6ed3c154a4b9: note that again this is only after toString('hex') for console logging purposes. The sha256 byte digest kSigning itself is not a hex string.

Then finally:

const stringToSign = [
    'AWS4-HMAC-SHA256',
    '20150830T123600Z',
    '20150830/us-east-1/iam/aws4_request',
    canonHash.toString('hex')
].join('\n');

const signed = HMAC(kSigning, stringToSign);
console.log("Final signed hash is:", signed.toString('hex'));

Which yields 5d672d79c15b13162d9279b0855cfba6789a8edb4c82c400e06b5924a6f2b5d7, and note that we had to turn the canonical hash into a hexadecimal string for signing purposes, not just logging, as per the instructions in the pages you link to. But, we still do not touch the kSigning digest, that stays a real sha256 byte sequence.