49

我试图弄清楚如何使用加密模块在 nodejs 中对密码进行加盐和哈希处理。我可以这样做创建散列密码:

UserSchema.pre('save', function(next) {
  var user = this;

  var salt = crypto.randomBytes(128).toString('base64');
  crypto.pbkdf2(user.password, salt, 10000, 512, function(err, derivedKey) {
    user.password = derivedKey;
    next();
  });
});

但是,我对以后如何验证密码感到困惑。

UserSchema.methods.validPassword = function(password) {    
  // need to salt and hash this password I think to compare
  // how to I get the salt?
}
4

7 回答 7

67

在您使用的任何持久性机制(数据库)中,您都可以将生成的哈希与盐和迭代次数一起存储,这两者都是明文。如果每个密码使用不同的盐(您应该这样做),您还必须保存该信息。

然后,您将比较新的纯文本密码,使用相同的盐(和迭代)对其进行哈希处理,然后将字节序列与存储的字节序列进行比较。

生成密码(伪)

function hashPassword(password) {
    var salt = crypto.randomBytes(128).toString('base64');
    var iterations = 10000;
    var hash = pbkdf2(password, salt, iterations);

    return {
        salt: salt,
        hash: hash,
        iterations: iterations
    };
}

验证密码(伪)

function isPasswordCorrect(savedHash, savedSalt, savedIterations, passwordAttempt) {
    return savedHash == pbkdf2(passwordAttempt, savedSalt, savedIterations);
}
于 2013-06-19T21:15:36.173 回答
30

根据 nodejs 文档 ( http://nodejs.org/api/crypto.html ),看起来没有特定的方法可以为您验证密码。要手动验证它,您需要计算当前提供的密码的哈希值,并将其与存储的密码进行比较以确定是否相等。基本上,您将对质询密码执行与原始密码相同的操作,但使用存储在数据库中的盐而不是生成新的盐,然后比较两个哈希值。

如果您不太愿意使用内置的加密库,我可能会建议您使用bcrypt。两者在安全方面差不多,但我认为 bcrypt 的界面更友好。如何使用它的示例(直接取自上面链接页面上的 bcrypt 文档)如下:

创建一个哈希:

var bcrypt = require('bcrypt');
var salt = bcrypt.genSaltSync(10);
var hash = bcrypt.hashSync("B4c0/\/", salt);
// Store hash in your password DB.

检查密码:

// Load hash from your password DB.
bcrypt.compareSync("B4c0/\/", hash); // true
bcrypt.compareSync("not_bacon", hash); // false

编辑添加:

bcrypt 的另一个优点是genSalt 函数的输出在一个字符串中包含哈希和盐。这意味着您可以在数据库中只存储单个项目,而不是两个。还提供了一种在散列发生的同时生成盐的方法,因此您根本不必担心管理盐。

编辑更新:

回应 Peter Lyons 的评论:你是 100% 正确的。我曾假设我推荐的 bcrypt 模块是一个 javascript 实现,因此异步使用它不会真正加快节点单线程模型的速度。事实证明,情况并非如此;bcrypt 模块使用本机 c++ 代码进行计算,并且异步运行速度更快。Peter Lyons 是对的,您应该首先使用该方法的异步版本,并且仅在必要时选择同步版本。异步方法可能和同步方法一样慢,但同步方法总是很慢。

于 2013-06-19T21:32:39.147 回答
11

将密码和盐存储在数据库中的单独列中,或者(我的首选方法)以与RFC 2307第 5.3 节兼容的格式将密码存储在数据库中。一个例子是{X-PBKDF2}base64salt:base64digest。您还可以在其中存储您的迭代计数,这使您可以在未来增加新帐户和更新密码的帐户的迭代计数,而不会破坏其他所有人的登录。

我自己的 Perl PBKDF2 模块中的示例哈希看起来像
{X-PBKDF2}HMACSHA1:AAAD6A:8ODUPA==:1HSdSVVwlWSZhbPGO7GIZ4iUbrk=其中包括使用的特定哈希算法,以及迭代次数、盐和生成的密钥。

于 2013-06-19T21:25:18.197 回答
9

这是@Matthews 答案的修改版本,使用 TypeScript

import * as crypto from 'crypto';

const PASSWORD_LENGTH = 256;
const SALT_LENGTH = 64;
const ITERATIONS = 10000;
const DIGEST = 'sha256';
const BYTE_TO_STRING_ENCODING = 'hex'; // this could be base64, for instance

/**
 * The information about the password that is stored in the database
 */
interface PersistedPassword {
    salt: string;
    hash: string;
    iterations: number;
}

/**
 * Generates a PersistedPassword given the password provided by the user. This should be called when creating a user
 * or redefining the password
 */
export async function generateHashPassword(password: string): Promise<PersistedPassword> {
    return new Promise<PersistedPassword>((accept, reject) => {
        const salt = crypto.randomBytes(SALT_LENGTH).toString(BYTE_TO_STRING_ENCODING);
        crypto.pbkdf2(password, salt, ITERATIONS, PASSWORD_LENGTH, DIGEST, (error, hash) => {
            if (error) {
                reject(error);
            } else {
                accept({
                    salt,
                    hash: hash.toString(BYTE_TO_STRING_ENCODING),
                    iterations: ITERATIONS,
                });
            }
        });
    });
}

/**
 * Verifies the attempted password against the password information saved in the database. This should be called when
 * the user tries to log in.
 */
export async function verifyPassword(persistedPassword: PersistedPassword, passwordAttempt: string): Promise<boolean> {
    return new Promise<boolean>((accept, reject) => {
        crypto.pbkdf2(passwordAttempt, persistedPassword.salt, persistedPassword.iterations, PASSWORD_LENGTH, DIGEST, (error, hash) => {
            if (error) {
                reject(error);
            } else {
                accept(persistedPassword.hash === hash.toString(BYTE_TO_STRING_ENCODING));
            }
        });
    });
}
于 2017-08-12T17:02:25.420 回答
7

面对同样的问题,我将所有内容整合到一个模块中:https ://www.npmjs.org/package/password-hash-and-salt

它使用 pbkdf2 并将哈希、盐、算法和迭代存储在单个字段中。希望能帮助到你。

于 2014-05-02T20:42:04.683 回答
4

我认为本教程最适合您。只是通过它,它是我发现的最好的。 使用 Node.js 和 Crypto 的 Passport 教程

希望对您有所帮助。

于 2013-06-21T18:55:05.257 回答
4

此方案涉及两个主要步骤

1) 创建和存储密码

在这里,您必须执行以下操作。

  • 取用户密码
  • 生成一串随机字符(盐)
  • 将盐与用户输入的密码结合起来
  • 散列组合的字符串。
  • 将哈希和盐存储在数据库中。

2) 验证用户密码

此步骤将需要对用户进行身份验证。

  • 用户将输入用户名/电子邮件和密码。

  • 根据输入的用户名获取哈希和盐

  • 将盐与用户密码结合起来

  • 使用相同的散列算法对组合进行散列。

  • 比较结果。

本教程详细解释了如何使用 nodejs 加密。正是您正在寻找的。 使用 NodeJS 加密的 Salt Hash 密码

于 2016-07-12T06:37:19.643 回答