不能直接用 md5 或 sha256 哈希密码,因其计算过快且无盐,易被彩虹表或 gpu 暴力破解;应使用 bcrypt 等自适应哈希方案,它内置加盐、可调成本因子、恒定时间比较,且 go 生态支持成熟。

为什么不能用 md5 或 sha256 直接哈希密码
直接对密码做 md5 或 sha256 哈希是危险的——它们计算太快,且不带盐(salt),攻击者可以用预计算的彩虹表或 GPU 暴力秒破。Go 标准库不提供密码专用哈希函数,必须用 golang.org/x/crypto/bcrypt、scrypt 或 argon2 这类自适应哈希方案。
目前最推荐的是 bcrypt:它内置加盐、可调成本因子、广泛验证、Go 生态支持成熟。除非有合规要求(如 FIPS),否则别自己拼接 salt + sha256。
用 bcrypt.GenerateFromPassword 安全生成哈希
关键不是“能不能哈希”,而是“是否用了足够慢且带盐的方式”。bcrypt.GenerateFromPassword 自动处理盐值生成和编码,输出是包含算法、成本因子、盐和哈希的完整字符串(如 a$...)。
- 成本因子建议设为
10~14:值越大越慢越安全,但会拖慢登录;本地开发可用10,生产环境根据 CPU 能力测压后选12或13 - 输入密码必须是
[]byte或string,但注意不要传空字符串或超长字符串(bcrypt 限制 72 字节有效长度,多余部分会被截断) - 不要自己生成 salt 并拼接——
bcrypt内部已强制加盐,手动干预反而破坏安全性
hash, err := bcrypt.GenerateFromPassword([]byte("myP@ssw0rd"), 12)
if err != nil {
log.Fatal(err)
}
// 输出类似: $2a$12$VbFm...(含算法标识、成本、盐、哈希)
用 bcrypt.CompareHashAndPassword 校验而非手动比对
永远不要用 == 或 bytes.Equal 比较哈希结果——这会引发时序攻击。必须用 bcrypt.CompareHashAndPassword,它内部做了恒定时间比较。
- 第一个参数是原始哈希字符串(即
GenerateFromPassword的输出),第二个是待校验的明文密码[]byte - 即使哈希格式错误(比如被篡改)、成本因子不匹配、或密码为空,该函数都统一返回
bcrypt.ErrMismatch,不泄露任何中间信息 - 如果传入的哈希字符串不是 bcrypt 格式(如误传了 sha256 值),会返回
bcrypt.ErrInvalid,需提前过滤或记录告警
err := bcrypt.CompareHashAndPassword(hash, []byte("myP@ssw0rd"))
if err != nil {
// err == bcrypt.ErrMismatch 表示密码错,不要区分提示“用户不存在”还是“密码错误”
}
常见坑:成本因子写死、哈希存储截断、错误处理暴露信息
很多线上事故不是算法选错,而是周边细节失控:
- 成本因子硬编码在代码里,升级时忘记调整——建议从配置或环境变量读取,并在启动时校验范围(
4~31,低于4太弱,高于31会溢出) - 数据库字段长度不够:bcrypt 输出最长约 60 字符,但有些老 schema 设了
VARCHAR(32),导致哈希被截断,后续永远校验失败 - 登录接口返回不同错误信息:“用户不存在” vs “密码错误”——这等于帮攻击者枚举用户名,应统一返回“用户名或密码错误”
- 没处理
context.Context超时:bcrypt 是 CPU 密集型操作,若成本因子过高又无超时,可能拖垮整个 API。建议包装成带超时的调用
真正难的从来不是调一个函数,而是让哈希行为在所有边界条件下都保持安全、一致、可观测。











