MD5加盐不能用于密码存储,因其计算过快(GPU每秒数十亿次),仅防彩虹表却无法抵御暴力穷举;Go中应使用bcrypt,它自动管理随机盐、可调迭代成本并自包含编码。

别用 MD5 + salt 做密码存储,哪怕加了盐也不行 —— 它在 2026 年已完全不满足最低安全要求。
为什么 md5 + salt 在 Go 里不能用于密码存储
MD5 是确定性极强、计算极快的哈希函数,现代 GPU 每秒可暴力跑数十亿次。加盐只能防彩虹表,但无法抵抗暴力穷举或字典攻击。真实攻防中,攻击者拿到数据库后,会直接对每个 salt + password 组合做离线爆破 —— md5 根本没成本门槛。
常见错误现象:
- 用
time.Now().Unix()或用户名当 salt —— 缺乏密码学随机性,salt 可预测 - 拼接顺序写成
password + salt而非salt + password—— 若 salt 含特殊字符(如\x00),可能被截断或解析异常 - 把 salt 和 hash 存在不同字段、没做长度校验 —— 验证时
strings.Split(dbHash, "$")可能 panic 或越界
真正该用的 Go 密码哈希函数是 bcrypt
golang.org/x/crypto/bcrypt 是 Go 生态事实标准,它自动完成三件事:生成强随机 salt、控制哈希迭代轮数(Cost)、把 salt 和 hash 编码进同一字符串。你不需要手动拼接、解析或管理 salt 生命周期。
立即学习“go语言免费学习笔记(深入)”;
实操建议:
- 使用
bcrypt.DefaultCost(当前为 12)起步;若 CPU 资源允许,可设为 14,显著拖慢暴力尝试速度 -
bcrypt.GenerateFromPassword([]byte(pwd), cost)返回的字符串形如$2a$12$...,含算法标识、cost、salt 和 hash,全部自包含 - 验证必须用
bcrypt.CompareHashAndPassword([]byte(storedHash), []byte(inputPwd)),不要自己拆解 hash 字符串 - 避免在
CompareHashAndPassword前对输入密码 trim 或 toLower —— 用户密码大小写敏感,预处理会引入逻辑漏洞
如果真要手写加盐哈希(仅限非密码场景)
比如签名 token、临时凭证、缓存 key 等不需要抗暴力破解的场景,可用 sha256 + crypto/rand 手写,但必须避开典型陷阱:
- salt 必须用
crypto/rand.Read(),禁用math/rand—— 后者可被预测 - salt 长度至少 16 字节(128 bit),太短易被穷举
- 拼接方式推荐
h.Write(salt); h.Write(password),而非h.Write(append(salt, password...))—— 后者可能修改原 slice 底层数组,引发并发写冲突 - 若需兼容旧系统(如对接 Java 的 PBKDF2),优先用
golang.org/x/crypto/pbkdf2,而非自己循环调用md5—— 多次 MD5 ≠ PBKDF2,缺少 HMAC 和伪随机函数设计
SM3 加盐不是“更安全”,而是“合规刚需”
在金融、政务等国密合规场景中,sm3(通过 github.com/tjfoc/gmsm/sm3)确实要用,但它和 bcrypt 不是替代关系:SM3 是哈希函数,bcrypt 是密码哈希方案。SM3 本身不带 cost 控制、不自带 salt 管理 —— 你仍得自己实现 salt 生成、拼接、存储格式(如 sm3$v1$<salt>$<hash></hash></salt>),且必须额外叠加迭代(如 PBKDF2-SM3)才能达到基本防护水位。
容易被忽略的关键点:
- SM3 输出是 32 字节 raw bytes,
hex.EncodeToString后变成 64 字符 —— 数据库字段长度必须预留足够空间,否则 silently truncate - 国密算法在非国产芯片(如 Apple Silicon)上无硬件加速,纯软件实现性能比 SHA256 低约 3–4 倍,高并发登录接口需压测验证
-
sm3Hash(password, salt)这类函数若没加const time.Sleep(1 * time.Nanosecond)类似防护,可能暴露时序侧信道 —— 实际中应统一走subtle.ConstantTimeCompare做 hash 比较


















