2FA双因子验证技术实现原理

发布于:2024-12-20 ⋅ 阅读:(14) ⋅ 点赞:(0)

一、背景

        随着关注数据安全的意识逐步提升,很多站点都开始强制用户不止使用用户名+密码的形式进行登录,还会逐步引导用户开启2FA多因子验证。 Github就已经强制用户使用2FA多因子验证进行账号的登录,现在阿里云、腾讯云等也逐步往这个方向发展。

        那什么是2FA多因子验证呢?

        2FA,2 Factor Authentication,双因子验证/双因素验证,是一种安全密码验证方式。区别于传统的密码验证,由于传统的密码验证是由一组静态信息组成,如:字符、图像、手势等,很容易被获取,相对不安全。2FA是基于时间、历史长度、实物(信用卡、SMS手机、令牌、指纹)等自然变量结合一定的加密算法组合出一组动态密码,一般每60秒刷新一次。不容易被获取和破解,相对安全。

        说简单点,我们传统使用用户名+密码的形式进行登录不够安全,如果你的密码泄露了,那么别人也能登录你的账号进行非法操作。  但是这种2FA认证呢,会在验证账号+密码通过之后,还会进行一次认证。  这一次认证,会给你一个30s或者60s之内的一个token或者验证码,这个验证码是动态的,必须在30s或者60s之内使用,否则立马就失效。并且这些token都是一次性的。

        那假设你泄露密码的之后,由于还有一层2FA的验证方式,那么由于黑客无法拿到2FA动态一次性密码,从而无法进行最后的验证操作,导致登录步骤无法继续得以进行,保证了账号的安全性。

       那有人就问了,我这种完全可以通过发送短信验证码或者邮箱验证码的方式实现二次认证啊。2FA认证和短信验证码又有什么区别呢?  按照我的理解两者优缺点如下:

        【2FA验证方式】

                优点:

                        1、动态令牌,每隔30s或者60s生成动态令牌,在不泄露秘钥的情况下,黑客依靠穷举的方法,无法短时间内进行破解

                        2、站点无须增加短信开销成本,因为动态令牌是在客户端产生的,无需向用户发送

                        3、用户无需预留手机号等信息,用户注册也不一定需要手机号,降低用户填写个人信息门槛负担,用户无需担心手机号等信息泄露(但是,国内都要实行手机号、实名制等,这个是政策问题,而非技术或者产品问题)

                缺点:

                        1、刚开始的使用门槛相对短信复杂,因为需要下载如Google authenticator等支持OTP认证协议动态生成口令的APP或者工具

        【短信验证方式】

                优点:

                        1、简单易用,用户使用门槛低。很简单,收到短信验证码填写即可

                缺点:

                        1、站点需要花费发送短信的成本,需要向短信运营商支付费用,如果用户量很大、短信量也会很大,成本是需要考虑的

二、使用流程

        使用Github登录进行举例

1、站点设置开启2FA多因子验证功能

2、使用站点提供的APP或者下载Google authenticator扫描2FA二维码

        我会在手机上下载这个APP:  Google authenticator

        其实还有很多类似这APP, 就是一个识别OTP协议、并且生成动态一次性口令的客户端工具。 

3、每次要填写2FA验证码,查看动态口令,输入口令

        进入APP,扫描站点提供的2FA二维码,这个二维码其实包含了你的一个秘钥,所以这个二维码不能泄露出去,泄露出去和你的账号密码是一个道理。 

        客户端会根据你的秘钥和时间戳,按照OTP协议的标准生成6位数字的一次性令牌,有效时间是30s.

        需要输入2FA口令的时候,在有效期时间内输入生成的口令/验证码即可通过认证。  二维码扫描只是一种方便的方式,二维码里面包含了秘钥的信息,所以没有二维码的时候,也支持手填秘钥的方式。

三、实现原理

        2FA的一次性密码验证方式,其实原理很简单。  服务端会先给客户端一个秘钥key(不能泄露), 客户端和服务端会使用同一套秘钥key, 这个key一般是会通过base32进行编码。

        然后,客户端和服务端根据相同的密钥,以及时间戳(时间戳有时间范围,例如30s, 防止客户端、服务端时间偏差,导致无法进行比较验证)。  2FA生成一次性密码和时间关系很大,时间差距太大,会导致双方产生的令牌/一次性密码无法比较通过。

        客户端工具,无论是哪个APP, 其实本质很简单,就是拿着你的秘钥key(可以通过二维码扫描出来,也可以进行手动填写的方式),  然后根据时间戳+秘钥的方式,按照OTP密码生成标准生成令牌。

        客户端传递令牌到后端,后端也使用同一套算法、秘钥、时间戳等生成令牌,两者令牌如果对比成功,那么验证通过,否则验证失败。

        流程图如下:        

四、代码模拟2FA认证过程

以下代码使用python进行实例,其它编程语言也是一样的,都会有一些开源的客户端SDK.

1、服务端生成约定的2FA密钥,客户端需要进行保存,不能泄漏

import pyotp
import base64

#实际秘钥信息
raw_secret_key = "rocky"

#需要进行base32编码,最后给用户的是base32编码之后的字符串
secret_key = base64.b32encode(raw_secret_key.encode(encoding="utf-8"))

print(f"秘钥信息: {secret_key.decode(encoding='utf-8')}")

最简单的就是直接输出密钥,运行如下:

这个密钥就是客户需要记录下来,且不能泄漏的。  为了降低用户的使用门槛和增加用户体验,我们还可以生成二维码的方式。

记住这个密钥信息: OJXWG23Z

2、otpauth协议内容

        OTPAuth 是一种用于配置一次性密码(One-Time Password, OTP)应用的协议。它允许通过扫描二维码或手动输入URL的方式配置基于时间的一次性密码(TOTP)或基于计数器的一次性密码(HOTP)。这种配置信息通常包含了生成OTP所需的全部参数,如密钥、算法类型、数字长度等。

        一个典型的 OTPAuth URL 看起来像这样:

otpauth://TYPE/LABEL?PARAMETERS

        TYPE 表示 OTP 的类型,可以是 totp 或 hotp。
        LABEL 通常是用户可见的名字或者标识符,用来识别这个 OTP 配置。它一般格式化为         issuer:accountName,其中 issuer 是服务提供商的名字,而 accountName 是用户的账户名。
        PARAMETERS 包含了一系列键值对形式的参数,用以提供生成 OTP 所需的信息。这些参数可能包括但不限于:
        secret: 这个密钥是[必须],用于生成 OTP。它是 Base32 编码的字符串。
        issuer: 发行者名称[可选],这在 LABEL 中已经提供了,但是在这里重复是为了确保兼容性和正确显示。
        algorithm: 指定哈希算法[可选],默认是 SHA1。其他支持的算法包括 SHA256 和 SHA512。
        digits: 指定 OTP 的位数[可选],默认是6位,但也可以设置为8位。
        period: 对于 TOTP 类型来说[可选],这是指更新 OTP 的时间间隔,默认是30秒。
        counter: 对于 HOTP 类型来说[可选],这是当前的计数值。每使用一次 OTP,这个值会递增。

        例如,一个完整的 TOTP URL 可能看起来像这样

otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example&algorithm=SHA1&digits=6&period=30

        1、首先是otpauth协议名称,类比http协议的格式, 协议名称://

        2、totp是固定的

        3、Example是服务的名称

        4、alice@google.com是账号名

        5、secret=后面就是秘钥

        6、issuer颁发者信息, 以及后面是各个参数信息。和HTTP的GET参数类似

        Google authenticator等工具,扫描二维的原理就是根据otpauth协议的标准解析二维码的URL信息,拿到密钥信息用来生成一次性令牌.

3、根据生成的密钥,构造otpauth URL地址,生成二维码

otpauth://totp/blog:rocky@google.com?secret=OJXWG23Z&issuer=rocky

  可以使用python或者客户端生成二维码图片,我为了方便直接chrome插件【草料二维码】生成即可.   

4、使用客户端扫描二维码,2FA生成动态令牌

        可以看到我扫描了二维码之后,新增了一个账号信息,可以实时生成动态口令, 并且口令存在30s的过期时间,过期会自动生成新的令牌:

5、模拟服务端解码过程

import pyotp

#约定的密钥
secret_key = "OJXWG23Z"

totp = pyotp.TOTP(secret_key)

#用户输入的动态口令
user_input_code = input("请输入您的TOTP码进行验证: ")
if totp.verify(user_input_code):
    print("TOTP码验证成功")
else:
    print("TOTP码验证失败")

五、总结

        2FA确实能增加我们的安全性,但是用起来门槛确实比较高,对于很多互联网小白望而却步。所以这种场景在数据不是很敏感的情况下,很少有产品强行推这种验证方式。 宁愿使用短信的方式,能降低用户的使用门槛以及带来良好的产品体验。

        但是随着数据安全的重视,预计以后针对银行、金融、以及如云计算服务公司等等,也会逐渐推广。