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 在使用,还有很多其他产品在使用。了解了其中的实现原理后,或许你自己的项目也可以考虑接入双重认证。

参考资料

相关推荐
一颗松鼠4 分钟前
JavaScript 闭包是什么?简单到看完就理解!
开发语言·前端·javascript·ecmascript
小远yyds24 分钟前
前端Web用户 token 持久化
开发语言·前端·javascript·vue.js
吕彬-前端1 小时前
使用vite+react+ts+Ant Design开发后台管理项目(五)
前端·javascript·react.js
学前端的小朱1 小时前
Redux的简介及其在React中的应用
前端·javascript·react.js·redux·store
许野平1 小时前
Rust: 利用 chrono 库实现日期和字符串互相转换
开发语言·后端·rust·字符串·转换·日期·chrono
guai_guai_guai2 小时前
uniapp
前端·javascript·vue.js·uni-app
bysking2 小时前
【前端-组件】定义行分组的表格表单实现-bysking
前端·react.js
王哲晓3 小时前
第三十章 章节练习商品列表组件封装
前端·javascript·vue.js
fg_4113 小时前
无网络安装ionic和运行
前端·npm
理想不理想v3 小时前
‌Vue 3相比Vue 2的主要改进‌?
前端·javascript·vue.js·面试