44

For an app I'm working on, nodejs needs to verify hashes created by PHP and vice-versa.

The problem is, the hashes generated in PHP (via Laravel's Hash class, which just uses PHP's password_hash function) return false when tested in node.js.

The following node.js script:

var bcrypt = require('bcrypt');

var password = 'password';

var phpGeneratedHash  = '$2y$10$jOTwkwLVn6OeA/843CyIHu67ib4RixMa/N/pTJVhOjTddvrG8ge5.';
var nodeGeneratedHash = '$2a$10$ZiBH5JtTDtXqDajO6f4EbeBIXGwtcGg2MGwr90xTH9ki34SV6rZhO';

console.log(
  bcrypt.compareSync(password, phpGeneratedHash)  ? 'PHP passed' : 'PHP failed',
  bcrypt.compareSync(password, nodeGeneratedHash) ? 'nodejs passed' : 'nodejs failed'
);

outputs: 'PHP failed nodejs passed', whereas the following PHP script:

<?php

$password = 'password';

$phpGeneratedHash  = '$2y$10$jOTwkwLVn6OeA/843CyIHu67ib4RixMa/N/pTJVhOjTddvrG8ge5.';
$nodeGeneratedHash = '$2a$10$ZiBH5JtTDtXqDajO6f4EbeBIXGwtcGg2MGwr90xTH9ki34SV6rZhO';

print password_verify($password, $phpGeneratedHash)  ? 'PHP passed' : 'PHP failed';
print password_verify($password, $nodeGeneratedHash) ? 'nodejs passed' : 'nodejs failed';

outputs 'PHP passed nodejs passed'.

I've run the tests in Ubuntu 14.04.1 using PHP 5.5.18, node.js v0.10.32 and the npm bcrypt module.

majidarif
  • 18,694
  • 16
  • 88
  • 133
wolfemm
  • 993
  • 3
  • 11
  • 16

3 Answers3

89

This fails because the types of bcrypt hashes being generated from php and node are different. Laravel generates the $2y$ while node generates the $2a$. But the good news is the only difference between 2a and 2y are their prefixes.

So what you can do is make one of the prefix similar to the other. Like:

$phpGeneratedHash  = '$2y$10$jOTwkwLVn6OeA/843CyIHu67ib4RixMa/N/pTJVhOjTddvrG8ge5.';
$nodeGeneratedHash = '$2a$10$ZiBH5JtTDtXqDajO6f4EbeBIXGwtcGg2MGwr90xTH9ki34SV6rZhO';

To something like:

$phpGeneratedHash  = '$2y$10$jOTwkwLVn6OeA/843CyIHu67ib4RixMa/N/pTJVhOjTddvrG8ge5.';
$nodeGeneratedHash = '$2y$10$ZiBH5JtTDtXqDajO6f4EbeBIXGwtcGg2MGwr90xTH9ki34SV6rZhO';

Notice that I replaced the $2a$ of the node hash to $2y$. You can simply do this with:

PHP

$finalNodeGeneratedHash = str_replace("$2a$", "$2y$", $nodeGeneratedHash);

Node

finalNodeGeneratedHash = nodeGeneratedHash.replace('$2a$', '$2y$');

Then compare phpGeneratedHash to finalNodeGeneratedHash.

Note: It is recommended that if you're comparing in PHP, change the prefix of the NodeJS generated hash to $2y$ and if you're comparing in NodeJS; change the prefix of the PHP generated hash to $2a$.

majidarif
  • 18,694
  • 16
  • 88
  • 133
  • 2
    Thanks for the answer, things are working as expected now! I hadn't thought to look up the structure of a bcrypt hash. Is there any obvious reason why the bcrypt module doesn't do what PHP is doing allowing either implementation to work? – wolfemm Oct 30 '14 at 02:28
  • 2
    @wolfemm, Simply, They are using different types/versions of bcrypt. – majidarif Oct 30 '14 at 02:41
  • 1
    Thank you, thank you, thank you, thank you. I was pulling my hair out trying to get an [Auth0 Database Connection](https://auth0.com/docs/connections/database) to [bcrypt.compareSync()](https://www.npmjs.com/package/bcrypt) a [Laravel password hash](https://laravel.com/docs/5.3/hashing). You saved my bacon, friendly Stack Overflow stranger. Thank you. – James Furey Sep 08 '16 at 01:35
  • 3
    ...2 years later. @JamesFurey you're welcome. I figured this out when I had to do the same thing which took awhile. – majidarif May 15 '19 at 23:14
  • Thanks. This got me on the right track. And then `.replace(/^\$2y/, "$2a")` is what I ended up using... from https://stackoverflow.com/a/36043615/ – Ryan Oct 24 '21 at 18:53
7

I have tried to compute what was said before to get codes that work. As you can see I don't need to replace anything.

On the PHP 7.2.4 side:

<?php
$password = "test123";
    $hash = password_hash($password, PASSWORD_BCRYPT);
    echo $hash; // I get $2y$10$5EaF4lMSCFWe7YqqxyBnR.QmDu1XhoiaQxrOFw.AJZkGCYmpsWDU6

On the nodeJS side:

Install bcryptjs package: npm i bcryptjs

var bcrypt = require('bcryptjs');
let hash1="$2y$10$5EaF4lMSCFWe7YqqxyBnR.QmDu1XhoiaQxrOFw.AJZkGCYmpsWDU6";
console.log(bcrypt.compareSync("test123", hash1)); // display true
Nicolas Guérinet
  • 2,086
  • 1
  • 29
  • 38
  • Could it be because you are using `bcryptjs` while the question uses `bcrypt` library – Adarsh Madrecha Jun 09 '19 at 13:16
  • meanwhile bcryptjs has worse performance, look at the testing: https://medium.com/javascript-in-plain-english/node-js-bcrypt-vs-bcryptjs-benchmark-69a9e8254cc2 – Maxim Firsoff Dec 02 '20 at 15:05
  • @MaximFirsoff That doesn't really matter. It's a one-time call on login/pw update, and the algorithm is slowed down on purpose (through repetitions) to resist attacks. Will a slow library help with that? Not really, as attacks will usually be on the raw userdata.... But it also doesn't really hurt. (please note: I don't mean the bryptjs library is slow on purpose, it's just slower than the bcrypt library because its running in JS instead on natively) – DennisK Nov 12 '21 at 12:51
  • This is the solution I'm looking for, Thanks @Nicolas Guérinet – not_null Jul 19 '23 at 06:11
0

The implementation of bcrypt in different language might be differ.

For example, in Node.js version bcrypt.js, the salt length applied are 29 characters

    bcrypt.getSalt = function(hash) {
        if (typeof hash !== 'string')
            throw Error("Illegal arguments: "+(typeof hash));
        if (hash.length !== 60)
            throw Error("Illegal hash length: "+hash.length+" != 60");
        return hash.substring(0, 29);
    };

But, in Go version golang.org/x/crypto/bcrypt, the salt size are 22 of bytes:

const (
    majorVersion       = '2'
    minorVersion       = 'a'
    maxSaltSize        = 16
    maxCryptedHashSize = 23
    encodedSaltSize    = 22
    encodedHashSize    = 31
    minHashSize        = 59
)

So, it might happen that hashed string in Node.js gets error when compared in Go, other languages likewise.

S.K.
  • 59
  • 1
  • 4