旧 HMAC 密码的验证边界
旧账号的 Password 保存的是 HMAC-SHA256(输入密码 + Salt) 的十六进制文本。计算时使用的 HMAC 密钥曾写在 Core 的公开辅助方法里。这个值随源码公开,不能再作为保护密码库的秘密;HMAC 计算也很快,不适合直接存储密码。
目前新建账号、修改密码和登录后的重哈希都使用 IPasswordHasher<User>。已有旧账号仍可能留在数据库中,直接删掉旧算法会让这些账号在密码正确时也无法登录。因此,历史密钥现在只留在 Lab 内部的 LegacyPasswordVerifier,这个类型只提供验证,不提供生成旧格式密码的入口。把密钥移动到这里不会提高已有旧哈希的强度;它的作用是阻止通用 Core API 继续用同一个固定密钥产生新值。
登录时先用 IPasswordHasher<User> 验证。只有验证失败、Salt 非空且不是现代格式标记 v2 时,才计算旧 HMAC 并用固定时间比较。旧密码匹配后,服务生成现代哈希;UpdatePasswordHashAsync 保存成功才允许登录。保存失败仍拒绝登录,数据库不会停留在一次“已认证但未升级”的状态。现代用户不会走旧验证分支。
旧的 UserClaimHelper.JWT2User 仅为了从令牌的手机号查账号,却会顺带生成一个旧 HMAC 密码。现在授权端点直接读取手机号 claim 查询用户,不再构造临时密码。令牌缺少手机号或用户已不存在时返回 401。
兼容性和退出条件
Core 不再提供 CryptologyHelper.HmacSha256(string value) 这个隐含固定密钥的重载;需要计算普通消息 HMAC 的调用方必须明确传入自己的密钥。外部源码若仍使用无密钥重载,需要修改。历史密码验证仍按原来的字节编码、盐和密钥计算,测试使用固定的历史哈希值,而不是调用新代码为测试生成哈希。
数据库里只要还有 Salt 不为 v2 的有效旧账号,就不能删除验证器或历史密钥。可统计剩余账号数量,让活跃用户在登录时升级;长期不登录的账号需要密码重置。确认不再需要旧格式后,才能删除验证器、历史密钥和独立的 Salt 列。此次没有修改 RSA,也没有把 HMAC 重写当作性能优化;没有可对比的性能数据或加速结论。