Authn.tech
首页
  • SAML 2.0
  • OAuth 2.0
  • OIDC
  • WebAuthn / Passkey
  • MFA / TOTP
  • 工具总览
  • JWT 解析与验签
  • JWT 签名生成
  • JWK / 密钥生成
  • JWK / JWKS → PEM
  • PKCE 生成器
  • OIDC Discovery
  • TOTP 工具
  • WebAuthn 演示
  • SAML 编解码
  • SAML Metadata 解析
  • SAML Response 解析
  • X.509 证书解析
  • Base64URL
  • 端点与说明
  • OIDC 登录演示
GitHub
首页
  • SAML 2.0
  • OAuth 2.0
  • OIDC
  • WebAuthn / Passkey
  • MFA / TOTP
  • 工具总览
  • JWT 解析与验签
  • JWT 签名生成
  • JWK / 密钥生成
  • JWK / JWKS → PEM
  • PKCE 生成器
  • OIDC Discovery
  • TOTP 工具
  • WebAuthn 演示
  • SAML 编解码
  • SAML Metadata 解析
  • SAML Response 解析
  • X.509 证书解析
  • Base64URL
  • 端点与说明
  • OIDC 登录演示
GitHub
  • MFA / 一次性密码

    • MFA 与一次性密码概述
    • HOTP / TOTP 算法详解
    • otpauth URI 与参数参考

otpauth URI 与参数参考

本页是 OTP 分发与参数的速查手册:otpauth:// URI 格式、各参数默认值、二维码约定、验证器兼容性、Base32 编码规则,以及"码不对"的排错速查表。算法原理见 HOTP / TOTP 算法详解。

otpauth:// URI 格式

验证器 App(Google Authenticator 等)通过一个 otpauth:// URI 导入共享密钥。这是事实标准(由 Google Authenticator 定义并被广泛采用)。二维码里承载的就是这段 URI。

otpauth://TYPE/LABEL?secret=...&issuer=...&algorithm=...&digits=...&period=...&counter=...

各组成部分:

  • TYPE:totp 或 hotp,决定移动因子类型。
  • LABEL:标识账户的显示名,格式 issuer:account(如 Example:alice@example.com)。冒号前是发行方,冒号后是账户名。LABEL 需 URL 编码(冒号可写作 %3A,空格写作 %20)。
  • query 参数:密钥及可选算法参数,见下表。

完整示例:

otpauth://totp/Example:alice@example.com?secret=JBSWY3DPEHPK3PXP&issuer=Example&algorithm=SHA1&digits=6&period=30

HOTP 示例(注意用 counter 而非 period):

otpauth://hotp/Example:alice@example.com?secret=JBSWY3DPEHPK3PXP&issuer=Example&counter=0

参数表与默认值

参数适用类型是否必需允许值默认值说明
secrettotp/hotp必需Base32 字符串无共享密钥,RFC 4648 Base32,大写、去填充
issuertotp/hotp强烈推荐字符串无发行方,建议同时出现在 LABEL 前缀与此参数,且两者一致
algorithmtotp/hotp可选SHA1 / SHA256 / SHA512SHA1HMAC 哈希算法
digitstotp/hotp可选6 / 86生成码位数
periodtotp可选正整数(秒)30时间步长,仅 TOTP 使用
counterhotpHOTP 必需非负整数无初始计数器,仅 HOTP 使用

issuer 的两处一致

最佳实践是 issuer 既作为 LABEL 前缀(Example:alice@...)又作为独立 issuer=Example 参数,且二者字符串一致。这样即使某个 App 只读其中一处,也能正确显示发行方,避免同名账户混淆。

二维码承载约定

  • 把完整 otpauth:// URI 作为二维码内容(纯文本)编码,用户用验证器 App 扫码即导入。
  • 纠错级别通常选 M(中);URI 较长且含长密钥时可用 L 以缩小图形。
  • 二维码只在绑定阶段展示,不应持久化或缓存,展示后应从内存清除;避免出现在日志、截图、CDN 缓存中。
  • 应同时提供密钥文本(Base32)供无法扫码时手动录入。

主流验证器 App 兼容性

App非默认 algorithm(SHA256/512)digits=8自定义 period备注
Google Authenticator历史版本常忽略,按 SHA1 处理支持不稳定支持不稳定兼容性最保守,建议只用默认
Microsoft Authenticator部分支持部分支持部分支持以默认组合最稳妥
Authy支持较好支持支持参数容忍度较高
FreeOTP / andOTP支持较好支持支持开源实现,参数完整
1Password支持较好支持支持—

互操作首要原则

若无强安全需求,坚持 SHA1 / 6 位 / 30 秒 默认组合。很多 App(尤其早期 Google Authenticator)会静默忽略 algorithm、digits、period 参数并一律按默认计算——此时它生成的码与你服务端按 SHA256/8 位计算的码永远对不上,且没有任何报错,极难排查。

Base32 编码说明(RFC 4648)

secret 使用 Base32 编码,约定如下:

  • 字母表:A–Z + 2–7,共 32 个字符(不含 0、1、8、9,避免与字母混淆)。
  • 大写:验证器展示与录入均用大写;解码时应大小写不敏感处理。
  • 无填充:otpauth 场景去掉尾部 = 填充。
  • 编码单位:每 5 bit 映射一个字符,故 8 字符编码 5 字节。
  • 建议长度:密钥至少 160 bit(20 字节)= 32 个 Base32 字符。
字节 (5)  : 0x48 0x65 0x6C 0x6C 0x6F
Base32    : JBSWY3DP

参数默认值一览

项默认
TYPEtotp
algorithmSHA1
digits6
period30 秒
T0(计时起点)0(Unix 纪元)
验证容错窗口±1 步(约 ±30 秒)
secret 编码Base32,大写,无填充
secret 长度≥160 bit(20 字节)

排错速查表:码总是不对

按出现频率从高到低排查:

症状 / 原因判断方法解决
客户端时钟偏移最常见。用户设备时间不准,或时区/自动同步关闭开启设备"自动设置时间";服务端可临时放宽 window 定位偏差量
服务端时钟偏移服务端未同步 NTP部署 NTP;用标准测试向量自检
密钥编码错误存/取密钥时把 Base32 当原始字节,或反之明确密钥在数据库、URI、HMAC 输入三处的表示;HMAC 输入必须是解码后的原始字节
Base32 填充/大小写secret 含 = 或小写导致 App 解析异常去填充、转大写后再写入 URI
算法/位数不符服务端用 SHA256 或 8 位,但 App 忽略参数按 SHA1/6 位算统一为默认 SHA1/6 位;或改用参数容忍度高的 App
period 不一致服务端 period 与 URI 不符,或 App 忽略了自定义 period统一用 30 秒
计数器失步(HOTP)用户误触令牌使 counter 超前服务端设前向查找窗口,命中后同步 counter
未做容错窗口只比对当前时间片,跨窗口边界即失败验证时检查 ±1 步
重放拦截误判防重放逻辑把合法首次使用当重放拒绝检查"上次使用时间片"记录逻辑
counter/period 用错字段HOTP 误填 period 或 TOTP 误填 counter按 TYPE 使用对应参数

自检建议

用 RFC 4226 附录 D 的 HOTP 测试向量(密钥 "12345678901234567890",计数器 0 → 755224)和 RFC 6238 附录 B 的 TOTP 测试向量校验服务端实现,可快速定位是算法实现问题还是配置/时钟问题。

返回:MFA 概述 · 算法详解

最近更新: 2026/7/3 13:26
贡献者: linux, Claude Fable 5
Prev
HOTP / TOTP 算法详解