bcrypt.CompareHashAndPassword要求先传已存储哈希、再传明文密码,颠倒即报错;注册时用GenerateFromPassword生成哈希后须转string存入VARCHAR(255)或TEXT字段,密码长度需≤72且非空,cost建议从12起步,登录时直接比对数据库中真实合法的哈希值。

bcrypt.CompareHashAndPassword 的参数顺序必须是「已存储哈希」在前、「明文密码」在后,颠倒就会报 crypto/bcrypt: hashedPassword is not the hash of the given password。这不是 bug,是函数明确拒绝非法输入的保护机制。
注册时用 bcrypt.GenerateFromPassword 生成哈希并存字符串
别把 []byte 直接塞进数据库字段;GenerateFromPassword 返回的是 ASCII 字符串(如 "$2a$10$..."),长度固定约 60 字节,必须转成 string 后存入:
- 数据库字段至少设为
VARCHAR(255)(MySQL)或TEXT(PostgreSQL),VARCHAR(60)是底线,但留余量更稳妥 - 存之前检查密码非空且字节数 ≤ 72:
if len([]byte(pwd)) == 0 || len([]byte(pwd)) > 72,超长部分会被静默截断,导致哈希失效 - 别跳过错误检查:
hashed, err := bcrypt.GenerateFromPassword([]byte(pwd), cost),err 不为 nil 时直接拒掉注册 - cost 值建议从 12 起步,压测后微调;硬编码
10或4(测试用)都得配环境开关,不能混用
登录时用 bcrypt.CompareHashAndPassword 直接比对
数据库查出的哈希字符串(u.Password)就是唯一输入源,绝不再次调用 GenerateFromPassword:
- 先确保查到了有效用户:
err := db.Where("email = ?", email).First(&user).Error,若err == gorm.ErrRecordNotFound,直接返回统一错误,不进比对流程 - 哈希值读出后立刻
strings.TrimSpace(),防止前后空格、BOM 或换行干扰校验 - 调用时顺序不能错:
err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(inputPwd)) - 返回
nil表示通过;返回bcrypt.ErrMismatch是密码错;其他错误(如bcrypt.ErrHashTooShort)说明哈希字段异常(为空、被截断、存了明文)
哈希字段内容必须真实合法,否则比对必然失败
CompareHashAndPassword 不会尝试“容错解析”,它只认标准 bcrypt 格式($2a$ / $2b$ / $2y$ 开头 + 正确长度 + 有效校验和):
- 如果
user.Password是空字符串、"null"、"password123"(明文)、或被 JSON 序列化自动 trim 过,都会触发not the hash of the given password - 调试时第一反应不是改逻辑,而是打长度:
fmt.Println("hash len:", len(user.Password)),不是 60 就立刻查入库路径 - 上线前加一层格式校验(可选):
regexp.MustCompile(`^\$2[abxy]\$\d{2}\$[./A-Za-z0-9]{53}$`).MatchString(user.Password) - 迁移旧数据时,若发现字段存的是明文,必须走重哈希流程,不能“兼容”——否则永远无法安全验证
错误处理要隔离敏感信息,日志要脱敏
所有密码相关失败对外必须返回泛化提示(如 "invalid credentials"),且不暴露任何内部细节:
- 前端看到
401+ 泛化消息,无法判断是账号错还是密码错,防枚举攻击 - 服务端日志里禁止打印明文密码,哪怕只是
log.Printf("pwd: %s", pwd);只记请求 ID、用户名(若存在)、IP、错误类型(如mismatch或invalid hash) - 若需排查哈希问题,只记录哈希前缀:
log.Printf("hash prefix: %s", user.Password[:10]) - 别在 HTTP 响应体里返回原始 error 消息,
err.Error()可能含技术细节,直接透出等于帮攻击者省力
CompareHashAndPassword 就会立刻失败,而且不会告诉你具体哪一环断了。上线前务必抽样检查几条真实记录的 Password 字段内容。


















