开发TOTP双因素认证:如何在数据库安全存储密钥?
安全存储TOTP密钥的可行方案建议
针对你开发基于TOTP的双因素认证系统时遇到的密钥存储难题,我结合实际生产经验给你几个可行的安全方案,帮你平衡安全性和可用性:
方案一:使用独立的系统级加密密钥(优先推荐)
既然哈希方案不可行(需要明文密钥计算TOTP),那最稳妥的方式是用与用户密码完全独立的系统级加密密钥来加密TOTP密钥。
具体做法:
- 生成一个专门的主加密密钥,把它存储在安全的密钥管理服务(比如KMS)或者硬件安全模块(HSM)中,绝对不能和数据库一起存储,更不能硬编码在代码里。
- 当生成用户的TOTP密钥后,用这个主密钥对其进行加密(推荐用
AES-GCM这种带认证的加密算法,既能加密又能防篡改),然后把加密后的密文存储到数据库。 - 验证TOTP时,从数据库取出密文,调用KMS/HSM解密得到明文密钥,计算验证码后立即从内存中清除密钥。
核心优势:完全不受用户密码重置的影响,系统可以独立完成解密操作,不会因为用户改密码导致TOTP密钥失效。而且主密钥的权限可以严格管控,只有负责TOTP验证的服务能访问,降低泄露风险。
注意事项:要定期轮换主密钥,每次轮换后需要重新加密所有用户的TOTP密钥(这个过程可以分批后台执行,不影响用户使用)。
方案二:混合加密(用户密码派生密钥 + 系统主密钥)
如果想兼顾用户密码的额外防护,同时避免密码重置的弊端,可以采用双重加密的思路:
具体流程:
- 生成用户的TOTP密钥
K。 - 先用系统主密钥
SM加密K,得到EK1。 - 再用用户密码通过密码派生函数(比如
Argon2id、PBKDF2)生成用户专属密钥UK,用UK加密EK1得到最终的密文EK2,存储EK2到数据库。 - 验证时:用户输入密码生成
UK,解密EK2得到EK1,再用SM解密EK1得到K,计算TOTP验证码。 - 密码重置时:先通过其他身份验证方式(比如邮箱验证码、短信验证)确认用户身份,然后用新密码生成新的
UK',重新加密EK1(EK1可以临时存储在安全的缓存中,或者重新用SM加密原密钥K生成),替换数据库中的EK2即可。
- 生成用户的TOTP密钥
核心优势:双重加密提供了两层防护,即使其中一层密钥泄露,也不会直接暴露TOTP密钥;同时完美解决了密码重置后无法解密的问题,不需要用户重新绑定TOTP。
方案三:用户密码派生密钥 + 备份机制(备选)
如果坚持想只用用户密码派生的密钥加密TOTP密钥,那必须配套完善的密钥备份机制:
具体做法:
- 用户设置TOTP时,除了用密码派生密钥加密密钥存储,还要生成一个备份版本——用系统主密钥加密原TOTP密钥,把这个备份密文存储在独立的安全存储中(比如单独的加密数据库或者KMS)。
- 当用户重置密码后,先通过严格的身份验证(比如人工审核、多渠道验证)确认身份,然后取出备份的密文,用系统主密钥解密得到原TOTP密钥,再用新密码派生的密钥重新加密后存储到数据库。
注意事项:备份的密文必须严格限制访问权限,恢复过程要做详细的审计日志,防止被滥用。
通用安全实践补充
不管采用哪种方案,这些细节都不能忽略:
- TOTP密钥在内存中处理时,要尽量缩短存活时间,计算完验证码后立即从内存中清除(比如用覆盖内存的方式,避免被内存dump泄露)。
- 数据库中存储的加密后的TOTP密钥,不要和用户密码哈希等敏感信息放在同一个表分区,最好分开存储,降低批量泄露的风险。
- 对所有加密、解密操作做审计日志,记录操作时间、操作人、用户ID等信息,方便追踪异常行为。
内容的提问来源于stack exchange,提问作者Dev0r




