--- date: 2026-06-12T23:48:07+08:00 title: 不需要联网的验证码 tags: [authenticator,2FA,TOTP] categories: [note] draft: false --- 最近发现一直在用的 google authenticator 这个 app 似乎不用联网也可以生成验证码,而且这个验证码确实是能用来登录开了 2FA 的 github、chatgpt 等平台的。感觉很神奇,作为一个土生土长的拆尼斯,只知道手机短信验证码,今天闲下来研究下 google authenticator 的原理,感觉以后做项目需要登录验证身份甚至是发散到其它用途上,会是个很好的参考。 离线验证的核心秘密就在于 TOTP(Time-based One-Time Password,基于时间的一次性密码算法) 。这个算法是 HOTP(HMAC-based One-Time Password,基于消息认证码的一次性密码算法)的时间变体,它用当前时间戳替换了 HOTP 中的计数器来生成动态密码。Google Authenticator、Authy、Microsoft Authenticator 这些常见的验证器 App,底层用的都是这套逻辑。 ## TOTP 原理 TOTP 的生成公式其实很简单: > TOTP = Truncate( HMAC-SHA-1(K, T) ) 其中 K 是共享密钥,就是绑定 2FA 时扫的二维码里藏着的那串玩意,通常是一个随机的 16 位或 32 位字符串,用 Base32 编码。这串共享密钥是会被保存到服务提供商的服务器上的。T 则是时间步数,计算公式是: > T = (Current Unix Time − T0) / X X 是时间步长,默认是 30 秒(当然也可以配置成 60 秒)。举个例子,如果当前 Unix 时间戳是 1734500000,除以 30,得到 T ≈ 57816666。这个 T 会随着时间单调递增,每 30 秒就会+1。 然后呢,用 HMAC-SHA-1 算法把这个 T 和密钥 K 搅和在一起,得到一个 20 字节的哈希值。但是显然你不能让用户输入一长串十六进制数——验证码要是那么长,谁受得了。于是就有了一个叫“动态截断”(Dynamic Truncation)的手法:从这 20 字节中挑出 4 字节,再转换成 6 位十进制数,就是我们屏幕上看到的那串验证码了。 你可以把整个过程想象成两个人拿着两块同步对表的怀表:你手机里有一块“表”,服务器端也有一块一模一样的“表”,两块表的指针都随时间同步跳动,每 30 秒走到一个新的位置,显示一个不同的 6 位数。你看一眼手机上的数字,打在网页上,服务器那边算出来的数字如果和你的一模一样,就证明了两件事:第一,你手机里确实存着那把共享密钥;第二,你的时间没有偏差太离谱。 ## 关键是时间,不是网络 这就是为什么 Google Authenticator 不需要联网的核心原因——**验证是在本地完成的**,只是把计算结果和时钟参数传给了服务器去比对。手机 App 根本不需要去请求什么远程接口,它唯一需要依赖的就是设备自身的系统时间。 这里就引出了一个潜在的坑:如果你的手机时间和服务器时间差了太多,验证码就会对不上。这在跨时区旅行或者手机自动时间同步没开的情况下经常遇到。那服务器是怎么处理的呢?它不会只验证当前 30 秒窗口的验证码,一般会在前后各多验证几个窗口——比如当前时间步、前一个步长、后一个步长,都视为有效。这就给设备之间那几秒十几秒的时钟偏差留出了容错空间。极端情况下如果偏差太大,有些系统还会要求用户连续输入两枚验证码来重新计算偏移量。 顺带一提,TOTP 还有个老表兄叫 **HOTP**。HOTP 用的是计数器而不是时间,每当用户触发一次请求,计数器就+1。但 HOTP 有个毛病:如果你在服务器没同步的情况下疯狂点“生成验证码”,两边的计数器就容易“脱节”,搞得谁都登录不上去。TOTP 用时间做驱动,自动每 30 秒滚动一次,彻底避免了这个问题。 ## 安全性没有绝对的安全。 TOTP 虽然比短信验证码安全得多(不怕 SIM Swap,也不依赖运营商网络),但它也有自己的软肋。 最典型的风险是中间人攻击 。如果你被钓鱼网站骗了,在假页面上输入了当前验证码,攻击者立马用这个验证码去真的网站登录,几秒钟内就把你的账号干掉了,因为 TOTP 只在 30 秒内有效,他们根本不需要破解算法,只用抢在这 30 秒窗口内完成登录就行。你输入的验证码,恰好帮他们开了门。 另一种是暴力破解。如果服务器在验证 TOTP 的时候没有做限流,攻击者拿到你的密码后,可以对着那 6 位数(一共也就 100 万个组合)狂刷,几十分钟之内就能把代码跑出来。这个锅不能甩给 TOTP 算法本身,完全就是后端实现偷懒的锅。 最后,共享密钥 `K`(也就是二维码里藏的那串 Base32 字符串)是整个 2FA 安全性的唯一基石。一旦泄露,攻击者可以在他自己的设备上导入这个密钥,然后和你一样生成完全相同的、实时滚动的 6 位验证码。 ## TOTP 的使用场景 TOTP 的应用场景远不止登录验证。你可以把它集成到自己的项目里: - **两步验证(2FA)** :给用户的 GitHub、Gmail 账号加一层保险,即便密码泄露也能挡住大部分攻击者。 - **敏感操作确认**:比如转账、改密码、删数据这种高风险操作,要求用户再输一次验证码双重确认。 - **无网络环境下的身份验证**:在一些 IoT 设备、内部管理系统、离线终端上,TOTP 可以作为唯一的认证方案。 - **防重放攻击**:每个验证码只能用一个时间窗口,过期自动作废,天然防御重放。 ## 为什么短信验证码占据市场 为什么国内很少看到 TOTP 的身影,一般都用手机验证码呢?从用户角度来看,手机验证码几乎是一个“零门槛”的解决方案,用户无需学习“密钥”、“二维码”这些新概念,也无需额外安装任何应用。用户只需要做两件事:等短信、输验证码。更致命的是,对于普通用户而言,TOTP 一旦换手机,恢复过程非常麻烦,极易造成账号丢失。 相比短信验证码,TOTP 更像是一个纯粹的安全工具,在商业上缺乏足够的推广动力。 ## 更牛逼的 Passkey 比于我们刚才聊的 TOTP,Passkey 是一种全新的、旨在彻底取代密码的身份验证技术。它基于非对称加密,代表了无密码身份验证的未来方向。它最大的特点就是 **无需记忆和输入任何密码**。它生成的是一个 **公私钥对**:私钥安全地存储在用户设备上,永不联网传输;公钥则注册在网站服务器上。认证过程是服务端发送一个“挑战”数据,设备用私钥签名后传回,服务端再用公钥验证,以此证明身份。整个过程通常在系统的安全硬件内完成,有效防止恶意软件窃取。使用时,用户只需通过设备的生物识别或 PIN 码解锁授权即可。 安全性上来说,由于每个 Passkey 都与特定网站深度绑定,钓鱼网站无法通过伪造身份来欺骗用户,从根本上斩断了网络钓鱼的链路。 使用的便捷性上来说,用户只需通过人脸、指纹或设备 PIN 即可完成验证,无需再为不同的账号设置和记忆繁琐的密码。这也能促使更多用户启用高强度的安全保护,因为使用的门槛大大降低了。 要说缺点嘛,肯定也是有的,大部分是生态的问题:如果你的 Passkey 保存在 Apple 的 iCloud 钥匙串中,要切换到安卓或 Windows 设备可能会比较困难。一旦丢失所有绑定了 Passkey 的设备且未设置云同步,账户恢复流程可能会非常复杂。 ## 结语 在认证这一块,用户是既要安全又要便捷的,而对于服务提供商来说,还得响应国家政策,比如身份证呀、人脸呀、手机号实名呀。所以 TOTP 是一个很有意思的认证技术,但在便捷、安全以及商业化的角度来说,都还不够完美。相比手机验证码,国内之所以没把 TOTP 推起来,不是因为技术不行,而是手机验证码恰好踩中了 "便捷" 这个一般用户最关注的点,而安全性这块得往后稍稍。 因此个人感觉,这是导致 TOTP 应用场景更多出现在 2FA 而不是 1FA。 ## 参考 https://datatracker.ietf.org/doc/html/rfc6238