摘要:动态口令(OTP)的安全并不取决于那串每 30 秒跳动一次的 6 位数字,而取决于生成它的种子密钥(seed)是否得到了妥善保护。本文从传统 OTP 系统把种子明文落库的风险切入,讲解如何借助 HSM 实现种子密钥的托管与安全注入:密钥永不明文导出、校验全程在 HSM 内完成、种子通过安全信道分发、以及轮换、吊销与多应用隔离的完整方案。 正在上传图片...
关键词:动态口令、TOTP原理、OTP双因素、国密SM3、双因素认证、手机令牌、硬件令牌、密钥安全
在基于时间的一次性口令(TOTP)体系中,服务端和用户令牌必须持有相同的种子密钥,再结合当前时间窗口(典型 30 秒)通过哈希算法(SHA1、SHA256、SHA512 或国密 SM3)生成 6 位动态口令。从密码学角度看,TOTP 是对称算法:掌握种子与时间计数器,任何人都能复现正确口令。真正的信任根不是那串短暂变化的数字,而是种子密钥本身。
很多自研 OTP 系统把种子当作普通字段直接写入业务数据库,落库前最多做一次静态加密甚至明文存储。这种做法埋下三层隐患。
第一,数据库横向风险。一旦数据库被拖库,攻击者可一次性拿到全部种子。由于 TOTP 离线可算,攻击者即便不触碰业务系统,也能自行计算任意时间窗口的口令,绕过双因素认证。
第二,运维与备份扩散风险。明文种子随数据库备份、跨机房同步、测试环境脱敏不彻底而不断复制,攻击面从单点变成一片。
第三,内部人员滥用风险。具有数据库直连权限的人仅靠一条 SQL 即可导出全部种子,传统审计只记录"谁访问了哪张表",无法证明数据是否被带走。在金融、保险、海关等强合规行业,这种"看得见明文"的设计本身就是审计红线。
正确目标是:让种子在任何时刻、任何位置都不以明文出现,唯一允许它参与运算的地方,是受物理与逻辑双重保护的 HSM 内部。
HSM(硬件安全模块)专门负责密钥生成、存储与密码运算。其核心承诺是:密钥一旦以"密钥对象"进入 HSM,就永远不能以明文导出,所有运算都必须在 HSM 内完成并返回结果。把 HSM 引入 OTP 体系,需遵循五条原则。
原则一:种子在 HSM 内生成,永不明文离开。 种子由 HSM 随机数发生器直接产生,以"不可导出"的密钥句柄(handle)留在 HSM。业务库只保存无意义的引用标识符(key_id)及算法、位数、状态等元数据。即便库被拖走,攻击者拿到的也只是无法反推种子的 key_id 字符串。
原则二:TOTP 校验全程在 HSM 内完成。 服务端把"用户 ID 对应的 key_id + 挑战时间 + 用户输入的 6 位口令"发往 HSM,由 HSM 用内部种子计算期望口令并比对,只返回"通过/失败"。种子从不出现在服务端内存或日志。
原则三:密钥按用户与应用双维度隔离。 每把种子绑定具体用户标识与应用标识,调用须同时提供正确句柄与访问上下文,避免越权校验。
原则四:密钥生命周期由 HSM 与密钥管理服务共管。 创建、激活、轮换、吊销、销毁都通过受控接口,并留防篡改审计日志。
原则五:性能与高可用提前规划。 TOTP 校验属高频、低算力但请求量大的场景,需在架构上做好连接池、批量与限流,避免 HSM 成为瓶颈。
HSM 内 TOTP 校验本质就是"时间分片 + HMAC + 截断取模"(RFC 6238),差异只在 HMAC 的密钥(种子)必须留在 HSM。
# 输入:key_id(HSM 内种子句柄)、challenge_time(挑战时间)
# 输入:user_code(用户提交的 6 位口令)
# 输出:PASS / FAIL,以及匹配的时间窗口偏移(抗时钟漂移)
def hsm_verify_totp(key_id, challenge_time, user_code):
# 1. 时间计数器 T = floor((challenge_time - T0) / X),X=30
T = (challenge_time - T0) // X
# 2. 在允许漂移窗口内尝试,例如 [-1, 0, +1]
for drift in (-1, 0, +1):
counter = T + drift
# 3. 关键:HMAC 在 HSM 内完成,种子不离开 HSM
expected = hsm_hmac_sha(key_id, big_endian_8bytes(counter))
# 4. 动态截断得到 6 位
code = totp_truncate(expected, 6)
if code == user_code:
return PASS, drift
return FAIL, None注意 hsm_hmac_sha 的语义:种子只有 HSM 能访问,服务端调用的是"用这个句柄做一次 HMAC"的指令,而非"把密钥返回给我",这是方案安全性的基石。

一次真实登录校验的全链路时序如下。
[用户手机令牌] [OTP 业务服务端] [HSM]
| | |
| 当前 6 位口令 | |
|----------------------->| |
| | 查 key_id(用户,应用) |
| |------------------------->|
| | | 取出种子句柄
| | 校验(key_id,时间,口令) |
| |------------------------->|
| | | HMAC*N窗口
| |<-------------------------| PASS/FAIL
| |<-------------------------|
|<-----------------------| |
| 登录成功/失败 | |种子只存在于 HSM 受保护内存,业务端全程只搬运"口令、key_id、结果"三类无害数据。即便链路被抓包,攻击者看到的也只是 key_id 与时间戳,无法复原口令。
算法选择上,除 SHA1、SHA256、SHA512 外,金融、政务、关基等受监管场景应支持国密 SM3 作为 HMAC 底层哈希。SM3 输出 256 位,足以支撑 TOTP 截断取模。当算法标识置为 SM3,上述 hsm_hmac_sha 实际调用 HSM 内 SM3-HMAC 指令,算法标签随 key_id 作为元数据持久化,对业务代码透明。
校验侧把种子锁进 HSM 后,新问题出现:种子在 HSM 内生成,用户令牌却必须持有同一份种子才能算出相同口令——种子如何安全地从 HSM "分身"到令牌而不泄露?
需澄清:HSM 不导出种子明文,不等于用户令牌拿不到种子。 工程上有两种合规做法。
第一种"注册期注入":用户注册环节由 HSM 内部生成种子,并直接封装进一次性受保护注册凭证(带签名与时间戳的加密票据)。手机令牌扫码后在安全区解密存储,HSM 未把明文吐给服务端。
第二种"令牌预置":硬件令牌或批量发放场景,种子在制卡或烧录环境中注入令牌,同时把同一份种子的句柄(非明文)登记进 HSM,从生成到注入都在受控边界内完成。
无论哪种,扫码注册须走加密信道并做完整性保护,否则二维码在展示、传输、截屏任一环被替换,用户会扫入攻击者控制的种子。要点如下。
[后台/HSM] [展示端] [手机令牌APP] [中间人]
| 生成种子(句柄k) | | |
| 构造注册票据: | | |
| enc(seed,k_sess) | | |
| +签名 +有效期 | | |
|-------------------->| | |
| | 展示二维码(加密信道) | |
| |-------------------------\ |
| | 替换二维码? |
| |<------------------------- 篡改票据 |
| | 用户扫码 | |
| |----------------------->| |
| | | 验签名+有效期 |
| | | 解密得到种子 |
| | | 本地安全存储 |
|<-------------------(仅返回注册成功确认)------| |归纳要点:展示二维码须基于加密信道,内容含服务端签名与时间戳,令牌先验签再解密;种子在票据内用一次性会话密钥加密;令牌把种子存于系统安全存储或私有加密区,不落明文文件;硬件令牌经出厂烧录或专用设备注入,明文仅存极短时间。
兼容性是现实需求:TOTP 是开放标准,同一份种子用 base32 或 hex 编码后,可写入符合 RFC 6238 的第三方令牌。开放编码只是"注入令牌那一刻"的表示形式,解决互通问题,不削弱 HSM 托管安全——因为编码动作也只是受保护边界内的最后一次转换。
再坚固的密钥也有生命周期。员工离职、令牌丢失、疑似泄露、合规定期更换都会触发轮换与吊销。HSM 托管下,这些操作是对 HSM 内密钥对象状态机的管理,而非改数据库字段。
建议为每把种子维护如下状态。
状态 | 含义 | 允许的操作 |
|---|---|---|
INIT | 已生成,待激活 | 激活、销毁 |
ACTIVE | 正常服务中 | 校验、轮换、吊销 |
ROTATING | 轮换中(新旧并存) | 双密钥校验、确认新密钥 |
REVOKED | 已吊销 | 仅留审计,拒绝校验 |
DESTROYED | 已销毁 | 不可恢复 |
轮换的关键是"新旧并存":HSM 生成新句柄,旧种子进入 ROTATING 并保留短暂重叠期(如 24 小时),期间两者均可校验,用户可无中断地重新扫码注册;重叠期结束旧种子转 REVOKED。
吊销用于令牌丢失或泄露的紧急处置:对应 key_id 置为 REVOKED,HSM 立即返回失败,攻击者即便握旧种子也无法通过。因种子明文从未离开 HSM,吊销即时且不可逆。
为支持并存校验,校验逻辑须能遍历用户有效密钥集合。
def verify_user(user_id, app_id, user_code, t):
keys = list_active_keys(user_id, app_id) # 返回 ACTIVE + ROTATING 的 key_id
for k in keys:
if hsm_verify_totp(k, t, user_code) == PASS:
return PASS
return FAIL这种多密钥并存模型同时解决"换手机重注册"和"旧令牌回收",又不让旧种子以明文游离。
企业常把双因素认证同时挂到多个系统:办公门户、堡垒机、云桌面、代码仓库等。各自部署一套 OTP 后台与 HSM 会让成本与运维失控。更好做法是"一个后台对接多应用",用命名空间隔离种子。
密钥标识建议用三元组 (tenant_id, app_id, user_id)。tenant_id 做多租户隔离,app_id 区分业务系统,user_id 标识用户。每把句柄绑定该三元组,校验须携带完整上下文,HSM 内部做权限校验,确保"A 应用的 key_id 不能被 B 应用拿来校验"。
接入上,业务系统通常通过两种协议转发校验请求:其一是标准 Radius 协议,适合堡垒机、云桌面、网络设备这类原生支持 Radius 的远程接入场景;其二是 REST API,适合办公门户、代码仓库等 Web 业务在登录流程嵌入二次认证。无论哪种,后台先解析三元组,再拿对应 key_id 去 HSM 校验,对上游只返回"成功/失败"。
[业务系统A] --Radius--> [OTP后台] --key_id+code--> [HSM] --> PASS/FAIL
[业务系统B] --REST ---> [OTP后台] --key_id+code--> [HSM] --> PASS/FAIL
[业务系统C] --Radius--> [OTP后台] --key_id+code--> [HSM] --> PASS/FAIL
(同一套后台,按 app_id 路由到不同种子命名空间)用户自注册也纳入该命名空间:在某应用注册页完成扫码后,后台自动在其三元组下登记种子句柄,无需管理员逐人录入。
TOTP 校验看似一次 HMAC,但放在企业全量用户登录高峰面前请求量可观。HSM 单设备吞吐有限,架构层须做好三件事。
第一,连接与句柄复用。HSM 访问通常经 PKCS#11 或厂商 SDK 建立会话,频繁开闭代价高。后台侧应维护到 HSM 的连接池,并把 key_id 到会话的映射做本地缓存(缓存的只是句柄引用,绝非种子明文)。
第二,限流与熔断。针对单用户应限制单位时间校验次数(如每分钟最多 5 次、连续失败 10 次锁定)。这能压制针对某种子的在线爆破——攻击者每试一个口令都要走一次 HSM 并受限流约束;而 6 位数字单窗口仅 100 万组合,叠加时间窗口与限流后在线爆破几乎不可行。同时对 HSM 整体做并发熔断,延迟或错误率超阈值时快速失败,避免请求堆积雪崩。
第三,校验窗口与漂移控制。两端时钟存在漂移须允许有限前后窗口(常见正负 1 个 30 秒窗口),但该窗口会被攻击者利用扩大爆破空间,故不宜过大,并应在日志记录匹配偏移量。
rate_key = "otp_verify:" + user_id + ":" + app_id
if not rate_limiter.allow(rate_key):
return FAIL_TOO_MANY # 触发限流,不碰 HSM
try:
with hsm_pool.connection() as hsm:
result = hsm_verify_totp(key_id, now(), user_code)
except HSMTimeout:
circuit_breaker.record_failure()
return FAIL_TEMPORARY
else:
circuit_breaker.record_success()
if result == PASS:
rate_limiter.reset(rate_key)
return result由于种子在 HSM 内、明文不外泄,攻击者无法离线穷举,每次猜测都必须经过 HSM,而 HSM 前的限流与熔断正是为在线尝试量身定制的防护。这是 HSM 托管相较"明文种子加应用内直接计算"在抗爆破上的结构性优势。
受监管关基行业,算法合规是硬指标。SM3 作为 HMAC 底层哈希时与 SHA 系列结构对等,仅压缩函数与常数表不同。后台应在每把种子元数据记录算法标识(SHA1、SHA256、SHA512、SHA224、SHA384、SM3),HSM 按标识自动选对应 HMAC 指令,调用方无需关心差异。
建议:新系统不应再用 SHA1,默认 SHA256 起步;密码合规明确的场景直接选用 SM3;同一后台可同时容纳多种算法,按应用或用户粒度配置,平滑支持存量令牌(老令牌可能只支持 SHA1)向新算法迁移——过渡期让新旧算法种子并存即可,与第五节轮换模型天然契合。

把动态口令的种子密钥托管进 HSM 是一套可复用的工程范式,而非某一产品的专属能力。若正规划或改造自家双因素认证体系,可参考以下落地步骤,不必拘泥于具体品牌:
衡量一套 OTP 后端是否真正把种子护住了,只需看一条判据:服务端进程不直接持有种子明文,种子的密码学运算下沉到 HSM 或等价的密钥保护边界内,业务侧只持有密钥句柄。即便 OTP 服务端进程被攻破、内存被 dump,攻击者也只能拿到 key_id 而拿不到可用种子,"攻破应用"与"攻破双因素认证"就此彻底解耦。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。