一文带你看懂 Two-factor authentication(2FA) 的密码学原理

熟悉 GitHub 的小伙伴都知道,前段时间, GitHub 宣布,到 2023 年底,所有用户都必须要启用双因素身份验证 (2FA) ,不能只使用密码进行高风险操作了。

开启2FA后,除了输入密码外,还需要通过一次性密码 (OTP) 等方式做二次身份验证,才能成功登录账号以及进行相关操作。这样做能有效避免因为密码泄露,而导致账号被盗用。

什么是 2FA 它如何工作,以及它的原理是什么

2FA 双因素认证

2FA 中文名称:双因素认证。一般来说,我们在大多数场景下都是使用帐号和密码来进行身份验证,而 2FA 就是除了建立帐号密码之外的第二个关卡。就算账号和密码不小心外泄了,也不至于马上被盗用(但还是要养成定期修改密码的好习惯)。而 2FA 又可以分为硬件版本和软件版本两种形式。

硬件型 2FA

像在 App Store 上购买 App 时需要指纹,或者网银转账时需要用到的 U 盾,只有通过那只随身携带在身上的硬件才能通过验证,这些都属于硬件型的 2FA 。

软件型 2FA

而今天要讲的 OTP(One Time Password) 就是软件型的 2FA 。像大部分 APP 在风险操作时都会要求你输入验证码、或者当你要从新电脑登陆时,也需要到手机查看验证码,当然这些都是免费的。

因为这些验证码每次都不一样,且会在短时间内过期失效(一般的都是 10-30 分钟),所以我们把它称为一次性的密码,每次都不一样自然安全性也比较高。

所以像这样用手机收取验证码有什么不好么?

虽然短信验证码每次都是随机的,但其实短信在传输的过程中非常不安全。撇开短信不谈,就算验证码是用 App 、电子邮件这些来传输,只要每次你登陆时,服务商试图把验证码「传」给你时,那就有遭窃的可能。

那如果我登陆时对方不把验证码传给我,那我要怎么拿到验证码?

虽然听起来很神奇,但确实是可以靠密码学做到的,那就是今天要介绍的主角 TOTP

基于时间的一次性密码

TOTP 的中文全称是基于时间的一次性密码,看名字就知道他也是 OTP 的一种,只不过是基于当前的时间来产生的 OTP,因此天生就具有随机性。因为直接看公式太硬了,本文就以 Github 2FA 的例子来讲解他的运行原理。

如果你的 Github 还没有实现 2FA 的话,可以跟着官方文件先设置好,然后看看下面的讲解你就会知道每个步骤都在做什么。

官方英文原版文档:

官方中文原版文档:

第 1 步 — 服务器生成带有密钥的 QRCode

通常要实现 TOTP 2FA 的第一步,会让你扫描一个很大的 QRCode ,而这个 QRCode 中就带有服务器想要传给你的秘钥。

如果把上图的 QRCode 解码出来会得到 otpauth://totp/GitHub:LarryLuTW?secret=X5CTBOMEYE3TXIIS 这个字符串,这串 URI 是由 Github 发布的,而后面的 secret=X5CTBOMEYE3TXIIS 就是 Github 想要传给你的秘钥。

因此当你用手机上的 Authy App 去扫描时(我是用 Authy ,还有很多其他 App 可以选择), App 就会把这个字符串秘密存储起来,以后就用这个秘密生成 TOTP 。

第 2 步 — 验证双方生成相同的 TOTP

有了这串 secret 后 App 要怎么生出下面六码 OTP 呢?答案就是把 secret 跟现在时间去进行杂凑,然后取最后的六码作为 OTP。

比如我现在时间换算成UNIX时间戳是 1692604605 (从 1970 年 1 月 1 号,已经过了这么多秒),然后密钥是 X5CTBOMEYE3TXIIS

因为 App 希望每 30 秒产生出不同的 OTP ,所以他把这 timestamp/30 两个 X5CTBOMEYE3TXIIS 东西丢进去 HMAC 做杂凑,杂凑的结果转十进位取最后六码,就可以得到这 30 秒的 OTP 是 017201 了。

而 30 秒后因为 timestamp/30 的结果又不一样了,所以又会生出完全不同的 OTP (只需输入值稍有不同, hash 的结果完全不一样).

有了这六位 OTP 后,你就在三十秒内到网站输入 017201 (三十秒后又是截然不同的 OTP)。如果 Github 也算出一模一样的结果,那他就知道你已经知道并拿到了正确的密钥。此后你跟 Github 就算是约定好秘密了, Github 也把这个秘密存在自家的资料库里面。

第 3 步 — 登入时验证 TOTP

设置好 2FA 后,以后你要登入时, Github 就会请你输入 App 生出来的 OTP。

因为双方已经沟通好了 X5CTBOMEYE3TXIIS 这个密钥,接下来你用不着收短信,甚至可以不需要网路,只要把 App 打开它就会帮你算出新的 OTP 。

Github 收到你输入的 OTP 后,如果他那边也出算一样的结果,那就代表你却是本人。如此一来,就实现了每次登入时 Github 不用传验证码给你,但你可以知道验证码

安全性分析

总归来说,使用 TOTP 来进行 2FA 的第一步就是通过 QRCode Server 让跟 Client(App) 约定好一个秘密,之后就都用那个秘密跟接下来的时间来进行 Hash 。我们就按这个流程来评估每个步骤的安全性。

秘密的传输安全吗?

一开始要设定 TOTP 2FA 的时候 Github 会生成一个 QRCode 让你扫一下,里面就贴着他要给你的秘钥。如果这个秘钥在传输的过程中一不小心被窃客偷走了,那他日后就可以用这个秘密生出登入所需的 OTP。

因此在设置 TOTP 的时候一定要注意检查网站是否有 HTTPS 类似安全性的问题,或者看看有没有人在你背后扫你的 QRCode 。如果都没有,那恭喜你!世界上应该只有你跟 Github 知道这个秘密,因此非常安全。

存放秘密的地方安全吗?

App 秘钥帮你存起来后,接下来要考虑的就是 App 安全吗。比如说他不会偷偷把你的秘密去拿卖钱,或者他有没有支持云端备份的功能,一不小心就会外泄。

像我自己用的 Authy 工具支持云端备份,不怕密钥不见,也不太担心手机突然爆掉,所以就没有太大问题。大家可以根据自己的实际情况去评估。

OTP 会不会被猜到

如果黑客拿不到密钥,那他能不能暴力破解猜到六位的 OTP 呢?简单试算一下,因为 OTP 有六位数字,所以有百万种可能性,而且每 30 秒就换一个全新的毫无规律可言的密钥。如果你想在30秒内猜出来,那一秒就需要进行超过三万次登陆。

但实际上只要连续失败几次,Github 就会暂时把你拦下来(其他网站也是),而你下次要试的时候验证码又不一样了,所以完全不可能暴力破解破解

因此总的来说,除非你的秘钥一开始就被偷走,否则使用 TOTP 2FA 绝对是足够安全的。

总结

这次介绍的 TOTP 2FA 除了原理简单、安全性高之外,我个人觉得最方便的就是 TOTP 是在我手机上产生的、打开 App 就可以看到了。

至于用哪个 APP ,我个人觉得 Authy ,不过大家也可以看看 The Best 2FA Apps 2022 评估一下,选一款最适合自己的密钥软件。