比较用PHP hash()和NodeJS生成的SHA256 crypto.createHash()

时间:2015-10-06 00:09:54

标签: javascript php mysql node.js hash

我正在为NodeJS中的网站制作实时应用程序,允许我的用户使用他们的帐户登录等。

但是,我在登录时遇到了一些问题。

当我在主站点注册/登录用户时,我使用PHP的hash()函数对密码进行了哈希处理:

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

它在我的网站上运行良好 但是我需要能够从NodeJS中的数据库中获取用户的盐,并能够对用户输入的密码进行哈希处理,并根据数据库的密码进行检查,并确保它们匹配记录用户。

我通过做这样的事情来做到这一点:

//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;
}

但是,当我console.log() hashed变量和dbPassword变量时,它们并不相等 - 因此它始终返回false /使用不正确的密码进行响应。

所以,我的问题:
有没有什么方法可以像在PHP中一样准确地在NodeJS中散列sha256字符串?

PS: 目前我正在使用Ajax / jQuery通过PHP脚本登录,但我希望能够完全从Apache / PHP托管转移到刚刚使用NodeJS(SocketIO,Express,MySQL)托管的站点。

我最近刚开始使用NodeJS,我已经在我的Apache站点上使用了NodeJS,但我听说使用NodeJS托管整个站点本身会更好/更高效。

<小时/> 修改: 所以,我决定不使用数据库/ socketio / express进行快速test.js。

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!");
}

至于我如何连接数据库,我这样做:

  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版本:5.5.44-0 + deb7u1(Debian)
存储密码salt的列是text的类型,并且具有utf8_unicode_ci的排序规则


注意:当我改变

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

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

哈希值不同,但hashed变量仍与数据库密码不匹配。

1 个答案:

答案 0 :(得分:4)

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.