Search code examples
node.jssymfonyhashpbkdf2

How to validate Symfony2 sha512 passwords using nodejs


I need to be able to validate in node some password which were generated and stored using Symfony2 with sha512 encoding.

I can retrieve the hash and the salt just fine but when using crypto I cannot manage to generate a hash using the salt which matches the one stored in the database.

Symfony security.yml

security:
    encoders:
        "FOS\UserBundle\Model\UserInterface": sha512

Hash stored in DB

6zxwRZc4EPXKxQes9avs0ZyCRFkC4dtpXrT983ML8VLvv9WhRnAi282bwuFuj3LHPQBGmqD1BfCLDUXGdHIjZQ==

Salt stored in DB

qu7rjvaietws8kg4cgsggksookwsws8

As there is a salt on the node side I'm using crypto.pbkdf2Sync, there is no iterations set in the configuration and it seems like the Symfony default is 1000. The default length however is 40 yet the stored hash length is 128 so using 128 as length (tried 40 without success).

I have also tried 5000 iterations as used by Symfony 2 MessageDigestPasswordEncoder and merging the raw password with the salt as symfony does raw_password{salt} without success.

I can also get a base64 long enough if I use pbkdf2Sync to generate a key of length 40 (as per symfony default) and then using that to update a hash generate with crypto.createHash

var hash = user.password;
var salt = user.salt;

console.log(hash);

console.log(crypto.pbkdf2Sync("password", salt, 1000, 128 >> 1, "sha512").toString("base64"));

The size of the hash generated and format matches the one stored in the database but they do not match in values which is my issue. I also tried multiple iteration values dynamically generated without any luck.

Output

6zxwRZc4EPXKxQes9avs0ZyCRFkC4dtpXrT983ML8VLvv9WhRnAi282bwuFuj3LHPQBGmqD1BfCLDUXGdHIjZQ==
5QPz3zXwhak/bTD2S9IFEEFmiJ8q/fqIlSF6cWin7dqmh92EFNXjw/FLtQw7NX3LVehwcXKjfypo2EhJxiLouQ==
w9+8xjklvGq9CuiqY8tEoxEetLV9lbhLJ/KaFQEooUFJrGT9/EdsVd/sSRJ+DXjsH4RQeaqsmftmuzLPgVv5MA==

How can I generate the hash so it matches the way used by Symfony 2?


Solution

  • Symfony does 5000 iterations with sha512.

    1. They concatenate raw password with user's salt to generate a new salt
    2. They generate a starting hash with above data.
    3. On each iteration previous hash its updated with the current hash + new salt generated at step 1.

    In node, on each iteration you should digest previous hash as binary and in the end digest as base64, to mimic what Symfony does.

    And an example:

    var crypto = require('crypto');
    
    var encodePassword = function (raw, salt) {
        var salted = raw + '{'+salt+'}',
            hash = crypto.createHash('sha512').update(salted, 'utf-8');
    
        for (var i = 1; i < 5000 ; i++) {
            hash = crypto.createHash('sha512').update(hash.digest('binary')+salted);
        }
    
        return hash.digest('base64');
    };
    
    console.log("Password: "+ encodePassword("secret", "h2zaays1cx2og00c6ow2gc0k4skg41g"));