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.
TL;DR
2 possibilities:
TEXT
to BLOB
. 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.
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.
TEXT
value had a length of 143 octets. 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.