了解2FA(双因素认证)
1086 字
5 分钟
了解2FA(双因素认证)

越来越多项目要求启用2FA,不得不去了解它!
概念
- 2FA 即:双因素认证 <=> Two-factor authentication <=> 2FA
- 双因素认证就是指,通过认证同时需要两个因素的证据,例如银行卡就是最常见的双因素认证,我们必须同时提供“银行卡”、“密码”两种信息才能拿钱
2FA的核心是组合2种不同类型的验证因子,验证因子主要分为以下3类:
- 你知道的东西:密码、PIN码等
- 你拥有的东西:手机、电脑安全密钥等,通过获取一次性验证码(OTP)或确认推送通知来完成验证
- 你是什么:指纹、虹膜等
2FA 方案
在实际应用中,最常用的 2FA 方案是 “密码 + 你拥有的东西”,因为生物特征(第三因素)需要特殊硬件支持,普适性稍差
方案(一)密码 + 硬件令牌
例如银行的U盾、以前网络游戏(如DNF)的密保卡、登录GitHub或系统用的YubiKey。用户插入设备或输入设备上动态码后,再输入密码
方案(二)密码 + 手机短信验证码
国内最常见的方案。登录时向绑定手机发送一条包含短时验证码的短信。注意:此方案安全性相对较低,因为短信可能被拦截、SIM卡可能被克隆,但胜在便捷
方案(三)密码 + 基于时间的一次性密码(TOTP)(当前主流且推荐的方案)
这是公认较可靠的软件层面2FA方案,国际标准为RFC6238 。用户需要先在自己的手机上安装一个身份验证器App(如 Google Authenticator、Microsoft Authenticator、Authy 等)
- 第一步(绑定):服务端生成一个密钥,以二维码形式展示给用户。
- 第二步(保存):用户用App扫描二维码,密钥安全保存到手机(也就说这个密钥在手机和服务端上都存有了)。注意,密钥必须跟手机绑定。一旦用户更换手机,就必须生成全新的密钥
- 第三步(验证):登录时,App使用密钥和当前时间戳,通过特定算法(如HMAC-SHA1)生成一个30秒或60秒(默认30秒)内有效的6-8位动态验证码。用户在有效期内,把这个动态验证码提交给服务器。服务器也使用密钥和当前时间戳,生成一个动态验证码,跟用户提交的验证码比对。只要两者不一致,就拒绝登录
TOTP的算法
TOTP 的全称是”基于时间的一次性密码”(Time-based One-time Password),仔细看上面的步骤,我会有一个问题:App和服务端,如何保证30秒期间都得到同一个验证码?答案就是下面的这个公式:
TC = floor((unixtime(now) − unixtime(T0)) / TS)TC—> 时间计数器unixtime(now)—> 当前 Unix 时间戳unixtime(T0)—> 约定的起始时间点的时间戳,默认是0,也就是1970年1月1日TS—> 哈希有效期的时间长度,默认是30秒
因此,上面的公式就变成下面的形式
TC = floor(unixtime(now) / 30)所以,只要在 30 秒以内,TC 的值都是一样的。前提是服务端和手机端的时间必须同步。接下来,就可以算出验证码了。
TOTP = HASH(SecretKey, TC)上面代码中,HASH就是约定的哈希函数,默认是 SHA-1
另外,TOTP 有“硬件生成器”和“软件生成器”之分,都是采用上面的算法
(左:硬件生成器,右:软件生成器)

TOTP的实现(Csharp)
(1)NuGet 引入 SimpleBase 包
(2)简单实现
using System;using System.Security.Cryptography;using SimpleBase;
public class SimpleTotp{ public static string Generate(string base32Secret) { // 1. 解码 Base32 密钥 byte[] secret = Base32.Rfc4648.Decode(base32Secret);
// 2. 计算时间计数器 (30秒一个窗口) long counter = DateTimeOffset.UtcNow.ToUnixTimeSeconds() / 30; byte[] counterBytes = BitConverter.GetBytes(counter); if (BitConverter.IsLittleEndian) Array.Reverse(counterBytes);
// 3. HMAC-SHA1 byte[] hash; using (var hmac = new HMACSHA1(secret)) { hash = hmac.ComputeHash(counterBytes); }
// 4. 动态截取 int offset = hash[^1] & 0x0F; int code = ((hash[offset] & 0x7F) << 24) | ((hash[offset + 1] & 0xFF) << 16) | ((hash[offset + 2] & 0xFF) << 8) | (hash[offset + 3] & 0xFF);
code %= 1000000; return code.ToString("D6"); }}参考
(完)
支持与分享
如果这篇文章对你有帮助,欢迎分享给更多人或打赏支持!
相关文章智能推荐
1
C# 技术备忘 - LINQ
CsharpLINQ是C#进阶的必经之路,它能让你用声明式的方式操作数据,代码会变得非常简洁优雅
2
使用DBeaver连接Dynamics 365 Dataverse
Dynamics 365在数据分析或开发工作中,有时我们希望能用熟悉的SQL工具直接查询Dataverse中的数据,除了使用SSMS、SQL 4 CDS外,还可以使用DBeaver,下文详细记录了配置过程。
3
C# 技术备忘 (1)
Csharp开发备忘笔记,记录日常开发中遇到的小技巧、代码片段和最佳实践
4
Dynamics 365 事件框架、事件执行管道
Dynamics 365Dynamics 365 插件运行的“流水线”到底长什么样?
5
初尝Github Actions
Git和GithubGitHub Actions 是 GitHub 提供的 CI/CD 服务,早在 2018 年 10 月就已推出,但我一直未曾使用。最近遇到一个需求(如何更优雅地部署博客),才终于想起了它。目前我的站点放在自己的服务器上,每次都要先在本地编译,再手动打包上传到服务器,接着解压到 public 文件夹,整个过程有点繁琐。而博客的源码已经托管在 GitHub 上,我想,如果每次 push 后都能自动完成部署,那就太好了。于是研究了下 Github Actions,顺便把一些内容记录下来,方便日后查阅。
随机文章随机推荐
