不推荐在 Gin 中用 UUID + MD5 组合处理用户密码,因其无盐、快速、可碰撞且 UUID 伪随机导致盐失效;应使用 bcrypt.GenerateFromPassword,它内置安全 salt、支持 cost 控制、抗暴力破解。

不推荐在 Gin 中用 UUID + MD5 组合处理用户密码——它既不安全,也不符合现代密码存储规范。
为什么 UUID + MD5 是危险的组合
MD5 是一种快速、可碰撞、无盐的哈希算法,早已被证实不适用于密码存储;而拼接 UUID 作为“盐”只是伪随机,且该 UUID 若随密码一并存入数据库(如 "hash@uuid"),攻击者拿到数据库后可直接对每个 UUID + 常见密码做穷举(相当于固定盐)。更严重的是:MD5 输出长度固定(32 字符十六进制),无法抵抗 GPU/ASIC 批量爆破。
- 相同密码 + 相同 UUID → 恒定 MD5 值 → 彩虹表可直接命中
- UUID 可预测或泄露(如日志、调试输出、前端暴露)→ 盐失效
- MD5 无计算代价(cost=1)→ 每秒可尝试上亿次尝试
bcrypt.GenerateFromPassword 是 Gin 密码加密的事实标准
Gin 本身不提供加密能力,但 Go 生态中 golang.org/x/crypto/bcrypt 是最成熟、内置 salt、支持 cost 控制的密码哈希方案。它被设计为“慢哈希”,天然防暴力破解。
-
bcrypt.DefaultCost默认为 10,意味着约 2¹⁰ 次哈希迭代;可按需调高(如 12–14),但需实测 CPU 开销 - 每次调用
GenerateFromPassword都生成全新 salt,并将 salt 和 hash 编码进同一字符串(如"$2a$10$abc...xyz..."),无需额外字段存 salt - 验证时只需传入原始密码和完整 hash 字符串,
CompareHashAndPassword自动提取 salt 并重算比对
hashed, err := bcrypt.GenerateFromPassword([]byte("123456"), bcrypt.DefaultCost)
// 输出类似: "$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy"
若必须兼容旧 MD5+UUID 逻辑,只能用于迁移,不可用于新注册
线上已有 MD5+UUID 用户数据?可以写迁移函数,在用户首次登录时升级哈希:
- 解析旧格式:
strings.Split(hashField, "@")→ 得到md5Hash和uuid - 用
md5.Sum([]byte(uuid + password))重新计算,与md5Hash比对 - 若匹配,立即用
bcrypt.GenerateFromPassword生成新 hash,更新数据库字段 - 后续所有校验走标准
bcrypt.CompareHashAndPassword
注意:不要在注册/登录路径里保留 MD5 分支逻辑;迁移完成后,彻底删除相关代码。
容易被忽略的关键点
很多人以为“加了 UUID 就算加盐”,其实盐必须满足:不可预测、唯一 per-password、不与 hash 共享生命周期。bcrypt 的 salt 是 cryptographically secure random 生成的 16 字节,编码后嵌入 hash 字符串前段,且每次独立;而 UUIDv4 虽随机,但若复用(比如一个用户多次注册用同一个 UUID)、或被日志打印、或被前端 JS 生成(时间戳可推测),就完全丧失 salt 价值。真正难的不是写对 bcrypt 调用,而是守住 salt 的机密性与唯一性边界。


















