GitHub双重认证(2FA)实现原理浅析

背景

前一篇文章提出了两种解决 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 间隔的原因大概有:

  1. 发送验证码到 GitHub 服务器网络超时
  2. Authenticator 应用生成验证码在第 29s,而 GitHub 生成验证码在第 31s,恰好处于两个 30s 间隔中
  3. Authenticator 应用生成验证码时手机系统时间设置不正确

这个问题已经超出了 TOTP 算法的范围,不同的服务商对此有不同的解决方案。

RFC 文档中建议的解决方案是:

对于第一、二种情况,一定是服务器收到验证码时间晚于生成验证码时间,依次从当前时间间隔往前推移几个间隔,只要其中一个间隔的验证码匹配上就认为验证通过。

对于第三种情况,修改手机系统时间的自由度太大了,双重认证机制已经无法控制,这个场景验证码验证肯定失败。

现在手机都会自动同步网络时间,大概率不会出现;另外,猜测 Authenticator 应用也会对比系统时间和网络时间,不一致时会给出提示。

3.如何保证安全

秘钥泄露

主要存在下面几种可能:

  • 在扫描 GitHub 给的绑定二维码时,如果被别人拍照、截图、数据劫持,二维码中的秘钥就会泄露
  • Authenticator 应用存储的秘钥被泄露,手机丢失、Authenticator 服务器被攻击等

拿到了秘钥之后按照 TOTP 算法生成验证码肯定是有效的,不过前提是对方知道了你的 GitHub 的账号和密码。

所以,选择一款可靠的 Authenticator 应用很关键,建议使用大厂的应用或者开源应用,甚至自己开发一款 TOTP 验证码生成应用。

破解秘钥

有两种破解可能:

  • 尝试验证码

    验证码只有 6 位数字长度、总共有 2^6 种可能,逐个尝试(暴力破解)

    破解的时间有限,验证码每隔一个时间间隔(30s)就会更新,同时 GitHub 有网关安全识别,频繁的请求会被视为网络攻击直接过滤掉,几乎不可能完成

  • 推测秘钥

    收集验证成功的 TOTP 验证码,根据 TOTP 算法尝试出原始秘钥

    这种方式要尝试所有可能的秘钥值、同时要知道成功的 TOTP 验证码生成的时间戳,几乎不可能完成

总结

从分析的过程可以看出 GitHub 的这种双重认证非常安全,就算账号密码泄露了,只要手机还在,你的账号也还是安全的。

文中介绍的基于TOTP 算法的双重认证不仅 GitHub 在使用,还有很多其他产品在使用。了解了其中的实现原理后,或许你自己的项目也可以考虑接入双重认证。

参考资料

相关推荐
梦境之冢9 分钟前
axios 常见的content-type、responseType有哪些?
前端·javascript·http
racerun12 分钟前
vue VueResource & axios
前端·javascript·vue.js
m0_5485147729 分钟前
前端Pako.js 压缩解压库 与 Java 的 zlib 压缩与解压 的互通实现
java·前端·javascript
AndrewPerfect29 分钟前
xss csrf怎么预防?
前端·xss·csrf
Calm55032 分钟前
Vue3:uv-upload图片上传
前端·vue.js
浮游本尊37 分钟前
Nginx配置:如何在一个域名下运行两个网站
前端·javascript
m0_7482398337 分钟前
前端bug调试
前端·bug
m0_7482329239 分钟前
[项目][boost搜索引擎#4] cpp-httplib使用 log.hpp 前端 测试及总结
前端·搜索引擎
新中地GIS开发老师1 小时前
《Vue进阶教程》(12)ref的实现详细教程
前端·javascript·vue.js·arcgis·前端框架·地理信息科学·地信
m0_748249541 小时前
前端:base64的作用
前端