Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Comparing SHA256 made with PHP hash() and NodeJS crypto.createHash()

I'm working on making a real-time application for my website in NodeJS, allowing my users to log in with their accounts, etc.

However, I am having some issues with the logging in part.

When I register/login the users on the main site, I hashed their password using PHP's hash() function like so:

$passwordSalt = mcrypt_create_iv(100);
$hashed = hash("sha256", $password.$passwordSalt.$serverSalt);

and it works great on my site
However I need to be able to grab the user's salt from the database in NodeJS and be able to hash the user's inputted password, check it against the database's password, and make sure they match to log the user in.

I do this by doing something like this:

//Check if Username exists, then grab the password salt and password
//Hash the inputted password with the salt in the database for that user
//and the salt I used for $serverSalt in PHP when creating passwords
//check if hashed result in NodeJS is equal to the database password
function checkPass(dbPassword, password, dbSalt){
    var serverSalt = "mysupersecureserversalt";
    var hashed = crypto.createHash("sha256").update(password+dbSalt+serverSalt).digest('hex');
    if(hashed === dbPassword)
        return true;
    return false;
}

However, when I console.log() the hashed variable and the dbPassword variable, they're not equal - so it always return false/responds with incorrect password.

So, my question:
Is there any way I can accurately hash a sha256 string in NodeJS the same way I do in PHP?

PS: For now I am using Ajax/jQuery to login via PHP Script but I want to be able to completely move from Apache/PHP hosting to the site just being hosted with NodeJS (SocketIO, Express, MySQL).

I just started working with NodeJS recently and I have made functionality on my Apache site to work with NodeJS, but I heard that hosting the whole site itself using NodeJS would be a lot better/more efficient.


Edit: So, I decided to make a quick test.js without using the database/socketio/express.
var crypto = require("crypto");
var serverSalt = "";
var passwordSalt = "­"; //The salt directly copied from database
var checkPassword = "password123"+passwordSalt+serverSalt; //All added together
var password = ""; //The hashed password from database
var hashed = crypto.createHash("sha256").update(checkPassword).digest('hex');
console.log(password);
console.log(hashed); //This doesn't match the hash in the database
if(password == hashed){
        console.log("Logged in!");
} else {
        console.log("Error logging in!");
}

As for how I'm connecting to the database, I'm doing so:

  connection.query("SELECT password,passwordSalt FROM users WHERE username = "+connection.escape(data.username), function(err,res){
    if(err){console.log(err.stack);socket.emit("message", {msg:"There was an error logging you in!", mType:"error"});}else{
      if(res.length != 0){
        var dbSalt = res[0]['passwordSalt'];
        var serverSalt = ""; //My server salt
        var dbPassword = res[0]['password'];
        var checkPassword = data.password+dbSalt+serverSalt;
        console.log("CheckPass: "+checkPassword);
        var hashed = crypto.createHash("sha256").update(checkPassword).digest('hex');
        console.log("Hashed: "+hashed);
        if(hashed === dbPassword){
          console.log("Worked!");
          socket.emit("message", {msg: "Logged in!", type:"success"});
        } else {
          console.log("Error logging in!");
          socket.emit("message", {msg: "Your password is incorrect!", type:"error"});
        }
      } else {
        socket.emit("message", {msg: "That user ("+data.username+") doesn't exist!", mType:"error"});
      }
    }
  });

MySQL version: 5.5.44-0+deb7u1 (Debian)
The column the password salt is stored in is type of text, and has a collation of utf8_unicode_ci


Note: When I change

var hashed = crypto.createHash("sha256").update(checkPassword).digest('hex');
To:

var hashed = crypto.createHash("sha256").update(checkPassword, "utf8").digest('hex');

The hashes are different, however, the hashed variable doesn't match the database password still.

like image 760
Axiom Avatar asked Oct 06 '15 00:10

Axiom


1 Answers

TL;DR

2 possibilities:

  1. The ideal solution: change the database field for the salt from TEXT to BLOB.
  2. The compromise: cast the TEXT to binary latin1 using:

    BINARY(CONVERT(passwordSalt USING latin1)) as passwordSalt
    

Then in both cases, use Buffer values everywhere:

var hashed = crypto.createHash("sha256").update(
    Buffer.concat([
        new Buffer(password),
        dbSalt, // already a buffer
        new Buffer(serverSalt)
    ])
).digest('hex');

It's tested and working.

The longer version

And of course the culprit is character encoding, what a surprise. Also a terrible choice on your part to store raw binary to a TEXT field.

Well, that was annoying to debug. So, I set up a MySQL table with a TEXT field and a BLOB field and stored the output of mcrypt_create_iv(100) in both. Then I made the same queries from both PHP and NodeJS.

  • PHP presents both values as identical.
  • JavaScript presents 2 different values.

In both cases, the BLOB was accurate and I even managed to get the proper hash under JavaScript by using Buffer values for all 3 components of the input.

But this did not explain why PHP and JavaScript were seeing 2 differents values for the TEXT field.

  • The TEXT value had a length of 143 octets.
  • The BLOB had a length of 100 octets.

Clearly the BLOB was correct and the TEXT wasn't, yet PHP didn't seem bothered by the difference.

If we look at the MySQL connection status under PHP:

$mysqli->get_charset();

Partial output:

[charset]   => latin1
[collation] => latin1_swedish_ci

Unsurprising really, it is notorious that PHP operates under ISO-8859-1 by default (or latin1 in MySQL), which is why both values where the same there.

For some reason, it seems that setting the charset in the MySQL module for NodeJS doesn't work, at least for me. The solution was to convert at the field level and preserve the data by casting to BINARY:

BINARY(CONVERT(passwordSalt USING latin1)) as passwordSalt

This returned exactly the same output as the BLOB.

But this is not enough yet. We have a mixture of strings and binary to feed to the hashing function, we need to consolidate that. We cast the password and the server salt to Buffer and concatenate:

var hashed = crypto.createHash("sha256").update(
    Buffer.concat([
        new Buffer(password),
        dbSalt, // already a buffer
        new Buffer(serverSalt)
    ])
).digest('hex');

This returns the same output as PHP.

While this works, the best solution is still to use BLOB in the database. In both cases, casting to Buffer is necessary.

like image 196
spenibus Avatar answered Sep 30 '22 07:09

spenibus