切勿存储密码
本课属于 Coddy Node.js 学习旅程的“身份验证与安全”单元。第 4 课,共 24 课。
人们会重复使用密码,因此泄露的明文密码可能会危及其他地方的账户。存储加盐密码哈希:这是一种派生值,你可以通过重新计算来验证它,而不是对其解码。
const salt = randomBytes(16).toString('hex');
const key = await scryptAsync(inviteCode, salt, 32);
const saved = salt + '.' + key.toString('hex');这个示例将盐和派生密钥组合起来,用于邀请码。分隔符可以让验证过程提取盐,并使用相同的参数派生另一个密钥。你的密码任务使用自己的分隔符和密钥长度。每次创建新密码时都生成盐,派生密钥,然后将两者都存储起来。加盐哈希仍然需要强密码,因为攻击者可以尝试猜测。
挑战
简单现在,注册时会原样保存用户输入的密码。请改为保存加盐哈希,这样即使数据库泄露,密码也不会被泄露。
- 在
passwords.js中,修改hashPassword(password),让它返回注册时保存的文本,格式为盐、冒号、哈希:- 盐是 16 个随机字节,每次调用时都重新生成,并以十六进制形式写入:由 0 到 9 和 a 到 f 组成的 32 个字符。
- 哈希是
scrypt根据密码和盐生成的 64 字节密钥,也以十六进制形式写入:共 128 个字符。scryptAsync的第三个参数是密钥长度,单位为字节。 - 用冒号连接这两部分。保存的值看起来像这样:
f2bba0669d9fa2d203afc7e61bf8f065:c3f7cae352e023a8d4766da9a316b5a2c844100efff22c4079f89a8ab8512ac91c33227481c3afa88c3ba0e88347f9e3e0c22dad9d55f19688a350f71be8dbd2
- 你不需要修改
server.js。它的POST /signup已经会调用hashPassword,并保存该函数返回的内容。它还有GET /debug/users/:username,可以显示某个用户保存的内容。这个路由只是为了让你查看,真正的应用不会有它。
试试看:按“运行”。发送 POST /signup,请求体为 {"username": "ada", "password": "correct horse"}。你会得到状态码 201 和 {"username": "ada"}。使用同一个请求体,再发送一次,但把用户名改为 "username": "bob"。现在先发送 GET /debug/users/ada,再发送 GET /debug/users/bob。每个 passwordHash 都由 32 个字符、一个冒号和另外 128 个字符组成。即使 ada 和 bob 选择了相同的密码,这两个值也不相同。
亲手试试
import { randomBytes, scrypt } from 'node:crypto';
import { promisify } from 'node:util';
const scryptAsync = promisify(scrypt);
// 把密码转换成 signup 存储的文本。
// 目前会原样保留输入的密码。
export async function hashPassword(password) {
return password;
}
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
身份验证与安全中的所有课
自己练习: Express 在线编辑器