不能用md5或sha256直接哈希密码,因其是快速哈希函数,专为完整性校验设计,缺乏加盐、不可调慢速等抗暴力破解特性,易遭gpu加速穷举和彩虹表攻击;应使用bcrypt等慢哈希方案,它内置随机盐、可调成本因子、恒定时间比较,且go生态支持成熟。

为什么不能用 md5 或 sha256 直接哈希密码
因为这些是快速哈希函数,专为校验完整性设计,不是为抗暴力破解优化的。攻击者用 GPU 一秒钟能试几亿次 sha256("password123"),而真实系统里用户密码往往很弱。必须用慢哈希(slow hash)加盐(salt),且盐不能复用、不能硬编码。
用 golang.org/x/crypto/bcrypt 是最稳妥的选择
bcrypt 内置随机盐、可调计算强度(cost),Go 官方维护的 x/crypto 包已稳定多年,无需自己实现 PBKDF2 或 scrypt。它返回的哈希字符串本身就包含 salt 和 cost,校验时自动提取,不用单独存 salt。
实操建议:
- 生成哈希时用
bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost);生产环境建议把 cost 提到12或14(更高更慢但更安全) - 校验时直接用
bcrypt.CompareHashAndPassword(hashBytes, []byte(inputPassword)),不要自己解析哈希字符串 - 输入密码长度要限制(比如 ≤ 72 字节),因为
bcrypt只取前 72 字节,多余部分被忽略——这容易导致“password1234567890...”和“password1234567890...+ 长尾”被当成相同密码
校验失败时别暴露具体原因
如果返回 “密码错误” 或 “用户不存在” 不同错误,攻击者就能枚举用户名。统一返回 “用户名或密码错误” 即可,且校验逻辑必须执行完整:即使查不到用户,也要调用一次 bcrypt.CompareHashAndPassword(用一个固定假哈希),避免时间侧信道泄露用户是否存在。
在 Go 中使用 google/wire 实现编译时依赖注入——wire.NewSet、wire.Build、wire.Bind(接口→实现)、wire.Struct、wire.Value、wire.Interface
示例伪逻辑:
// 假设 userDB.FindByLogin 返回 *User 或 nil
user := userDB.FindByLogin(login)
hash := []byte("$2a$00$invalidhash") // 固定假哈希
if user != nil {
hash = user.PasswordHash // 实际哈希
}
err := bcrypt.CompareHashAndPassword(hash, []byte(password))
if err != nil {
return errors.New("用户名或密码错误") // 统一错误信息
}
别把哈希当密钥,也别拿它做其他用途
bcrypt 哈希是单向的,不可逆,也不能当 AES 密钥、JWT 签名密钥或 session ID 使用。有人试图用 bcrypt 输出的 base64 字符串截取前 32 字节当加密密钥——这是错的:它的输出不是均匀随机字节,且含算法标识和 salt,不符合密钥熵要求。
真正需要密钥时,用 crypto/rand.Read 生成;需要派生密钥时,用 golang.org/x/crypto/scrypt 或 pbkdf2,并单独提供 salt 和迭代次数。
另外,数据库字段类型要用 TEXT(不是 VARCHAR(60)),因为 bcrypt 哈希长度固定但随 cost 变化,cost=14 时约 60 字符,留点余量更稳妥。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










