Search code examples
javascriptnode.jshashcryptojs

How to validate a java generated password hash (sha-256) in nodejs?


I'm working in a legacy solution with a java service that manages user registration. For each user the java service generates a password hash which is stored in our DB together with the used salt. The java code uses the org.apache.shiro.crypto.hash.Sha256Hashmethod to generate the hash.

I'm now trying to validate the same user in a Nodejs service, by taking the same salt with which I'm hashing the new password input from the user and comparing this with the same user's password hash from the DB. However, I can't get it to match and I don't know why/where it goes wrong.

Java code snippet that is used for hashing

public static final int HASH_ITERATIONS = 1004;
public static final String HASH_ALGORITHM = Sha256Hash.SHA-256;
hashedPw = new Sha256Hash(password, new SimpleByteSource(salt), HASH_ITERATIONS).toHex();
// hashedPW and salt are stored in DB

My failing Nodejs attempt:

// Getting salt and hashedPw from DB, they are
// salt = <Buffer 1e e7 1d 5a ec f2 a1 02 e9 9c 86 d7 33 04 a4 5b>
// hashedPw = f88b92d40fbc1644395d704d4f29d7e702fc8add275d5e93a52a3645611fd352

// Using crypto library, assuming length of salt is 16 bytes (given trace above)
// and that the hash algorithm corresponding to SHA-256 is 'sha256' in nodejs-crypto
const key = crypto.pbkdf2Sync(password, salt, 1004, 16, 'sha256');
console.log(key);
console.log(key.toString('hex'));

//This prints:
// <Buffer 80 10 b5 30 0e ca e0 ff 1f 97 96 1b b4 d4 d3 41>
// 8010b5300ecae0ff1f97961bb4d4d341
// which clearly doesn't match the 'hashedPw' above

I hope someone that has more crypto experience than me can help me see what is wrong above.

** Edit (Additional info requested in comment) **

(I created a new, temporary user to be able to share all info) The password that is hashed is: myTest123

The salt that is used in java code is: NJxGXOhrAWJ1pPNm2Hg29Q==

The resulting, hashed password is: 63816c31d2221151edf8134de7d9b2fb4d2d189ce5fc1084b84b33c28441217c

The result I get from pbkdf2Sync (per nodejs console log above) is:

<Buffer d4 9b 98 09 aa a1 92 c9 ca 70 0a 34 5b ca cb 13>
d49b9809aaa192c9ca700a345bcacb13

Solution

  • As @x4rf41 assumed right in his comment, PBKDF2 must not be used, instead the hash generated by SHA256 must be hashed again with SHA256 each time according to the iteration count, see SimpleHash#hash. A possible NodeJS implementation is therefore (using your test data):

    const crypto = require("crypto");
    
    // Set salt, password and iteration count
    const salt = Buffer.from('NJxGXOhrAWJ1pPNm2Hg29Q==', 'base64');
    const password = 'myTest123';
    const hashIterations = 1004;
    
    // Iteration 0
    var value = crypto.createHash('sha256').update(salt).update(password).digest();
    
    // Iterations 1 to hashIterations - 1
    var hashIteration = 1;
    while (hashIteration++ < hashIterations) {
        value = crypto.createHash('sha256').update(value).digest();
    }
    console.log(value.toString('hex')); // 63816c31d2221151edf8134de7d9b2fb4d2d189ce5fc1084b84b33c28441217c
    

    NodeJS doesn't implement a reset function for the Hash class (as it's done in Java with MessageDigest#reset, which is also applied by org.apache.shiro.crypto.hash.Sha256Hash), i.e. after calling digest() the Hash object can no longer be used, so it has to be recreated.

    Note that NJxGXOhrAWJ1pPNm2Hg29Q== is not the salt but the Base64 encoded salt.