Search code examples
javascriptencodingcryptojssha512

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.


Solution

  • 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>