Hmac-SHA256 not returning expected hash - javascript

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?

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.

Related

How can I mimick OpenLDAP's slappasswd using NodeJS?

My goal is to use NodeJS to create LDAP password hashes that are similar to what comes out of the slappasswd command-line tool.
Here's how LDAP passwords can be produced with command-line:
slappasswd -h '{SSHA}' -s 'P#ssw0rd'
{SSHA}1RHPt8m4AWLjK8Px1MT6FEBJOBJpdzqT
The result is a base64 encoded, salted SHA1 password.
Here's what I tried initially to recreate it:
#!/usr/bin/env node
import sha1 from 'crypto-js/sha1.js';
let password = 'P#ssW0rd';
let salt = btoa(0xA5); // Not random, just a proof of concept temporary value.
let hash = sha1(password + salt);
console.log('{SSHA}' + btoa(hash));
But, I got a much longer string than what the slappasswd command produced and I'm not sure why.
{SSHA}NDVkN2JjODQ2ZDk3Yjc2YmViNTU3MzUzYjBiNzExN2ZmYzMxYWY5ZA==
I did some digging around on the net and found this on an LDAP password generator web page:
<script src="lib/cryptojs/core.js"></script>
<script src="lib/cryptojs/sha1.js"></script>
<script src="lib/cryptojs/enc-base64.js"></script>
<script>
function slappasswd(password) {
var salt = CryptoJS.lib.WordArray.random(128/8).toString().substr(0,4);
var hash = CryptoJS.SHA1(password + salt);
var base = CryptoJS.enc.Latin1.parse(hash.toString(CryptoJS.enc.Latin1) + salt).toString(CryptoJS.enc.Base64);
return '{SSHA}' + base;
}
...
The web page produces a string that is the same length as what comes out of slappasswd, so I assume it's an accurate recreation of the slappasswd logic.
Using this information, my next attempt looks like this:
#!/usr/bin/env node
import * as CryptoJS from 'crypto-js';
let password = 'P#ssW0rd';
let salt = CryptoJS.lib.WordArray.random(128/8).toString().substr(0,4);
let hash = sha1(password + salt);
let base = CryptoJS.enc.Latin1.parse(hash.toString(CryptoJS.enc.Latin1) + salt).toString(CryptoJS.enc.Base64);
console.log('{SSHA}' + base);
However, I get errors.
First, there is TypeError: Cannot read properties of undefined (reading 'WordArray')
If I replace let salt = with let salt = btoa(0xA5) from my first attempt code, I then get the error: ReferenceError: sha1 is not defined
My feeling is that I've got the import wrong somehow.
I'm trying to do the ES6 module equivalent of var CryptoJS = require("crypto-js");, but failing somewhere.
So my question is two-fold:
Can my first attempt be made to produce a string length similar to what slappassword outputs?
If not, what can I do to fix the errors I'm getting in the second attempt?
Ideally, I'd like to understand where I went wrong in my first attempt rather than simply copying and pasting someone else's code (second attempt) without fully grasping it.
Here is alternative of python/php implementations for NodeJS.
Import Crypto module
const crypto = require('crypto');
It will be used to create LDAP password hashes (SSHA)
function generate_hash(passwd, salt) {
if (!salt) {
const buf = crypto.randomBytes(4);
salt = buf.toString('base64');
}
let ctx = crypto.createHash('sha1');
ctx.update(passwd, 'utf-8');
ctx.update(salt, 'binary');
let digest = ctx.digest('binary');
return '{SSHA}' + Buffer.from(digest + salt, 'binary').toString('base64');
}
It will be used to verify hash
function verify_hash(passwd, hash) {
let bhash = Buffer.from(hash.substr(6), 'base64');
let salt = bhash.toString('binary', 20);
let newssha = generate_hash(passwd, salt);
return hash === newssha;
}
Test it together
const hash = generate_hash("qwe1234");
let test = verify_hash("qwe1234", hash);
console.log(test); //Output: true
let test = verify_hash("XXXX", hash);
console.log(test); //Output: false
Hope it help you. Please let me know.
Try Now

Console log ({value}) isn't the same as console.log(value)

I'm not stuck or anything, it's just my own curiosity and I noticed something about Console.log,
And I can't find anwsers online , or its limited since I think making a Google search with "{}" isn't really working out
I'm coding a small node.Js app that interacts with Wi-Fi, and I tried the following thing :
console.log(ssid + " : " + pwd);
and it returns this
freebox_XXXXX : fake_password
So far, everything is normal but then, I was tired and messed up and tried this :
console.log({ ssid: ssid, password: pwd });
and it returned this
{
ssid: 'f\x00r\x00e\x00e\x00b\x00o\x00x\x00_\x005\x007\x00a\x002\x00a\x007\x00',
password: '\x00T\x00e\x00s\x00t\x00'
}
I'm wondering why do you have any answers?
More details :
The data sent via bluetooth by this function as an bufferArray is created with this function
str2ab(str) {
let buf = new ArrayBuffer(str.length * 2); // 2 bytes for each char
let bufView = new Uint16Array(buf);
for (let i = 0, strLen = str.length; i < strLen; i++) {
bufView[i] = str.charCodeAt(i);
}
return buf;
}
let payload = str2ab('freebox_XXXXX|' + alertData.password);
Then, the other devices receives it
WriteOnlyCharacteristic.prototype.onWriteRequest = function(data, offset, withoutResponse, callback) {
let payload = data.toString()
let wifiData = payload.split('|');
let ssid = wifiData[0];
let pwd = wifiData[1];
console.log(ssid + " : " + pwd);
console.log({ ssid: ssid, password: pwd });
});
The difference is because of how Node.js logs values.
The behaviour of console.log() depends on the type of values being logged:
If a string is logged, it is written to stdout as-is (well, not as-is, because %-args are substituted, but that's off-topic for this question), to allow raw texts (optionally with escape sentences) to be displayed in the console
If another data type is passed, it will be inspected (intended for debugging, the same way as util.inspect() does it), that is, it will be converted to a format nearly identical to the JS syntax that created it. That makes the strings inside complex data structures displayed as their representation: with quotes around them and special characters escaped.
Your strings contain NULL characters between each letter (that's probably because your code treats a UTF-16 string as UTF-8 or ASCII somewhere), which don't appear when logged to the console, but show up escaped (\x00) when the string is inspected.
That is the cause of the inconsistency between the two logging methods.
{ ssid: ssid, password: pwd }
It is a object not string.

Leading and trailing zeros in numbers

I am working on a project where I require to format incoming numbers in the following way:
###.###
However I noticed some results I didn't expect.
The following works in the sense that I don't get an error:
console.log(07);
// or in my case:
console.log(007);
Of course, it will not retain the '00' in the value itself, since that value is effectively 7.
The same goes for the following:
console.log(7.0);
// or in my case:
console.log(7.000);
JavaScript understands what I am doing, but in the end the actual value will be 7, which can be proven with the following:
const leadingValue = 007;
const trailingValue = 7.00;
console.log(leadingValue, trailingValue); // both are exactly 7
But what I find curious is the following: the moment I combine these two I get a syntax error:
// but not this:
console.log(007.000);
1) Can someone explain why this isn't working?
I'm trying to find a solution to store numbers/floats with the exact precision without using string.
2) Is there any way in JS/NodeJS or even TypeScript to do this without using strings?
What I currently want to do is to receive the input, scan for the format and store that as a separate property and then parse the incoming value since parseInt('007.000') does work. And when the user wants to get this value return it back to the user... in a string.. unfortunately.
1) 007.000 is a syntax error because 007 is an octal integer literal, to which you're then appending a floating point part. (Try console.log(010). This prints 8.)
2) Here's how you can achieve your formatting using Intl.NumberFormat...
var myformat = new Intl.NumberFormat('en-US', {
minimumIntegerDigits: 3,
minimumFractionDigits: 3
});
console.log(myformat.format(7)); // prints 007.000
Hi
You can use an aproach that uses string funtions .split .padStart and .padEnd
Search on MDN
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/split
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padStart
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padEnd
Here you have an example:
const x = 12.1;
function formatNumber( unformatedNumber) {
const desiredDecimalPad = 3;
const desiredNonDecimalPad = 3;
const unformatedNumberString = unformatedNumber.toString();
const unformatedNumberArr = unformatedNumberString.split('.');
const decimalStartPadded = unformatedNumberArr[0].padStart(desiredDecimalPad, '0');
const nonDecimalEndPadded = unformatedNumberArr[1].padEnd(desiredNonDecimalPad, '0');
const formatedNumberString = decimalStartPadded + '.' + nonDecimalEndPadded;
return formatedNumberString;
}
console.log(formatNumber(x))

how to get Hmac code with javascript

I can get a hmac sing using Python as following:
import hmac, base64, hashlib
def make_sign():
hash_data = "data"
secret = "this is secret"
sha512 = hashlib.sha512
hmac_obj = hmac.new(secret, hash_data, sha512)
str_hash = hmac_obj.digest()
sign = base64.b64encode(str_hash)
hex_hash = hmac_obj.hexdigest()
hex_sign = base64.b64encode(hex_hash)
print "correct_sign:",sign
print "hex_digest_sign:",hex_sign
make_sign()
output:
correct_sign: Lg4pXNCIpitNQt2DLU19qWb+FxdsYZlK4LLncfkTzSidrYoFJLNolUziRqh09B5HyRdCTEP7enZp6/Te34FK1g==
hex_digest_sign: MmUwZTI5NWNkMDg4YTYyYjRkNDJkZDgzMmQ0ZDdkYTk2NmZlMTcxNzZjNjE5OTRhZTBiMmU3NzFmOTEzY2QyODlkYWQ4YTA1MjRiMzY4OTU0Y2UyNDZhODc0ZjQxZTQ3YzkxNzQyNGM0M2ZiN2E3NjY5ZWJmNGRlZGY4MTRhZDY=
but with js, I can get hex_digest_sign, but I need to get correct_sign for web request.
function make_request() {
hash_data = "data"
secret = "this is secret"
hmac = hmac_512(hash_data, secret)
var sign = $.base64.encode(hmac),
console.log("js_sign="+sign);
}
function hmac_512(message, secret) {
var hmac = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA512, secret);
hmac.update(message);
var hash = hmac.finalize();
return hash;
}
js output:
js_sign="MmUwZTI5NWNkMDg4YTYyYjRkNDJkZDgzMmQ0ZDdkYTk2NmZlMTcxNzZjNjE5OTRhZTBiMmU3NzFmOTEzY2QyODlkYWQ4YTA1MjRiMzY4OTU0Y2UyNDZhODc0ZjQxZTQ3YzkxNzQyNGM0M2ZiN2E3NjY5ZWJmNGRlZGY4MTRhZDY="
the correct sign is correct_sign: Lg4pXNCIpitNQt2DLU19qWb+FxdsYZlK4LLncfkTzSidrYoFJLNolUziRqh09B5HyRdCTEP7enZp6/Te34FK1g==
how to get it in js?
I suspect that you are running into trouble with types and encoding. According to the CryptoJS source, the iterative hashing style that you are using returns a WordArray once you call finalize().
With that, once you go to print the results, you are printing the contents of the WordArray.
The purpose for itterative hashing is typically if you have a large input, you can break it into chunks to work on one piece at a time. Try the below edit I made that removes this as it does not look like you need to iterate.
function hmac_512(message, secret) {
var newHMAC = CryptoJS.HmacSHA256(message, secret);
return newHMAC;
}
The above will simply return the HMAC in string form which, once Base64 encoded, should match the result you see in Python.
Hope this helps!

OAuth nonce value

I am working with the FatSecret REST API
Im using the OAuthSimple javascript library to generate the signed url.
Here's the code I have -
params['oauth_timestamp'] = Math.floor(new Date().getTime()/1000);
params['oauth_nonce'] = '1234';
params['oauth_version'] = '1.0';
var paramStr = '';
for(var key in params){
paramStr += key+"="+params[key]+"&";
}
paramStr = paramStr.substring(0,paramStr.length-1);
var oauth = OAuthSimple();
oauth.setAction('GET');
var o = oauth.sign(
{
path:this.requestURL,
parameters: paramStr,
signatures:{
api_key:this.apiKey,
shared_secret:this.sharedSecret,
access_token: this.accessToken,
access_secret: this.accessSecret
}
});
console.log(o.signed_url);
return o.signed_url;
params is an associative array containing all the non oauth related parameters for this call.
When I use this signed url I get an "invalid/used nonce"
The OAuth Testing Tool uses the same OAuthSimple library and if I put in all the same parameters (including the timestamp) it generates exactly the same url.
The only difference is that the url generated by the testing tool works and gives me the full response from the server. The url generated by my code does't.
I tried various nonce values including sending a MD5 of the timestamp but I get the same error. The reason I'm using 1234 right now is that the testing tool uses 1234 by default and that seems to work.
Any help is appreciated. Thanks in advance.
Updating #Saravanan's answer with something that works on current browsers:
function genNonce() {
const charset = '0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._~'
const result = [];
window.crypto.getRandomValues(new Uint8Array(32)).forEach(c =>
result.push(charset[c % charset.length]));
return result.join('');
}
console.info(genNonce());
The nonce value as per twitter documentation:
The value for this request was generated by base64 encoding 32 bytes of random data, and stripping out all non-word characters, but any
approach which produces a relatively random alphanumeric string should
be OK here.
Based on the above notes, I use the following javascript code to generate nonce value each time I send a request:
var nonceLen = 32;
return crypto.randomBytes(Math.ceil(nonceLen * 3 / 4))
.toString('base64') // convert to base64 format
.slice(0, nonceLen) // return required number of characters
.replace(/\+/g, '0') // replace '+' with '0'
.replace(/\//g, '0'); // replace '/' with '0'
Try this if it works!
Try this
This works every time
var nonce = Math.random().toString(36).replace(/[^a-z]/, '').substr(2);

Categories