nodejs w / crypto中的SALT和HASH密码

我想弄清楚如何使用crypto模块在nodejs中encryption和散列密码。 我能够创build哈希密码做到这一点:

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(); }); }); 

不过,我很困惑如何以后validation密码。

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

在你使用的任何持久性机制(数据库)中,你会把得到的散列和salt以及迭代次数一起存储,这两者都是纯文本。 如果每个密码都有不同的盐分(你应该这样做),你必须保存这些信息。

然后,您将比较新的纯文本密码,使用相同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 }; } 

validation密码(伪)

 function isPasswordCorrect(savedHash, savedSalt, savedIterations, passwordAttempt) { return savedHash == pbkdf2(passwordAttempt, savedSalt, savedIterations); } 

基于nodejs文档( http://nodejs.org/api/crypto.html ),它看起来不像是有一个特定的方法来validation你的密码。 要手动validation它,你将需要计算当前提供的密码的哈希值,并将其与存储的密码进行比较以求相等。 基本上,你可以用原来的挑战密码来做同样的事情,但是使用存储在数据库中的盐而不是生成一个新的密码,然后比较两个哈希值。

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

创build一个哈希:

 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函数的输出在一个string中同时包含散列和salt 。 这意味着您可以只将单个项目存储在数据库中,而不是两个。 还提供了一种方法,在散列发生的同时产生一个盐,所以你根本不用担心盐的pipe理。

编辑更新:

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

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

来自我自己的Perl的PBKDF2模块的示例哈希看起来像
{X-PBKDF2}HMACSHA1:AAAD6A:8ODUPA==:1HSdSVVwlWSZhbPGO7GIZ4iUbrk=它包括使用的特定的散列algorithm,以及迭代次数,盐和结果键。

我认为这个教程对你来说最适合。 只要通过它,它是我find的最好的。 护照教程与Node.js和encryption

希望你觉得有帮助。

面对同样的问题,我把所有东西放在一个模块中: https : //www.npmjs.org/package/password-hash-and-salt

它使用pbkdf2并将散列,盐,algorithm和迭代存储在单个字段中。 希望能帮助到你。

在这种情况下涉及两个主要步骤

1)创build和存储密码

在这里你将不得不做以下的事情。

  • 取用户密码
  • 生成一串随机字符(盐)
  • 将salt与用户input的密码组合在一起
  • 散列组合的string。
  • 将散列和盐存储在数据库中。

2)validation用户密码

这一步将需要validation用户。

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

  • 根据input的用户名获取散列和盐

  • 将salt与用户密码组合在一起

  • 使用相同的哈希algorithm哈希组合。

  • 比较结果。

本教程详细解释了如何使用nodejs crypto完成此操作。 正是你在找什么。 使用NodeJSencryption的盐哈希密码

这是@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)); } }); }); }