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

Axi*_*iom 9 javascript php mysql hash node.js

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

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

当我在主站点上注册/登录用户时,我使用PHP的hash()函数来密码他们的密码如下:

$passwordSalt = mcrypt_create_iv(100);
$hashed = hash("sha256", $password.$passwordSalt.$serverSalt);
Run Code Online (Sandbox Code Playgroud)

它在我的网站上工作得很好
但是我需要能够从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;
}
Run Code Online (Sandbox Code Playgroud)

然而,当我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!");
}
Run Code Online (Sandbox Code Playgroud)

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

  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"});
      }
    }
  });
Run Code Online (Sandbox Code Playgroud)

MySQL版本:5.5.44-0 + deb7u1(Debian)
存储密码salt的列是type text,并且具有utf8_unicode_ci的排序规则


注意:当我改变时

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

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

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

spe*_*bus 5

TL; DR

2种可能性:

  1. 理想的解决方案:将盐的数据库字段从TEXT更改为BLOB
  2. 折衷方案:TEXT使用以下命令将强制转换为二进制latin1:

    BINARY(CONVERT(passwordSalt USING latin1)) as passwordSalt
    
    Run Code Online (Sandbox Code Playgroud)

然后,在两种情况下,请Buffer在各处使用值:

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

经过测试,可以正常工作。

较长的版本

当然,罪魁祸首是字符编码,真是令人惊讶。同样,将原始二进制存储到TEXT字段中也是一个糟糕的选择。

好吧,这很烦人。因此,我使用一个TEXT字段和一个BLOB字段设置了一个MySQL表,并将mcrypt_create_iv(100)两者的输出存储在其中。然后,我从PHP和NodeJS进行了相同的查询。

  • PHP将两个值表示为相同。
  • JavaScript表示2个不同的值。

在这两种情况下,BLOB都是准确的,我什至设法通过Buffer对输入的所有3个组件使用值来在JavaScript下获得正确的哈希值。

但这并不能解释为什么PHP和JavaScript在该TEXT字段中看到两个不同的值。

  • TEXT值的长度为143个八位位组。
  • BLOB长度为100个八位位组。

显然,“ BLOB正确”和“ TEXT不是” 是正确的,但是PHP似乎并没有为差异所困扰。

如果我们查看PHP下的MySQL连接状态:

$mysqli->get_charset();
Run Code Online (Sandbox Code Playgroud)

部分输出:

[charset]   => latin1
[collation] => latin1_swedish_ci
Run Code Online (Sandbox Code Playgroud)

实际上,毫不奇怪,PHP在ISO-8859-1默认情况下(或latin1在MySQL中)运行是众所周知的,这就是为什么两个值在那里相同的原因。

出于某种原因,至少在我看来,在MySQL模块中为NodeJS设置字符集似乎不起作用。解决方案是在字段级别进行转换,并通过强制转换为来保存数据BINARY

BINARY(CONVERT(passwordSalt USING latin1)) as passwordSalt
Run Code Online (Sandbox Code Playgroud)

返回的输出与完全相同BLOB

但这还不够。我们将字符串和二进制混合在一起以提供给哈希函数,我们需要对其进行合并。我们将密码和服务器盐加入Buffer并连接:

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

这将返回与PHP相同的输出。

尽管这可行,但是最佳的解决方案仍然是BLOB在数据库中使用。在两种情况下,Buffer都必须强制转换为。