背景
前一篇文章提出了两种解决 GitHub 国内手机号无法双重认证(2FA)问题的方法,其中使用 TOTP 验证码的方式令我非常好奇,于是我进一步分析了其实现原理。
在 GitHub上开启双重认证,有使用短信验证码、GitHub APP和TOTP 验证码三种方式。
本文主要介绍 TOTP 验证码的实现原理。
说明一下概念 Two-factor authentication,缩写为 2FA,中文翻译为:
双重认证
、双重验证
或者双因素认证
都是可以的,下文就统一称其为:双重认证
。
认证步骤
先回顾一下开启 GitHub 双重认证的步骤
第一步,生成绑定二维码
第二步,TOTP 应用/插件扫码绑定
这里示意的是插件扫码
第三步,登录账号后输入 TOTP 验证码
账号密码登录成功后,继续输入并验证 TOTP 验证码
基本原理
验证码生成器和 GitHub 各自使用相同的秘钥 对当前时间 采用相同的 TOTP 算法 生成的一组 6 位数字,对比双方生成的结果,相同则验证通过。
这种验证方法的高明之处在于:
- 不是直接输入秘钥判断双方拥有的秘钥是否一致(账号密码机制),有效防止秘钥泄露。
- 采用离线的 Authenticator 应用/插件生成,避免传输过程被第三方拦截(短信验证码)
下面详细介绍这个过程。
传递秘钥
点击启用 2FA 双重认证时,GitHub 会给出一个绑定二维码
二维码解码出来的内容为:
txt
otpauth://totp/GitHub:JohnieXu?secret=A8XTDOHEKE6TIHHQ&issuer=GitHub
当使用 Authenticator 应用扫码时,会解析这里面的参数存储到手机上。
其中,secret=
后面的部分就是最关键的秘钥。
有很多 APP 都支持 GitHub 生成的这种二维码,例如:1Password、Authy 等,可以看出这种双重认证方式比较开放
确认绑定
这一步是告诉 GitHub 我已经保存了刚刚这个秘钥,下次我们就约定使用这个秘钥进行双重认证。
具体过程为:
-
Authenticator 应用和 GitHub 服务器各自按照 TOTP 算法生成验证码进行对比,相同则验证通过。
-
通过后,GitHub 就会将这个秘钥与你的账号关联,下次双重认证时使用这个秘钥。
TOTP 算法生成验证码的过程见下文。
生成验证码
Authenticator 应用把秘钥和当前时间一起进行哈希运算,生成六码数字作为验证码。
所以,生成的验证码随着当前时间不断变化,并且无法根据验证码反推出秘钥。
但是,在 Authenticator 应用中可以看到验证码有 30s 的有效期,并且在 30s 内生成的验证码是一样的。
红框部分为验证码剩余有效期
这是因为:用来哈希运算的时间戳还进行了 30s 分段,相当于将当前时间戳除以 30 再取整再作为哈希运算的内容。
验证是否有效
在 GitHub 页面输入验证码时,验证码会被发送到 GitHub 服务器。
GitHub 用相同的算法以服务器当前时间来生成 6 位验证码,与接收的验证码对比,相同则验证通过。
这个验证过程会存在一个问题:
如果 Authenticator 验证码生成的时间,与 GitHub 生成的验证码不在一个 30s 间隔内,那么会验证失败。
大家可以思考下如何解决?介绍完 TOTP 原理后再来解答。
TOTP原理
TOTP 全称是基于时间的一次性验证码,是 OTP(一次性验证码)的一种,已经是国际标准规范:RFC-6238。
验证码生成过程
用公式表示如下:
使用当前时间戳 TC 与秘钥 SecrectKey 进行哈希运算
txt
TOTP = HASH(SecretKey, TC)
HASH:哈希函数,一般是 SHA-1
TC 是从起始时间 T0 到当前时间的总秒数,再除以时间间隔 TS 并取整
txt
TC = floor((unixtime(now) − unixtime(T0)) / TS)
floor: 表示取整 unitxtime:unix 格式的时间戳 now:当前时间 T0:约定的起始时间 TS:验证码有效间隔时间,一般是:30s、60s
一般约定从 1970 年 1 月 1 日
起,T0 就是 0
txt
TC = floor(unixtime(now) / 30)
生成公式汇总如下:
txt
TOTP = HASH(SecretKey, floor(unixtime(now) / 30))
TOTP 的基本原理就是这样,更多细节可以查看这份 RFC-6238。
问题解答
下面解答一些相关疑问
1.为什么需要时间间隔
如果不定义时间间隔那么每一秒的验证码都是不同的,在服务器校验验证码时永远不可能通过,因为双方生成验证码时一定存在时差。
2.不在同一间隔内如何处理
导致不在一个 30s 间隔的原因大概有:
- 发送验证码到 GitHub 服务器网络超时
- Authenticator 应用生成验证码在第 29s,而 GitHub 生成验证码在第 31s,恰好处于两个 30s 间隔中
- Authenticator 应用生成验证码时手机系统时间设置不正确
这个问题已经超出了 TOTP 算法的范围,不同的服务商对此有不同的解决方案。
RFC 文档中建议的解决方案是:
对于第一、二种情况,一定是服务器收到验证码时间晚于生成验证码时间,依次从当前时间间隔往前推移几个间隔,只要其中一个间隔的验证码匹配上就认为验证通过。
对于第三种情况,修改手机系统时间的自由度太大了,双重认证机制已经无法控制,这个场景验证码验证肯定失败。
现在手机都会自动同步网络时间,大概率不会出现;另外,猜测 Authenticator 应用也会对比系统时间和网络时间,不一致时会给出提示。
3.如何保证安全
秘钥泄露
主要存在下面几种可能:
- 在扫描 GitHub 给的绑定二维码时,如果被别人拍照、截图、数据劫持,二维码中的秘钥就会泄露
- Authenticator 应用存储的秘钥被泄露,手机丢失、Authenticator 服务器被攻击等
拿到了秘钥之后按照 TOTP 算法生成验证码肯定是有效的,不过前提是对方知道了你的 GitHub 的账号和密码。
所以,选择一款可靠的 Authenticator 应用很关键,建议使用大厂的应用或者开源应用,甚至自己开发一款 TOTP 验证码生成应用。
破解秘钥
有两种破解可能:
-
尝试验证码
验证码只有 6 位数字长度、总共有
2^6
种可能,逐个尝试(暴力破解)破解的时间有限,验证码每隔一个时间间隔(30s)就会更新,同时 GitHub 有网关安全识别,频繁的请求会被视为网络攻击直接过滤掉,几乎不可能完成
-
推测秘钥
收集验证成功的 TOTP 验证码,根据 TOTP 算法尝试出原始秘钥
这种方式要尝试所有可能的秘钥值、同时要知道成功的 TOTP 验证码生成的时间戳,几乎不可能完成
总结
从分析的过程可以看出 GitHub 的这种双重认证非常安全,就算账号密码泄露了,只要手机还在,你的账号也还是安全的。
文中介绍的基于TOTP 算法的双重认证不仅 GitHub 在使用,还有很多其他产品在使用。了解了其中的实现原理后,或许你自己的项目也可以考虑接入双重认证。