Skip to main content

双因素认证(2FA) & TOTP 2FA 简述

· 约24分钟
Proca

为什么要使用 2FA?

随着互联网的蓬勃发展,我们的网络平台账号数量也日益增长,比如QQ、微信、淘宝、京东、GitHub、BiliBili……这些平台都需要我们设置密码,以保护我们的个人隐私和财产。

但是,仅依靠密码的保护并不绝对安全。密码很容易被黑客通过各种方式破解!一旦我们的密码泄露,我们的账号就很容易被他人盗用,造成或大或小的损失。而 2FA 正在一定程度上提高着我们账号的安全性,防止他人盗用或冒用。

那么,什么是 2FA?

什么是 2FA?

我们可以用保险箱来理解双重认证(Two-Factor Authentication, 2FA)的作用:

假设你家里有个保险柜,里面放着些贵重玩意。要是保险柜就一把钥匙,被人拿去钥匙就糟了,人家照样能把保险柜打开,把你的宝贝全都取走。 这和账号就一个密码一个道理,太不安全了!于是,为了防人盗取,你在保险柜上又加装了密码锁,得密码对了才行。 这就跟账号开启双因素验证一个意思,非得两个条件都满足才能登进去。

严谨地说,2FA 是一种双重认证机制。在登录时,它要求我们提供两种不同形式的识别证明,以证明我们是该账号的合法拥有者。这两种识别证明通常细分为以下四种要素:

要素描述
知识要素只有我们自己知道的信息,比如密码、PIN码或安全问题答案。
占有要素只有我们自己拥有的物品,比如手机、银行卡或硬件令牌。
生物要素我们独有的生物特征,比如指纹、面部或虹膜信息。
行为要素IP 地址范围或我们通常用于登录到应用程序的位置数据。

举个例子,密码属于知识要素,而手机属于占有要素。如果你的淘宝启用了 2FA,那么在登录账号时,当输入完密码之后,你还需要输入手机收到的验证码。这样你的账号就安全多了————吗?

事实上,通过手机验证码实现的 2FA,虽然有一些显而易见的优点,比如简单易用、成本低廉、易于部署和使用,但是它也并没有那么地安全和完美。正因如此,围绕以上提到的要素,更安全的 2FA 方案应运而生,比如行为2FA、硬件令牌、生物识别以及TOTP等。

接下来,我们将分别介绍它们。

短信 2FA

假设你要在淘宝开设一个网店,并且你决定启用短信认证来保护自己的账号安全。这种 2FA 的原理是,在用户输入用户名和密码登录后,平台会向用户手机发送一条包含6位数字验证码的短信,用户必须正确输入验证码才能完成登录。这样就增加了登录账户的难度,可以在一定程度上防止账户被盗。

但是,你也将面临一些安全问题。对于一些平台来说,在它们直接向用户手机发送短信的过程中,可能并非实现端对端加密,也就是说,一些心术不正的黑客可能会通过中间人攻击手段,截获短信验证码,从而盗取你的账号! 更严重的是,它们也有可能恶意篡改这条短信,让你收到含有钓鱼链接的短信 —— 一旦点击,账户信息就有泄露的风险。此外,你的手机号也可能被他人非法获取并转卖,这对于你的隐私来说可不是小问题!

为了杜绝这些问题,你决定只用这号码注册少数几个重要应用,不乱给网站留手机号。同时你注意辨别验证码短信的来源,不轻信陌生链接。另外,你还使用独立的电子邮箱作为重要账户的备用验证方式。 采取这些预防措施,可以帮助你尽可能减少短信2FA对账户的影响。但是,如果平台支持的话,下面这些方式在安全性以及隐私性上会更优秀!

生态系统 2FA

生态系统 2FA 是建立在同一公司旗下产品之间的验证机制。用户可以使用已经登录的设备对新设备的登录请求进行确认,而无需再次输入验证码。

举个我们都很熟悉的栗子:

你在自己的手机上开启了微信的生态系统2FA。之后当你想在新设备上登录微信时,只需在自己的手机上确认验证请求,就可以顺利登录,不需要输入任何验证码!

生态系统2FA可以极大地简化验证流程,并且这种验证方式不依托于第三方的通信服务,而是完全加密的!这可比短信2FA安全多了!

但是,如果你的所有已验证设备(如手机)丢失或者恢复了出厂设置,你将没法像以前那样登录微信了,只能通过更复杂繁琐的流程来验证“你是你自己”!

行为 2FA

行为2FA利用用户的行为模式如登录地点、设备、操作习惯等数据建立起用户画像,当检测到与用户习惯行为不符时,会要求进行额外验证。

举个例子,如果你每天都在北京登录微信,突然有一天在上海登录,平台会发现地点异常,这时就会要求你进行人脸识别或输入验证码,来验证你的身份。

行为2FA可以持续适应用户的行为模式,当检测到异常时才会进行额外验证。这比传统2FA对所有登录请求都进行二次验证更加智能和方便。但是构建精准的用户画像也需要收集大量用户数据,这引起了一些隐私方面的担忧。

总之,行为2FA使验证更加无缝和智能,但需要在方便性与隐私之间求取平衡。

硬件 2FA

上面三种方式我们在日常中的接触还比较多,但下面的这两种方式,我们也许就用得比较少了。

硬件2FA使用专门的物理设备作为第二认证因素,例如yubikey等。用户将这些物理设备插入电脑,设备会在特定软件显示或生成一个随机验证码,输入验证码后即完成认证。

例如,你使用的银行采用了硬件2FA来保护网银登录。 登录时,你先输入用户名和密码,然后将银行提供的硬件令牌插入电脑的USB端口。 该令牌会显示一个6位数字验证码,你将其准确输入后便可完成二次认证。

这种方式安全可靠,但是你需要随身携带硬件令牌,如果硬件令牌丢失,你将无法完成二次认证,无法登录网银。所以在使用硬件双因素认证时,一定要注意妥善保管好硬件设备。

TOTP 2FA

TOTP是Time-based One-Time Passwords的缩写,意思是基于时间的一次性密码。它是一种利用当前时间和一个共享密钥生成唯一的数字密码的标准算法。只要服务端和客户端的时间和密钥相同,就可以进行验证。

TOTP 2FA也被称为应用程序验证、软件令牌或者软令牌。常见的支持TOTP标准的应用程序有Authy和Google Authenticator。

别踩雷!

虽然Authy很常见,但它并不是开源的,并且它会有一些违背TOTP标准的行为(在剖析TOTP的原理后我们将会介绍)。

同样地,Google Authenticator也不是开源的。如果你想使用TOTP 2FA这个标准,我们建议你使用开源的应用程序,比如FreeOTP, andOTP或aegis authenticator。

使用教程

要使用TOTP 2FA,你需要先在你的手机上安装一个支持TOTP标准的应用程序(可以在我们上面提到的软件中选择一款)。 然后,你需要在每个支持TOTP 2FA的网站或服务上进行设置。 不同的网站或服务可能有不同的设置流程,但一般都会提供一个二维码或者一个密钥给你扫描或者输入到你手机的应用程序中。 这样,你就可以在你手机的应用程序中看到每个网站或服务对应的一次性密码了。

当你登录一个支持TOTP 2FA的网站或服务时,除了输入你的用户名和密码之外,还需要输入你的应用程序中显示的一次性密码。 这个密码通常每30秒就会变化一次,所以要尽快输入。如果输入正确,就成功登录了!

下面是一个使用Google Authenticator设置和登录GitHub账户的示例:

  1. 在手机上安装Google Authenticator应用程序。

  2. 在电脑上打开GitHub网站,并登录自己的账户。

  3. 点击右上角的头像图标,然后选择 Settings

  4. 在左侧菜单中选择 Password and authentication

  5. Two-factor authentication 部分中点击 Enable two-factor authentication

  6. 等待跳转页面,接着输入你当前账号的密码。

  7. 打开手机上的Google Authenticator应用程序,并点击右下角的 + 号图标。

  8. 在手机上选择 Scan a QR code(扫描二维码),并扫描电脑屏幕上显示的二维码。

知识点

这个二维码其实是共享密钥字符串的一种编码方式,本质上就是一个字符串,只不过用二维码的方式来展示,方便用户扫描。我们也把这个字符串称为种子(seeds)

  1. 在Google Authenticator应用程序中会出现一个新条目,显示 GitHub:[用户名] 和一个6位数字密码。

  2. 在电脑上输入这个6位数字密码,并点击 Continue

  3. GitHub会显示一些备份恢复码,并提示保存到安全地方。这些恢复码可以在丢失手机或者无法使用应用程序时用来登录账户。

  4. 点击Download或者直接复制、截图来保存恢复码,并点击 I have saved my recovery codes

  5. 下次登录GitHub时,在输入用户名和密码后,还需要输入Google Authenticator应用程序中显示的6位数字密码。

  6. 如果输入正确,就可以成功登录了。

原理剖析

TOTP 2FA的原理是基于一个开放的标准,文档在RFC 6238中可以查看。 它的核心是一个叫做HMAC-based One-Time Password (HOTP)的算法,在RFC 4226中可以查阅到该算法的细节。 HOTP算法是一种利用一个共享密钥和一个计数器生成唯一的数字密码的算法。TOTP算法在HOTP算法的基础上,将计数器替换为当前时间戳与时间步长的整除结果。

TOTP算法的输入包括一个共享密钥K和一个时间步长X。共享密钥K是一个随机生成的字符串,通常在设置TOTP 2FA时由服务端生成并通过二维码或者文本形式提供给客户端。时间步长X是一个固定的时间间隔,通常为30秒或者60秒。TOTP算法的输出是一个数字密码,通常为6位或者8位。

In a word

一言蔽之,TOTP通过HMAC算法,以密钥(也就是你扫描的二维码所代表的字符串)和当前设备的时间戳派生出一次性密码种子,进而生成验证码。在验证时,服务端也会通过同样的算法,以密钥当前设备的时间戳派生出一次性密码种子,进而生成验证码,然后与用户输入的验证码进行比对,如果相同,则验证通过。

可见,TOTP的安全性依赖于密钥的安全性,所以在设置TOTP时,务必要保证密钥的安全性。然而,诸如Authy这样的应用程序,却会将密钥存储在它们的服务器上, 这就违背了TOTP的安全原则。并且用户甚至无权查看他们的密钥,这使得用户无法自己备份密钥,从而捆绑于这些应用程序中!

TOTP算法的具体步骤如下:

  1. 计算当前时间戳(以秒为单位)与初始计数时间戳的差,再与时间步长X作商,得到一个整数C(向下取整),其中C是一个8个byte的数值,客户端与服务端需要保持同步。

  2. 随机生成一个至少128位的共享密钥K。

  3. 使用HMAC-SHA-1算法,以共享密钥K为密钥,以数值C为消息,计算消息认证码(MAC),得到一个20字节的十六进制串HS。

  4. 取HS的最后一个字节,再进一步取该字节的最低4位作为偏移量O,并将该偏移量转化为十进制数。

  5. 取HS从第O个字节开始的4个字节(共32位)为Sbits。

  6. 将Sbits转换为一个十进制整数Snum。

  7. Snum对10的6次幂或8次幂(根据需要的密码长度)进行取模运算,得到一个6位或者8位的数字密码D。

代码实现!

纸上谈来终觉浅,绝知此事要躬行。为了更好地理解和掌握TOTP 2FA的原理,我们可以自己动手实现一个简单的TOTP算法!

方便起见,我们可以借助一些编程语言提供的库或者模块来完成某些功能,比如生成和扫描二维码、计算时间戳、计算HMAC-SHA-1等。这里我们以Python语言为例,展示一些简单的代码片段。

首先,我们需要安装一些依赖库:

pip install pyotp
pip install qrcode
pip install pillow
pip install requests

然后,我们可以使用pyotp库来生成一个共享密钥,并生成一个包含密钥信息的二维码图片:

import pyotp
import qrcode

# 生成一个随机的共享密钥
key = pyotp.random_base32()
print(key)

# 生成一个包含密钥信息和服务名称的二维码图片
uri = pyotp.totp.TOTP(key).provisioning_uri(name='user@example.com', issuer_name='Example')
img = qrcode.make(uri)
img.save('qrcode.png')

接下来,我们可以使用手机上的应用程序扫描这个二维码图片,并获取对应的一次性密码。我们可以使用pyotp库来验证这个密码是否正确:

import pyotp

# 创建一个TOTP对象
totp = pyotp.totp.TOTP(key)

# 获取当前时间对应的一次性密码
password = totp.now()
print(password)

# 验证一次性密码是否正确
result = totp.verify(password)
print(result)

如果要在自己的网站或服务上实现TOTP 2FA,我们需要在后端和前端都进行一些开发工作。后端需要提供一个接口来生成和存储共享密钥,以及验证一次性密码。前端需要提供一个界面来显示和扫描二维码,以及输入和提交一次性密码。

这里我们以Flask框架为例,展示一些简单的代码片段。我们假设我们已经有了一个用户模型和一个用户数据库。

首先,我们需要在用户模型中添加一个字段来存储共享密钥:

from flask_sqlalchemy import SQLAlchemy
from pyotp import random_base32

db = SQLAlchemy()

class User(db.Model):
# 省略其他字段
totp_key = db.Column(db.String(16), default=random_base32)

然后,我们需要在登录视图函数中添加一个逻辑来判断用户是否启用了TOTP 2FA,并根据情况跳转到不同的页面:

from flask import Flask, render_template, redirect, url_for, flash, session
from flask_login import login_user
from pyotp import TOTP

app = Flask(__name__)

@app.route('/login', methods=['GET', 'POST'])
def login():
form = LoginForm()
if form.validate_on_submit():
user = User.query.filter_by(username=form.username.data).first()
if user and user.check_password(form.password.data):
# 如果用户启用了TOTP 2FA
if user.totp_key:
# 将用户ID存入会话中
session['user_id'] = user.id
# 跳转到输入一次性密码的页面
return redirect(url_for('totp'))
else:
# 登录用户
login_user(user)
# 跳转到主页
return redirect(url_for('index'))
else:
flash('Invalid username or password')
return render_template('login.html', form=form)

接下来,我们需要创建一个输入一次性密码的表单和一个输入一次性密码的视图函数:

from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField
from wtforms.validators import DataRequired

class TOTPForm(FlaskForm):
password = StringField('One-time password', validators=[DataRequired()])
submit = SubmitField('Verify')

@app.route('/totp', methods=['GET', 'POST'])
def totp():
form = TOTPForm()
if form.validate_on_submit():
# 从会话中获取用户ID
user_id = session.get('user_id')
if user_id:
user = User.query.get(user_id)
if user:
# 创建一个TOTP对象
totp = TOTP(user.totp_key)
# 验证一次性密码是否正确
if totp.verify(form.password.data):
# 登录用户
login_user(user)
# 清除会话中的用户ID
session.pop('user_id')
# 跳转到主页
return redirect(url_for('index'))
flash('Invalid or expired one-time password')
return render_template('totp.html', form=form)

最后,我们需要创建一个设置TOTP 2FA的表单和一个设置TOTP 2FA的视图函数:

from flask import Flask, render_template, redirect, url_for, flash, session
from flask_login import login_required, current_user

class TOTPSetupForm(FlaskForm):
submit = SubmitField('Enable two-factor authentication')

@app.route('/totp-setup', methods=['GET', 'POST'])
@login_required
def totp_setup():
form = TOTPSetupForm()
if form.validate_on_submit():
# 启用TOTP 2FA
current_user.totp_key = random_base32()
db.session.commit()
flash('Two-factor authentication enabled')
return redirect(url_for('index'))
# 生成一个包含密钥信息和服务名称的二维码图片的URL
uri = TOTP(current_user.totp_key).provisioning_uri(name=current_user.email, issuer_name='Example')
qrcode_url = 'https://api.qrserver.com/v1/create-qr-code/?data=' + uri
return render_template('totp_setup.html', form=form, qrcode_url=qrcode_url)

这样😍,我们就完成了TOTP 2FA的基本功能的实现。当然,这些代码只是为了演示,还有很多细节和优化可以做!

Pros and cons

总而言之,TOTP 2FA是一种轻量级、易于实施的身份验证方法,可以在不增加太多成本的情况下提高账户安全性,但也需要权衡其依赖设备和密码时效性带来的潜在问题,根据实际情况选择是否启用。

总结

综上所述,2FA通过在登录时提供两个验证要素,大大提高了账户的安全性,有效地防止了账号冒用。

短信2FA简单易用,但在某些应用中存在安全隐患,而TOTP 2FA、硬件2FA和生物2FA安全性更高。鉴于不同2FA机制各有优劣,我们应根据实际场景选择合适的2FA方案,谨防恶意攻击,并注意妥善保管验证设备和密钥。

总之,2FA能有效防范大多数账户盗用情况,是一种值得推荐的安全最佳实践。但也需要权衡安全性、便利性和隐私性,谨慎选择适合自己的2FA解决方案。

支持一下

暂无评论,来留下友好的评论吧