不能只用一次哈希,因为易受彩虹表攻击且相同密码产生相同哈希值;必须加随机唯一盐值并使用慢哈希算法(如Bcrypt)以增强安全性。

为什么不能只用一次哈希?
直接对密码做 sha256.Sum([]byte(password)) 是常见误区。攻击者拿到哈希值后,可以用预计算的彩虹表快速反查常见密码;如果多个用户用了相同密码,哈希值也完全一样,暴露了账户关联性。所以单次哈希 ≠ 安全存储。
盐值(Salt)必须随机且唯一
每次注册/修改密码时,都要生成一个全新、高熵的 salt,比如用 crypto/rand.Read 生成 16 字节随机字节。它不能复用,也不能硬编码在代码里。否则加盐就失去意义——攻击者只需为这一个 salt 构建一次彩虹表。
-
salt需和最终哈希值一起存入数据库(明文存,不加密) - 推荐用
encoding/base64编码后再存,避免二进制字段处理麻烦 - 别用时间戳、用户名、ID 等可预测值当 salt,它们不具备随机性
双重哈希不是“哈希两次”,而是“加盐后哈希”
所谓“双重”,是指:先拼接随机 salt 和原始密码,再整体哈希。不是 sha256(sha256(pwd)) 这种嵌套,后者既不防彩虹表,还可能削弱输出分布。
正确写法示例:
立即学习“go语言免费学习笔记(深入)”;
func hashPassword(pwd, salt []byte) string {
h := sha256.New()
h.Write(salt)
h.Write([]byte(pwd))
return hex.EncodeToString(h.Sum(nil))
}
- 顺序很重要:必须是
salt + pwd,而不是pwd + salt(虽然后者也有一定防护,但前缀 salt 更抗长度扩展攻击) - 别省略
h.Sum(nil)后的hex.EncodeToString—— 直接用[]byte存数据库容易引发编码/比较错误 - 验证时,从 DB 取出 salt,调用同一函数,比对结果字符串是否相等
更稳妥的选择:用标准库 golang.org/x/crypto/bcrypt
自己拼接 salt + hash 容易出错,比如 salt 长度不足、编码不一致、比较时没用 subtle.ConstantTimeCompare。而 bcrypt.GenerateFromPassword 内部已封装完整流程:自动生成 salt、控制迭代次数、输出含 salt 的 base64 字符串(如 $2a$10$...),验证时 bcrypt.CompareHashAndPassword 自动提取 salt 并重算。
- 迭代成本(cost)建议设为 10–12,太低不抗暴力,太高拖慢登录
- 不要试图“优化” bcrypt 输出——它自带 salt 和参数,解析逻辑已固化,改格式会导致验证失败
- 如果已有老系统用 SHA256+salt,迁移时可保留旧哈希,在用户下次登录时用 bcrypt 重哈希并更新
真正容易被忽略的点:哈希只是存储侧防护,它解决不了传输过程中的明文问题。前端仍需配合 RSA 公钥加密或 HTTPS,否则密码在到达服务端前就已被截获。


















