在 Node.js 中复制 Java 密码哈希代码 (PBKDF2WithHmacSHA1)

Replicating Java password hashing code in Node.js (PBKDF2WithHmacSHA1)

编辑:我的问题已更新,请查看此 post 底部的最新问题。我把剩下的留给想阅读整个故事的人:)

我一直致力于将一个小型 Java 应用程序翻译成 Node.js,这在很大程度上进展顺利。我不得不查找很多 Java 函数来弄清楚它们的作用以及如何在 Node 中复制它们的行为(因为我对 Java 几乎没有任何经验),但我得到了大多数功能现在都可以使用。

不幸的是,有一点我似乎无法开始工作。它是一种用于生成密码哈希的方法,使用了一组高级 Java-specific 函数,这些函数在 Node.js 中似乎不存在。我已经尝试了两天来让它工作,但我就是得不到我想要的结果。

这是原来的Java代码:

public static String hashPassword(final String password, final String salt) throws NoSuchAlgorithmException, InvalidKeySpecException {
    final char[] passwordChars = password.toCharArray();
    final byte[] saltBytes = salt.getBytes();
    final PBEKeySpec spec = new PBEKeySpec(passwordChars, saltBytes, 1000, 192);
    final SecretKeyFactory key = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
    final byte[] hashedPassword = key.generateSecret(spec).getEncoded();
    return String.format("%x", new BigInteger(hashedPassword));
}

注意:salt是固定值,不是随机的。我知道这不应该是这样,但这是应用程序的设置方式。因此,由于 Java 代码总是得到相同的结果,因此在 Node 中也应该可以得到相同的结果。

我试过使用 crypto.pbkdf2,使用各种看似相似的密​​码,但它们都给了我与 Java 代码不同的结果。所以我想我会在这里问,看看是否有人知道如何做到这一点,或者对如何处理这个问题有任何建议。

请注意(正如我所说)我对 Java 一无所知,所以我很难让它发挥作用可能是因为我很难掌握内部发生的事情这种方法,并在谷歌上搜索所使用的各种功能给出了相互矛盾的答案,而且大多只是表明其他人也很难使用它们。

所以实际上我要问三个问题:

  1. 这是否可以在 Node.js 中复制或 Java 使用 Node 中不存在的功能?
  2. 有更多 Java 经验的人可以解释这段代码中的各个行,以及每一行的作用吗?最好是具有相当程度 Node.js 经验(和一些 PHP)但从未与 Java 合作过的人会理解的方式:)
  3. 如果有人知道,我要查看哪些 Node 函数才能使其正常工作?我可以使用内置的加密模块吗?还是需要额外的模块?

最后,在你说 "just implement a Node-specific hashing algorithm"(这将是更简单的选项)之前,我不能这样做,因为这将用于已经包含这些散列密码的现有数据库,并且被其他现有 Java 应用程序。目前无法更改其他应用程序或数据库。

更新:我得到了一个非常有帮助的答案,现在我在我的 Node.js 代码中得到了这个:

hashPassword = function(password, salt){
    crypto.pbkdf2(password, new Buffer(salt), 1000, 24, 'sha1', function(err, key){

    }
}

这就是我再次陷入困境的地方。我无法从键中获取我需要的字符串值。我用谷歌搜索了一下,发现 Java 代码中的 String.format 行将 BigInteger 转换为十六进制整数,但我似乎无法获得正确的值。

非常感谢任何有关如何从缓冲区中获取正确字符串值的帮助。

Update2:我终于让我的应用程序工作了,事实证明它是一个输出错误哈希值的外部模块。正确实施加密模块修复了它。

这些参数生成相同的缓冲区:

crypto.pbkdf2('test', 'salt', 1000, 24, 'sha1', function(err, key) {});

剩下的就是用同样的方式格式化一个字符串。这可能有点问题,因为 BigInteger 是签名的,所以你也应该考虑到签名。

您可以使用 bn.js 执行以下操作:

function format(key) {
  if (key[0] >>> 7 === 0) {
    return key.toString('hex');
  }

  return '-' + new BN(key.toString('hex'), 16).notn(192).add(new BN(1)).toString(16);
}

bn.js不会将前导位解释为符号,所以你必须先检查它,然后根据two's complement表示转换为字符串。

我需要让它与节点 6 一起工作,事实证明,事情变得更简单了(不需要 bigints)。如果没有 vkurchatkin 的原始答案,我永远不会想出这个,但是:

console.log(hashPassword('password', 'salt').toString('hex'));

function hashPassword(password, salt) {
    return crypto.pbkdf2Sync(password, Buffer.from(salt, 'hex'), 1000, 24, 'sha1');
}