munkiepus
munkiepus

Reputation: 66

How to duplicate symfony passwordEncode in javascript

I need to create a javascript hashing algorithm the same as Symfony 3 encodePassword.

This was a similar problem to that in but in symfony3: Symfony2 password encoder function in Javascript

this is to create a message digest to test a rest endpoint with wsse headers in Symfony with fosbundle in postman.

I've managed to simplify and duplicate the Symfony hashing function in PHP

$pass = "hello";
$salt = "";
$iterations=5000;

echo $this->encoder->encodePassword($pass,$salt);
//contains: U5xyFq7KQU1CWeX3UcLB0mwWZZQUq0PL8U+GLWomfGW/WQWxxGLi+0ifhmnlw/gQ5pPjNNZV1/q8kMVpAXsFZw== 

//simplyfying and replicating the hashing algo in php with same pass/salt:

$salted = $pass.$salt;
$digest = hash("sha512", $salted, true);

for($i=1; $i<$iterations; $i++) {
    $digest = hash("sha512", $digest.$salted, true);
}

echo base64_encode($digest);
//contains: U5xyFq7KQU1CWeX3UcLB0mwWZZQUq0PL8U+GLWomfGW/WQWxxGLi+0ifhmnlw/gQ5pPjNNZV1/q8kMVpAXsFZw==

but trying to replicate it in javascript using CryptoJS is proving troublesome. I suspect its to do with the character encoding too.

according to https://code.google.com/archive/p/crypto-js/#The_Hasher_Input

The hash algorithms accept either strings or instances of CryptoJS.lib.WordArray [...] an array of 32-bit words. When you pass a string, it's automatically converted to a WordArray encoded as UTF-8.


password = 'hello';

//attempt 1 use hex converted pass
hexpass = CryptoJS.enc.Utf8.parse(password);
digest = CryptoJS.SHA512(hexpass);

for (i = 1; i < 5000; ++i) {
    hexvar = CryptoJS.SHA512(digest + hexpass);
}

digest = digest.toString(CryptoJS.enc.Base64);
console.log(digest);

// need hash to contain: U5xyFq7KQU1CWeX3UcLB0mwWZZQUq0PL8U+GLWomfGW/WQWxxGLi+0ifhmnlw/gQ5pPjNNZV1/q8kMVpAXsFZw==
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.9-1/crypto-js.min.js"></script>

I've tried many different ways to with converting to word array first etc. but none seem to come up with the same hash

https://jsfiddle.net/munkiepus/awdoq4kL/34/

EDIT: i think the problem is that the php uses some form of raw binary

outputting the result of $digest = hash("sha512", $salted, true); to the terminal shows:

▒q▒$▒b▒x]▒▒j▒▒=s1▒▒� ▒▒▒▒▒%g<▒##▒ٛ▒▒|z▒n▒▒▒ FcG.:▒▒os▒▒▒C

so maybe it's not possible in JS after all. if the digest was encoded to a readable string during each iteration then it may be possible, as in the linked example.

Upvotes: 0

Views: 196

Answers (2)

munkiepus
munkiepus

Reputation: 66

Ok so it was the binary data causing the problem, if we convert the word array into a binary string it works.

needed some other functions to do the conversions see the runnable example for the functions. example


hashWordArray = CryptoJS.SHA512(password);
uint8array    = convertWordArrayToUint8Array(hashWordArray);
binaryString  = convertUint8ArrayToBinaryString(uint8array);

for (var i=1; i<5000; i++) {
    wordArrayFromString = CryptoJS.enc.Latin1.parse(binaryString+password);
    hashWordArray = CryptoJS.SHA512(wordArrayFromString);
    uint8array    = convertWordArrayToUint8Array(hashWordArray);
    binaryString  = convertUint8ArrayToBinaryString(uint8array);
}

b64_encoded = btoa(binaryString);

const password = "hello";
// set up the container to display output
var div = document.getElementById('message');
div.innerHTML += 'string to hash:<br>';
div.innerHTML += password+'<br><br>';
div.innerHTML += 'php generated hash:<br>';
correct_hash = 'U5xyFq7KQU1CWeX3UcLB0mwWZZQUq0PL8U+GLWomfGW/WQWxxGLi+0ifhmnlw/gQ5pPjNNZV1/q8kMVpAXsFZw=='
div.innerHTML += correct_hash+'<br><br>';


//actually do the hashing
hashWordArray = CryptoJS.SHA512(password);
uint8array    = convertWordArrayToUint8Array(hashWordArray);
binaryString  = convertUint8ArrayToBinaryString(uint8array);

for (var i=1; i<5000; i++) {
    wordArrayFromString = CryptoJS.enc.Latin1.parse(binaryString+password);
    hashWordArray = CryptoJS.SHA512(wordArrayFromString);
    uint8array    = convertWordArrayToUint8Array(hashWordArray);
    binaryString  = convertUint8ArrayToBinaryString(uint8array);
}

b64_encoded = btoa(binaryString);


// add the outputr to the display container
div.innerHTML += 'javascript generated hash:<br>';
div.innerHTML += b64_encoded +"<br><br>"; //b64_encode()




// functions from
// https://gist.github.com/getify/7325764

function convertWordArrayToUint8Array(wordArray) {
	var len = wordArray.words.length,
		u8_array = new Uint8Array(len << 2),
		offset = 0, word, i
	;
	for (i=0; i<len; i++) {
		word = wordArray.words[i];
		u8_array[offset++] = word >> 24;
		u8_array[offset++] = (word >> 16) & 0xff;
		u8_array[offset++] = (word >> 8) & 0xff;
		u8_array[offset++] = word & 0xff;
	}
	return u8_array;
}

function convertUint8ArrayToBinaryString(u8Array) {
	var i, len = u8Array.length, b_str = "";
	for (i=0; i<len; i++) {
		b_str += String.fromCharCode(u8Array[i]);
	}
	return b_str;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.9-1/crypto-js.min.js"></script>

<div id="message"></div>

Upvotes: 1

Ezequiel Esnaola
Ezequiel Esnaola

Reputation: 60

Why do you need that? The best thing is that you only encrypt it on one side, either from JS or Symfony and only compare the hashes.

The other option is to not use encodePassword() and use md5() for example.

In this link shows you how it works encodePassword().

Regards!

Upvotes: 0

Related Questions