默认 WorkFactor=10 计算过快(约几毫秒),易被暴力破解;生产应设为12(~50ms)或14(~200ms),并用配置项管理;验证前必须 Trim() 明文,确保哈希值完整未截断、未编码污染。

为什么不能直接用 BCrypt.Net-Next 的默认参数
默认调用 BCrypt.HashPassword("pwd") 会使用 WorkFactor = 10,也就是 2¹⁰ = 1024 轮哈希。现在主流 CPU 能在几毫秒内完成——对攻击者来说太“友好”了。安全不是靠算法保密,而是靠计算成本拖慢暴力尝试速度。
- 生产环境建议至少设为
WorkFactor = 12(约 4096 轮),CPU 耗时升到 ~50ms;14更稳妥(~200ms),但得实测别拖垮登录接口 - 别硬编码数字,用配置项或常量:比如
const int WorkFactor = 12;,方便后续统一调整 - 低于
10基本等于没设防;高于16在 Web 场景容易引发超时,尤其高并发时
BCrypt.Verify() 失败的常见原因
最常踩的坑不是密码错,而是字符串处理污染了哈希值或明文——比如前后空格、换行符、BOM、编码不一致。
- 验证前务必
inputPassword.Trim(),用户粘贴密码时极易带空格 - 数据库存的哈希值必须是完整原始字符串(含
$2a$或$2b$前缀、盐和密文),不能被截断或转义过 - 如果从 JSON 或 HTTP Header 读取哈希值,确认没被自动 URL 解码或大小写转换(
BCrypt对大小写敏感) - 错误示例:
BCrypt.Verify("pass ", "$2a$12$...")→ 返回false,但不是算法问题,是空格导致
如何安全地生成和验证——一个最小可靠流程
不依赖封装层,直用 BCrypt.Net-Next 的核心方法,避免中间抽象引入意外行为。
- 注册/改密时:
string hash = BCrypt.HashPassword(plainText.Trim(), workFactor: 12); - 存入数据库前检查长度:
if (hash.Length (正常应为 60 字符左右) - 登录验证时:
bool isValid = BCrypt.Verify(input.Trim(), storedHash);,两个Trim()缺一不可 - 不要自己拼接盐或做 Base64 处理——
BCrypt.HashPassword输出已是标准格式,直接存、直接验
升级旧哈希时要不要重算所有密码
不用。可以懒加载升级:用户下次成功登录时,用新参数重新哈希一次并覆盖旧值。前提是旧哈希本身没被破解(否则早该强制重置)。
- 检测旧哈希强度:
storedHash.StartsWith("$2a$10$")表示老参数,可触发升级 - 升级代码示例:
if (BCrypt.Verify(input, oldHash) && oldHash.StartsWith("$2a$10$")) { newHash = BCrypt.HashPassword(input, 12); SaveNewHash(newHash); } - 注意并发风险:同一账号多次登录可能同时触发升级,用数据库唯一约束或乐观锁防重复写
- 别批量跑脚本重算——万一出错没法回滚,且毫无必要


















