5.6 密码加密服务
概述
密码加密由 utility/crypto 实现:bcrypt(password+salt) 双重加盐。外部 salt 存库、bcrypt 内部再加盐,双层防御彩虹表与拖库攻击。旧 md5 数据被 IsBcrypt 识别并拒绝登录(强制重置升级)。
加密与校验(utility/crypto)
| 函数 | 职责 |
|---|---|
| GenerateSalt | 生成 8 字节随机盐,base64url 编码截 10 位 |
| HashPassword | bcrypt(password+salt),Cost=DefaultCost |
| VerifyPassword | bcryptHash 与 password+salt 比对 |
| IsBcrypt | 识别是否 bcrypt 格式($2a$/$2b$ 前缀) |
双重加盐设计
text
存储形态:user 表的 password 列(bcrypt 哈希)+ salt 列(外部盐)
加密:HashPassword(明文, salt) = bcrypt(明文 + salt)
校验:VerifyPassword(存储哈希, 明文, salt)
→ 内部计算 bcrypt(明文 + salt) 与存储哈希比对
为什么双重加盐:
* bcrypt 自带 cost 迭代 + 内部盐 → 防彩虹表、抗暴力
* 外部 salt 存库 → 即使哈希算法被攻破,攻击者还需拿到 salt 列
* 同密码不同用户哈希不同 → 拖库后无法横向碰撞密码路径全景
| 路径 | 行为 |
|---|---|
| 用户新增(user.Add) | GenerateSalt + HashPassword(事务外预计算),salt 与 password 同插入 |
| 用户修改密码(index.UpdatePassword) | 旧密码校验(1012 原密码错误)→ 新密码 bcrypt → 更新 |
| 重置密码(user.ResetPwd) | 未传新密码时重置为系统默认密码;管理员操作,logType=reset |
| 用户导入(user.Import) | 未提供密码的行使用默认密码统一 bcrypt |
| 登录(auth.Login) | VerifyPassword 比对;旧 md5 数据被 IsBcrypt 拒绝 |
事务纪律(重要)
bcrypt 必须在事务外预计算
bcrypt 是 CPU 密集操作(DefaultCost 约 6-10ms/次)。事务内做 bcrypt 会拉长行锁持有时间。项目纪律:
text
❌ WithTrans(ctx, func(ctx) error {
hash := HashPassword(...) // CPU 密集在事务内
dao.User.Ctx(ctx).Data(...).Insert()
})
✅ hash := HashPassword(...) // 事务外预计算
WithTrans(ctx, func(ctx) error {
dao.User.Ctx(ctx).Data(...).Insert() // 事务内只做 DB 操作
})租户开户(tenant.Account)与用户新增(user.Add)均遵循此纪律;事务内同时禁止 HTTP/文件 IO。
旧数据兼容(md5 → bcrypt 升级)
text
IsBcrypt(存储值):
true → 正常 bcrypt 比对
false → 旧 md5 数据:登录直接拒绝,提示重置密码拒绝而非自动迁移的原因:md5 无盐(或弱盐)数据无法安全地"验旧写新",强制重置是更安全的升级路径。
安全清单
text
* bcrypt DefaultCost(框架默认,够用;更高安全场景可显式提升 cost)
* salt 为独立列,永不返回接口(GetUser/Detail 均不含 password/salt)
* 操作日志脱敏:param 中 password/oldPassword/newPassword/salt → ***
* 修改密码走旧密码校验(本人)或 ResetPwd(管理员,留审计)
* 无独立复杂度策略——业务层未强制,可按需在密码路径追加规则