⏺ bcrypt.hash(password, 10) — 生成哈希
输入: "myPassword123", saltRounds=10
步骤1: 生成随机 salt
→ 16 字节随机数 → Base64 编码 → "K1a2b3c4d5e6f7g8h9i0jk"
步骤2: 组合 salt + 迭代次数
→ "$2a$10$K1a2b3c4d5e6f7g8h9i0jk"
^^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^
算法 cost salt
步骤3: 用 Blowfish 算法迭代 2^10=1024 次加密固定文本 "OrpheanBeholderScryDoubt"
→ 每次用 password+salt 作为密钥加密上一轮的输出
→ 得到 24 字节哈希值 → Base64 编码
步骤4: 拼接为最终字符串
→ "$2a$10$K1a2b3c4d5e6f7g8h9i0jkEOuI3fYv8qW2xN5mR7pL4sD6tH1bC"
|___|__|______________________|______________________________|
算法 cost salt (22字符) 哈希值 (31字符)
关键点:salt 嵌入在输出中,所以不需要单独存储 salt。
bcrypt.compare(password, hash) — 验证密码
输入: password = "myPassword123"
hash = "$2a$10$K1a2b3c4d5e6f7g8h9i0jkEOuI3fYv8qW2xN5mR7pL4sD6tH1bC"
步骤1: 从 hash 字符串中解析出元信息
→ 算法: "2a"
→ cost: 10
→ salt: "K1a2b3c4d5e6f7g8h9i0jk"
步骤2: 用解析出的 salt 和 cost,对输入的 password 重新执行一遍 bcrypt.hash
→ bcrypt.hash("myPassword123", "$2a$10$K1a2b3c4d5e6f7g8h9i0jk")
→ 得到新哈希: "$2a$10$K1a2b3c4d5e6f7g8h9i0jkEOuI3fYv8qW2xN5mR7pL4sD6tH1bC"
步骤3: 常量时间比较两个哈希字符串
→ 新哈希 === 存储的哈希 → true (密码正确)
⚠️ 常量时间比较: 无论哪一位不同,都遍历完整个字符串
防止攻击者通过比较耗时推断出"前 N 位是对的"
所以 compare 本质就是用同样的 salt 重新 hash 一遍,看结果是否一致。不需要"解密",因为 bcrypt 是单向的。