什么是密码哈希?
密码哈希是一种单向密码学过程,将口令转换为固定长度的字符串。与加密不同,哈希无法还原——不能从哈希值反推出原始口令。因此哈希适合安全存储口令:即便攻击者拿到哈希,也无法直接得到明文口令。
用户登录时,对其输入的口令用相同算法计算哈希,并与存储的哈希比对;一致则口令正确。系统无需保存明文口令,安全性明显提高。
核心原则: bcrypt、scrypt 等现代密码哈希函数刻意设计得较慢、较耗资源,从而抵抗暴力破解:攻击者尝试大量口令组合时,每次尝试都要付出可观时间与算力。
Bcrypt 概览
Bcrypt 是 Niels Provos 与 David Mazières 于 1999 年基于 Blowfish 密码设计的口令哈希函数,曾在 USENIX 发表,现已成为现代应用中最广泛采用的口令哈希算法之一。
Bcrypt 如何工作
Bcrypt 采用密钥拉伸(key stretching),使哈希过程在计算上昂贵。算法通过工作因子(亦称 cost / 代价因子)控制迭代次数;硬件变快时可提高该因子以维持安全强度。
🔐
可调强度
随硬件升级可提高 cost,以应对未来威胁。
🧂
内置 Salt
Bcrypt 自动生成并嵌入随机盐,降低彩虹表攻击风险。
⏱️
刻意放慢
在拖慢暴力破解的同时,仍能满足正常认证耗时要求。
✅
久经检验
使用逾二十年,未发现重大缺陷,生产环境验证充分。
Bcrypt 哈希格式
bcrypt 哈希具有固定结构,编码了校验所需的全部信息:
示例 Bcrypt 哈希:
$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy
结构分解:
$2a$ - 算法标识(bcrypt 变体)
10 - Cost 因子(2^10 = 1,024 次迭代)
$N9qo8uLOickgx2ZMRZoMye - Salt(22 个字符)
IjZAgcfl7p92ldGxad68LJZdL17lhWy - 哈希值(31 个字符)
Cost 因子说明
Cost 为对数量级:10 表示 2^10(1,024)次迭代,12 表示 2^12(4,096)次。每加 1,计算时间约翻倍:
| Cost 因子 |
迭代次数 |
约耗时 |
适用场景 |
| 8 |
256 |
~40ms |
过快,不推荐 |
| 10 |
1,024 |
~150ms |
最低推荐值 |
| 12 |
4,096 |
~600ms |
多数应用的较好折中 |
| 14 |
16,384 |
~2.5s |
高安全需求应用 |
| 16 |
65,536 |
~10s |
极高安全(可能影响体验) |
性能考量: Cost 越高越安全,但会增加服务器负载与认证耗时。应在安全与体验间取舍;多数应用使用 10–12。
在 CyberChef 中使用 Bcrypt
CyberChef 的 Bcrypt 操作用于为口令生成 bcrypt 哈希,便于测试、理解哈希格式或在开发中生成示例哈希。
使用 Bcrypt 哈希的步骤
- 打开 CyberChef,搜索并添加 Bcrypt 操作
- 在输入区填入口令
- 配置 cost 因子(轮数),常见为 10–12
- 可选:指定 salt,或由工具自动生成
- 在输出区查看 bcrypt 哈希
Scrypt 概览
Scrypt 是 Colin Percival 于 2009 年提出的基于口令的密钥派生函数(PBKDF)。bcrypt 偏重 CPU 开销,scrypt 同时要求高内存,对 GPU 或专用 ASIC 的硬件加速攻击更不友好。
为何出现 Scrypt
GPU 与专用硬件越来越强、越来越便宜,攻击者可并行计算大量 bcrypt。scrypt 通过占用大量内存抬高并行攻击成本,缓解这一问题。
💾
内存困难(Memory-hard)
计算需要大量内存,使 GPU / ASIC 攻击在经济上难以承受。
⚙️
参数可配置
可调整 CPU 代价、内存代价与并行度,细调安全强度。
🛡️
抗 ASIC
内存需求使定制硬件攻击成本显著上升。
🔑
密钥派生
最初用于从口令派生加密密钥,不仅限于存口令哈希。
Scrypt 参数
Scrypt 主要有下列参数,控制计算代价与内存占用:
| 参数 |
符号 |
说明 |
典型取值 |
| CPU / 内存代价 |
N |
决定内存与迭代(须为 2 的幂) |
16384, 32768, 65536 |
| 块大小 |
r |
微调内存访问模式 |
8 |
| 并行度 |
p |
允许并行计算(越大越能利用多核) |
1 |
| 密钥长度 |
dkLen |
派生密钥的字节长度 |
32, 64 |
内存用量估算
scrypt 约需内存:128 × N × r × p 字节
常见参数示例(N=16384, r=8, p=1):
内存 ≈ 128 × 16384 × 8 × 1 = 16,777,216 字节 = 16 MB
更高安全(N=32768, r=8, p=1):
内存 ≈ 128 × 32768 × 8 × 1 = 33,554,432 字节 = 32 MB
在 CyberChef 中使用 Scrypt
CyberChef 的 Scrypt 操作可用 scrypt 派生密钥或对口令做密钥派生式处理,常见于存口令与为加密派生密钥。
使用 Scrypt 的步骤
- 打开 CyberChef,搜索并添加 Scrypt 操作
- 在输入区填入口令或口令短语
- 配置 salt(固定值或随机)
- 设置代价参数 N、r、p
- 指定目标密钥长度
- 在输出区查看派生结果
Bcrypt 与 Scrypt 对比
Bcrypt
- 年代:1999
- 基础:Blowfish 密码
- 主要防线:CPU 密集型迭代
- 内存占用:低(约 4 KB)
- 参数:单一 cost 因子
- 更适合:Web 应用中的口令存储
- 性能:通常快于 scrypt
- 抗 GPU:中等
- 生态:支持极广
Scrypt
- 年代:2009
- 基础:内存困难函数
- 主要防线:内存密集型运算
- 内存占用:可配置(常见 16+ MB)
- 参数:多项(N, r, p, 长度)
- 更适合:高价值场景、密钥派生
- 性能:更慢、更耗资源
- 抗 GPU:很高
- 生态:增长中,常见于加密货币
该选哪个? 多数 Web 应用用 bcrypt 足够且库支持更全;若需最大限度抵御硬件加速攻击,或要从口令派生加密密钥,可考虑 scrypt。二者均远优于 MD5、SHA-1 等过时方案。
常见用途
1. 用户口令存储
注册或修改口令时,先用 bcrypt 或 scrypt 哈希再写入数据库;切勿保存明文口令。
2. API 密钥派生
用 scrypt 从主口令派生 API 密钥,提高暴力破解成本。
3. 口令校验
登录时对提交的口令计算哈希,与库中存储比对,无需知道原始口令即可验证身份。
4. 加密密钥生成
用 scrypt 从用户口令派生加密密钥,即便口令偏弱也能提升密文安全性。
5. 安全审计
用常见口令表尝试破解哈希,评估口令强度与策略效果。
6. 迁移项目
从较弱算法(MD5、SHA-1)迁移时,可在用户下次登录时用 bcrypt 或 scrypt 重算哈希。
安全最佳实践
务必使用 Salt
bcrypt 与 scrypt 都依赖盐抵御彩虹表。bcrypt 会自动生成 salt;使用 scrypt 时请为每个口令生成独立随机 salt。
选择合适的代价参数
- 在安全与体验间平衡
- 认证耗时目标可设在约 250–500ms
- 硬件升级后适当提高代价
- 在目标环境上实测性能
不要自研实现
应使用经过充分测试的库与实现。密码学代码极易写错,细微失误即可导致严重漏洞。
可考虑 Pepper
在哈希前为口令追加应用级密钥(pepper),并与数据库分开保管,增加一层防护。
现代替代方案:可关注 Password Hashing Competition(2015)优胜算法 Argon2,兼具 bcrypt 与 scrypt 的优点。bcrypt 与 scrypt 仍是支持广泛、安全性经实践验证的稳妥选择。
应避免的错误
不要:
- 用 MD5、SHA-1 或裸 SHA-256 做口令哈希——过快易被暴力破解
- 自行设计哈希算法
- 所有用户共用同一 salt
- 开发阶段「暂时」明文存口令
- 仅在客户端哈希(服务端仍须再次安全哈希)
- 为省算力把 cost 设得过低
- 不必要地记录或展示哈希结果
- 认证比对前忘记按相同流程计算哈希
CyberChef 配方思路
以下是与 Bcrypt、Scrypt 相关的配方组合示例:
- 口令 / 哈希实验:Generate random → Bcrypt → To Hex(测试哈希生成)
- 密钥派生链:Scrypt → AES Encrypt(派生密钥后加密)
- 批处理:Fork → Bcrypt(并行处理多条口令)
- 格式转换:Bcrypt → To Base64(便于按存储格式编码)
- 安全测试:生成口令列表 → Bcrypt → 与已知哈希比对
现实流程示例
示例:用户注册
用户输入:"MyPassword123"
↓
服务端收到口令
↓
用 Bcrypt 哈希(cost=12)
↓
写入数据库:"$2b$12$KIXxRH6j1yPnEuVG3dJ2Oe..."
↓
丢弃原始口令
示例:用户登录
用户输入:"MyPassword123"
↓
服务端收到口令
↓
从数据库读取已存哈希
↓
用相同算法对输入口令计算哈希
↓
比对:新哈希 === 已存哈希?
↓
一致:认证成功
不一致:认证失败
示例:用 Scrypt 派生密钥
用户口令:"StrongPassword456"
Salt:"user123_unique_salt"
↓
Scrypt(password, salt, N=16384, r=8, p=1, dkLen=32)
↓
派生密钥:256 位加密密钥
↓
用于 AES-256 加密
↓
保存密文,销毁内存中的密钥
← 返回操作指南