nodejs中的SALT和HASH密码w/crypto

los*_*ion 30 node.js node-crypto

我试图找出如何使用crypto模块在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();
  });
});
Run Code Online (Sandbox Code Playgroud)

但是我对如何验证密码感到困惑.

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

Mat*_*hew 44

在您正在使用的任何持久性机制(数据库)中,您将存储结果哈希以及盐和迭代次数,这两者都是纯文本.如果每个密码使用不同的盐(您应该这样做),您还必须保存该信息.

然后,您将比较新的纯文本密码,使用相同salt(和迭代)的哈希值,然后将字节序列与存储的字节序列进行比较.

生成密码(伪)

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

验证密码(伪)

function isPasswordCorrect(savedHash, savedSalt, savedIterations, passwordAttempt) {
    return savedHash == pbkdf2(passwordAttempt, savedSalt, savedIterations);
}
Run Code Online (Sandbox Code Playgroud)

  • 是的,如果您不知道用于生成哈希的salt,则无法使用相同的纯文本重建相同的哈希. (4认同)

Twe*_*les 24

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

如果您不太愿意使用内置的加密库,我可能会建议使用bcrypt.这两个在安全方面大致相同,但我认为bcrypt具有更加用户友好的界面.如何使用它的一个示例(直接从上面链接的页面上的bcrypt文档中获取)将是这样的:

创建哈希:

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

要检查密码:

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

编辑添加:

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

编辑更新:

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

  • @TwentyMiles它不是关于速度,而是关于单线程.在像webapp这样的多用户网络服务中,如果使用同步API,整个节点进程将暂停并且除了cpu密集型加密之外什么都不做.这将导致所有其他网络客户端体验服务器冻结.您根本无法在网络服务器中使用任何同步调用.它从根本上打破了节点工作原理的第一个基本概念.请参阅:http://stackoverflow.com/questions/16827373/should-i-use-the-async-file-io-methods-over-their-synchronous-equivalents-for-lo/16827473#16827473 (6认同)
  • 我认为发布node.js问题的同步调用示例会产生误导和混淆.您根本无法在网络服务中使用此类功能,例如基于mongoose的Web应用程序,提问者几乎肯定会在其中工作.请仅发布异步示例,除非您确定同步是合适的,但它几乎从不. (5认同)
  • @JonasDrotleff你不了解上下文.我说的是异步vs同步,这是使1个用户等待慢速加密(异步)与使每个客户端等待任何加密(同步)之间的区别.关键是"你不能在node.js网络服务器中使用同步调用".这种情况下的呼叫是加密的事实只是间接的. (2认同)

hob*_*bbs 9

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

来自我自己的PerlPBKDF2模块的示例散列看起来像
{X-PBKDF2}HMACSHA1:AAAD6A:8ODUPA==:1HSdSVVwlWSZhbPGO7GIZ4iUbrk=包括使用的特定散列算法,以及迭代次数,salt和结果密钥.


And*_*ena 8

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


小智 7

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

它使用pbkdf2并在单个字段中存储散列,salt,算法和迭代.希望能帮助到你.


Sar*_*tra 5

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

希望你发现它有用.