bcrypt.GenerateFromPassword的cost参数必须显式传入且限定在4–31范围内,推荐新项目统一设为12;需校验范围并传[]byte(password),空格不可提前Trim,哈希存储须确保字段长度≥60且不截断。

bcrypt.GenerateFromPassword 的 cost 参数必须显式传入
不传 cost 就调用 bcrypt.GenerateFromPassword,它会退化到 bcrypt.DefaultCost(当前 Go 官方库仍是 10),2026 年已无法抵御中等算力的暴力尝试。实测 cost=10 时单次哈希耗时约 25ms,而 cost=12 升至 80–120ms,P95 登录延迟可控;cost=14 虽更安全,但容易在容器环境触发连接积压或 too many open files 错误。
必须做范围校验:if cost 31,否则 GenerateFromPassword 直接 panic 报 invalid cost。推荐从配置读取后立即校验,而非依赖文档默认值。
- 新项目统一设为
12,平衡安全性与响应延迟 - 若业务对登录延迟极度敏感(如高频内部系统),可压测后谨慎选
11,但不得低于10 - 永远传
[]byte(password),不要提前strings.TrimSpace—— 空格是密码的一部分
CompareHashAndPassword 报 invalid hash 不代表密码错
这个错误不是说“你输错了”,而是 bcrypt.CompareHashAndPassword 根本没法解析你传进去的哈希字符串。真正的问题在哈希本身:格式非法、长度被截、前缀不兼容或为空。
常见破坏哈希完整性的操作:
立即学习“go语言免费学习笔记(深入)”;
- 数据库字段定义过短:
VARCHAR(50)存不下 bcrypt 哈希(标准长度 ≥60) - GORM 或其他 ORM 自动 trim 字符串,把开头的
$2a$12$前空格删了,实际存进去了"$2a$12$..."变成"$2a$12$..."(看着一样,但字节流已损) - 前端没 trim 密码,后端又没校验,导致空密码被哈希成
$2a$12$...,但CompareHashAndPassword遇到空明文会 panic - 从 PHP 迁移来的旧数据含
$2y$前缀 —— Go 官方golang.org/x/crypto/bcrypt不支持,必须手动替换为$2b$
加盐不是你手动拼接 salt + password
bcrpyt 内置盐值生成和嵌入机制,GenerateFromPassword 返回的字符串(形如 $2a$12$...)已完整包含算法标识、cost、salt 和 hash,全部自包含。验证时只需原样传入,无需、也不该拆解或重算。
手写加盐哈希仅适用于非密码场景,比如签名 token、缓存 key、临时凭证等——这些不需要抗暴力破解,但需防碰撞。若真要这么做,必须避开两个坑:
- 别用
sha256.Sum256([]byte(password + salt)).Sum(nil)这种简单拼接,应使用crypto/rand生成足够长(≥32 字节)的随机 salt,并独立存储或绑定上下文 - 永远避免用固定 salt(如硬编码
"mysalt123"),否则失去加盐意义
空密码和零值结构体是静默陷阱
用户注册时没判空、查库失败后没处理 err,直接拿零值 User{} 去比对,CompareHashAndPassword 会因 hashedPassword 为空字符串 panic。这不是边界 case,而是上线即爆的高频问题。
必须在三处卡点拦截:
- 注册接口:强制
if len(password) == 0返回错误,不进入哈希流程 - 登录查询后:
if err != nil || user.Password == "",跳过比对直接返回失败 - 数据库字段:确保
password_hash列允许 NULL,且应用层不把空字符串当合法哈希存入
bcrypt 的安全水位线不在算法本身,而在你如何把它嵌进真实的数据流里——哈希字符串一旦被截断、trim、误判为空或混入旧前缀,再强的算法也形同虚设。



















