面试官:讲讲2FA 双因素认证原理

1,359 阅读13分钟

前几天配置 GitHub 2FA 的时候,盯着那个每 30 秒跳动的 6 位数字发呆——扫个二维码就能生成验证码,还不需要联网,这玩意儿到底是怎么做到的?

带着这个疑问,我花了个周末把 TOTP(Time-based One-Time Password)的原理了解了下。没想到这个看起来简单的小功能,背后的密码学设计还挺有意思的。

先抛几个问题,看看你是不是也有同样的困惑:

  • 那 6 位数字是怎么算出来的?
  • 二维码里到底存了啥?
  • 为什么手机和服务器能算出一样的数字?
  • 登录的时候为啥不需要再扫码了?(我一开始真的搞混了)

目录


为什么需要双因素认证?

密码已经不够安全了

微软 2019 年发了个报告,里面几个数字挺吓人的:

  • 81% 的数据泄露都和弱密码或密码重用有关
  • 开了 2FA 后,99.9% 的账号入侵都能被阻止
  • 每天有大约 30 亿次密码攻击

想想也是,我自己常用的密码也就那几个,虽然会加点变化,但核心部分基本没变。万一哪个小网站被拖库了,攻击者拿着这个密码到处试,说不定就中了。

常见的密码攻击方式

攻击者拿密码的套路有这些:

graph TB
    A[攻击者] --> B[暴力破解]
    A --> C[撞库攻击]
    A --> D[钓鱼攻击]
    A --> E[键盘记录]

    B --> F[自动尝试常见密码]
    C --> G[使用泄露的密码数据库]
    D --> H[伪造登录页面骗取密码]
    E --> I[恶意软件记录键盘输入]

    F --> J[获取账号访问权]
    G --> J
    H --> J
    I --> J

    style J fill:#ff6b6b

问题的根源在于密码只是"你知道的东西",一旦泄露就完了。单点失败,没有冗余。

双因素认证的思路

2FA 的思路很直接:光有密码不够,还得有手机(或者其他设备)。就算密码被偷了,没手机照样进不去。


双因素认证的三类要素

认证因素分类

类别说明示例安全特性
你知道的 (Knowledge)记忆中的信息密码、PIN 码、安全问题可被窃取、破解、钓鱼
你拥有的 (Possession)物理持有的设备手机、硬件令牌、USB 密钥需要物理访问
你是谁 (Inherence)生物特征指纹、面容、虹膜难以复制

2FA = 密码(知道) + TOTP(拥有)

攻击者就算搞到了密码,没你的手机也白搭。

常见的 2FA 实现方案对比

方案第一因素第二因素安全性缺点
SMS 短信密码手机号收短信⭐⭐可被 SIM 卡劫持攻击
TOTP App密码手机 App 生成验证码⭐⭐⭐⭐需要时间同步
硬件密钥密码USB 安全密钥(YubiKey)⭐⭐⭐⭐⭐需要额外硬件
推送通知密码手机 App 批准⭐⭐⭐⭐需要网络
WebAuthn/Passkeys-公钥加密 + 生物识别⭐⭐⭐⭐⭐需要硬件支持

为什么 TOTP 最流行?

TOTP 算是性价比最高的方案了:

优点

  • 离线能用(飞行模式都没问题)
  • 完全免费(不像 YubiKey 要几百块)
  • 哪都能用(各种平台都有 App)
  • 标准统一(一个 App 管所有账号)
  • 容易备份(密钥可以导出)

相比之下,短信验证码问题就多了。前段时间看新闻,英国 2024 年 SIM 卡劫持诈骗暴增了 10 倍。攻击者通过社会工程学骗运营商把你的号码转到他们手机上,然后就能收你的验证码了。地下室收不到信号、出国漫游费贵这些就不说了。


TOTP 算法核心原理

基本思路

TOTP 的原理说穿了挺简单:

服务器和客户端各存一份相同的密钥(Secret),然后都用"当前时间"作为输入,通过相同的算法计算,得到相同的 6 位数字。因为用的是时间,所以每 30 秒会自动变化。

graph LR
    A[共享密钥 Secret] --> B[客户端 TOTP]
    A --> C[服务器 TOTP]
    D[当前时间] --> B
    D --> C
    B --> E[生成: 123456]
    C --> F[生成: 123456]
    E --> G{验证码匹配?}
    F --> G
    G -->|是| H[认证成功]
    G -->|否| I[认证失败]

    style A fill:#4ecdc4
    style D fill:#4ecdc4
    style H fill:#95e1d3
    style I fill:#ff6b6b

TOTP 核心公式

TOTP = HMAC-SHA1(Secret, TimeCounter) % 10^Digits

具体来说:

  1. Secret(共享密钥)

    • 通常 160 位(20 字节)
    • Base32 编码后像这样:JBSWY3DPEHPK3PXP
    • 扫二维码时传输的就是这个
  2. TimeCounter(时间计数器)

    TimeCounter = floor(CurrentUnixTime / 30)
    // 每 30 秒跳一次
    
  3. HMAC-SHA1(哈希消息认证码)

    • 输入:Secret + TimeCounter
    • 输出:160 位(20 字节)哈希值
  4. 动态截断(Dynamic Truncation)

    • 从 20 字节哈希中提取 4 字节
    • 转换为 6 位数字(或 8 位)

TOTP 算法流程图

flowchart TD
    Start([开始生成 TOTP]) --> GetTime[获取当前 Unix 时间戳]
    GetTime --> CalcCounter[计算 TimeCounter = floor 时间戳 / 30秒]
    CalcCounter --> ToHex[将 TimeCounter 转为 8 字节大端序]
    ToHex --> HMAC[HMAC-SHA1共享密钥, TimeCounter]
    HMAC --> GetHash[得到 20 字节哈希值]
    GetHash --> GetOffset[取最后 1 字节的低 4 位作为 offset]
    GetOffset --> Extract[从 offset 位置提取 4 字节]
    Extract --> MaskBit[屏蔽最高位得到 31 位整数]
    MaskBit --> Modulo[对 1000000 取模]
    Modulo --> PadZero[不足 6 位前面补 0]
    PadZero --> Output([输出 6 位验证码])

    style Start fill:#4ecdc4
    style HMAC fill:#f38181
    style Output fill:#95e1d3

为什么是 30 秒?

30 秒是个折中方案。太短了(比如 10 秒),你还没输完就过期了;太长了(比如 5 分钟),安全性又差。

实际上服务器会检查前后各 1 个时间窗口,也就是说你有 90 秒的时间输入验证码。这样即使手机和服务器时间有点偏差也没关系。

gantt
    title TOTP 时间窗口示例
    dateFormat X
    axisFormat %S秒

    section 时间窗口
    窗口1 (123456) :0, 30
    窗口2 (789012) :30, 60
    窗口3 (345678) :60, 90
    窗口4 (901234) :90, 120

就算有人偷拍了你的屏幕,最多 90 秒后这个验证码就废了。


GitHub 2FA 完整流程详解

流程一:GitHub 2FA 启用流程

sequenceDiagram
    autonumber
    actor User as 用户
    participant Browser as 浏览器
    participant GitHub as GitHub 服务器
    participant App as TOTP App<br>(Google Authenticator)

    User->>Browser: 访问 Settings → Password and authentication
    Browser->>GitHub: 请求启用 2FA
    GitHub->>GitHub: 生成随机密钥 Secret<br>(20 字节 Base32 编码)
    GitHub->>GitHub: 生成恢复码<br>(8-16 个备用码)
    GitHub->>Browser: 返回 QR 码 + Setup Key
    Note over GitHub,Browser: QR 码内容:<br>otpauth://totp/GitHub:username<br>?secret=SECRET&issuer=GitHub

    Browser->>User: 显示 QR 码
    User->>App: 打开 TOTP App
    User->>App: 扫描 QR 码
    App->>App: 保存 Secret 到本地加密存储
    App->>App: 开始生成 TOTP 验证码<br>(每 30 秒更新)
    App->>User: 显示 6 位验证码

    User->>Browser: 输入验证码
    Browser->>GitHub: 提交验证码
    GitHub->>GitHub: 验证 TOTP 码是否匹配
    alt 验证成功
        GitHub->>GitHub: 保存加密后的 Secret
        GitHub->>GitHub: 启用 2FA 标志
        GitHub->>Browser: 显示恢复码
        Browser->>User: 下载并保存恢复码
        Note over User: ⚠️ 恢复码是唯一的<br>账号恢复方式
        GitHub->>Browser: 2FA 启用成功
    else 验证失败
        GitHub->>Browser: 提示验证码错误
    end

几个关键点:

  1. 密钥只传一次 - 扫码那次之后,服务器和客户端就各算各的了
  2. 必须加密存储 - 无论手机还是服务器,Secret 都不能明文存
  3. 恢复码很重要 - 手机丢了这是唯一的救命稻草,我都打印出来锁保险箱了
  4. 有 28 天缓冲期 - GitHub 刚开 2FA 会给你 28 天适应期

流程二:GitHub 2FA 登录验证流程

sequenceDiagram
    autonumber
    actor User as 用户
    participant Browser as 浏览器
    participant GitHub as GitHub 服务器
    participant App as TOTP App
    participant DB as 数据库

    User->>Browser: 访问 github.com
    Browser->>GitHub: 请求登录页面
    GitHub->>Browser: 返回登录表单

    User->>Browser: 输入用户名 + 密码
    Browser->>GitHub: POST /login (credentials)
    GitHub->>DB: 验证密码哈希

    alt 密码正确 & 2FA 已启用
        DB->>GitHub: 返回用户信息 + 2FA 启用标志
        GitHub->>Browser: 返回 2FA 验证页面
        Browser->>User: 要求输入验证码

        User->>App: 打开 TOTP App
        App->>App: Secret + floor(time/30) → HMAC-SHA1
        App->>App: 动态截断 → 生成 6 位码
        App->>User: 显示验证码 (如: 123456)

        User->>Browser: 输入 6 位验证码
        Browser->>GitHub: POST /verify-2fa (code: 123456)

        GitHub->>DB: 获取用户加密的 Secret
        GitHub->>GitHub: 解密 Secret
        GitHub->>GitHub: 计算当前时间窗口 TOTP
        GitHub->>GitHub: 计算前后窗口 TOTP (±1)

        alt 验证码匹配
            GitHub->>GitHub: 生成 Session Token
            GitHub->>Browser: 设置认证 Cookie
            GitHub->>DB: 记录登录成功日志
            Browser->>User: 登录成功,跳转到主页
        else 验证码不匹配
            GitHub->>GitHub: 记录失败尝试
            alt 失败次数 < 5
                GitHub->>Browser: 返回错误提示
            else 失败次数 >= 5
                GitHub->>GitHub: 触发账号锁定 (5 分钟)
                GitHub->>Browser: 返回锁定提示
            end
        end

    else 密码错误
        GitHub->>Browser: 返回密码错误提示
    end

GitHub 在安全上做得挺细的:

  1. 先验密码 - 密码错了不会告诉你有没有开 2FA
  2. 时间容错 - 前后各检查 1 个窗口
  3. 防暴力破解 - 5 分钟最多试 5 次
  4. 防重放 - 同一个验证码不能用两次
  5. 会话管理 - Cookie 也有过期时间

扫描二维码的秘密

很多人会搞混,我来捋一下:

场景时机二维码出现作用
启用 2FA首次配置显示二维码传输共享密钥 Secret
登录验证每次登录不显示二维码只需输入验证码

启用时的二维码长这样:

otpauth://totp/GitHub:username?secret=JBSWY3DPEHPK3PXP&issuer=GitHub&algorithm=SHA1&digits=6&period=30

参数说明:

参数说明示例值
scheme协议类型otpauth://totp
label账号标识GitHub:username
secret共享密钥JBSWY3DPEHPK3PXP (Base32)
issuer发行者GitHub
algorithm哈希算法SHA1 (默认)
digits验证码位数6 (默认)
period时间步长(秒)30 (默认)

如果你要自己实现,推荐这些库:


安全性深度分析

TOTP 的安全优势

mindmap
  root((TOTP 安全性))
    一次性密码
      30秒自动失效
      无法重放使用
      降低时间窗口攻击风险
    双向独立性
      密码泄露不影响验证码
      验证码截获无法推导密钥
      攻击者需同时突破两道防线
    抗重放攻击
      服务器可记录已使用验证码
      防止同一验证码多次使用
      结合时间窗口限制
    离线安全
      不依赖网络通信
      无中间人攻击风险
      避免网络层面拦截
    标准化
      RFC 6238 国际标准
      多平台兼容
      开源实现可审计

潜在的安全风险与防御

风险 1:密钥泄露

如果手机被 root 了,恶意软件可能偷走密钥:

sequenceDiagram
    actor Attacker as 攻击者
    participant Phone as 用户手机
    participant Malware as 恶意软件
    participant Server as 目标服务器

    Attacker->>Phone: 植入恶意软件
    Malware->>Malware: 获取 Root 权限
    Malware->>Phone: 读取 TOTP App 数据库
    Malware->>Attacker: 窃取密钥 Secret
    Attacker->>Attacker: 生成 TOTP 验证码
    Attacker->>Server: 使用窃取的验证码登录
    Server->>Attacker: 登录成功

防御办法:

防御措施说明有效性
密钥加密存储使用 AES-256 加密,密钥由硬件保护⭐⭐⭐⭐⭐
硬件安全模块 (HSM)密钥永不离开安全芯片⭐⭐⭐⭐⭐
生物识别锁定App 打开需要指纹/面容⭐⭐⭐⭐
设备绑定检测设备指纹变化⭐⭐⭐

风险 2:实时钓鱼攻击(MitM)

这个是 TOTP 的软肋,钓鱼网站可以实时转发你的验证码:

sequenceDiagram
    actor User as 用户
    participant Phishing as 钓鱼网站<br>(attacker.com)
    participant Attacker as 攻击者后端
    participant Real as 真实网站<br>(github.com)

    User->>Phishing: 访问钓鱼网站
    User->>Phishing: 输入密码 + TOTP
    Note over User,Phishing: 用户以为是真网站

    Phishing->>Attacker: 转发凭证
    Attacker->>Real: 立即使用凭证登录
    Real->>Attacker: 返回 Session Cookie
    Attacker->>Phishing: 保持用户会话
    Phishing->>User: 显示假的成功页面

    Note over Attacker: 攻击者获得<br>真实账号访问权

为啥 TOTP 防不了钓鱼?

  • TOTP 不验证域名
  • 验证码还有 30-90 秒有效期,够攻击者用了
  • 用户很难分辨 URL 真假

防御办法:

防御措施说明有效性
WebAuthn/FIDO2密钥绑定域名,钓鱼网站无法获取⭐⭐⭐⭐⭐
硬件安全密钥U2F 协议绑定 TLS 证书⭐⭐⭐⭐⭐
证书绑定Certificate Pinning⭐⭐⭐⭐
用户教育检查 URL、HTTPS 证书⭐⭐
浏览器防护反钓鱼扩展、浏览器警告⭐⭐⭐

风险 3:SIM 卡劫持攻击(针对 SMS 2FA)

这就是我不用短信验证码的原因。2024 年英国 SIM 卡交换诈骗暴增 1055%(从 289 起增至 3000 起),96% 的案例都是通过社会工程学或运营商内鬼搞的。

攻击流程:

flowchart TD
    A[攻击者收集目标信息] --> B[社会工程学攻击运营商]
    B --> C{运营商验证}
    C -->|内部串通| D[直接转移号码]
    C -->|伪造身份| D
    D --> E[SIM 卡转移到攻击者手机]
    E --> F[拦截 SMS 验证码]
    F --> G[登录受害者账号]
    G --> H[转移资产/数据]

    style A fill:#ff6b6b
    style D fill:#ff6b6b
    style H fill:#ff6b6b

TOTP 为啥不怕这个?

  • 密钥在手机本地,不在 SIM 卡上
  • 换 SIM 卡拿不到密钥
  • 就算手机号被劫持了,TOTP App 照样能用

风险 4:社会工程学攻击

经典话术:

  • "您好,我是 GitHub 客服,检测到您的账号异常,请提供验证码。"
  • "系统升级需要验证,请把验证码告诉我。"

记住:真的客服永远不会要你的验证码!

攻击方式对比

攻击方式SMS 2FATOTPWebAuthn硬件密钥
SIM 卡劫持高风险不受影响不受影响不受影响
实时钓鱼高风险高风险免疫免疫
密钥泄露无密钥保护中等风险硬件保护硬件保护
社会工程高风险中等风险较低风险较低风险
中间人攻击可拦截离线生成绑定域名绑定域名

生产环境最佳实践

1. 密钥安全管理

密钥管理的几个要点:

  • 加密存储:AES-256-GCM,不要用 ECB 模式
  • 硬件保护:能用 HSM 就用 HSM
  • 密钥轮换:定期换主密钥
  • 别记日志:千万别把密钥打到日志里

客户端可以用用户密码派生的密钥加密,服务器端用环境变量或 HSM。

2. 速率限制

防暴力破解:

机制配置说明
尝试次数限制5 次 / 5 分钟超过锁定 5 分钟
IP 速率限制10 次 / 分钟防分布式攻击
渐进式延迟失败后延迟加倍1s → 2s → 4s → 8s
CAPTCHA3 次失败后防自动化攻击

3. 时间同步与容错

标准做法:

  • 检查 ±1 个时间窗口(90 秒有效期)
  • 记录时钟漂移超过 60 秒的设备
  • 根据历史偏移动态调整

4. 防重放攻击

用 Redis 记录用过的验证码:

  1. 验证前检查是否已用
  2. 验证成功后标记为已用
  3. 设置 90 秒过期

5. 恢复码机制

生成 8-16 个一次性恢复码:

  • SHA-256 哈希存储
  • 每个只能用一次
  • 提供下载和打印功能

现代认证技术演进

TOTP vs WebAuthn vs Passkeys

timeline
    title 认证技术演进
    2000s : 单因素密码认证
          : 暴力破解、撞库攻击盛行
    2010s : SMS 2FA 普及
          : 仍存在 SIM 卡劫持风险
    2012 : RFC 6238 - TOTP 标准化
          : Google Authenticator 发布
    2014 : FIDO U2F 发布
          : 硬件密钥防钓鱼
    2019 : WebAuthn 成为 W3C 标准
          : 浏览器原生支持
    2022 : Passkeys 推出
          : Apple/Google/Microsoft 联合支持
    2024 : 无密码认证时代
          : 生物识别 + 硬件保护

技术对比

特性TOTPWebAuthnPasskeys
标准RFC 6238W3C 标准FIDO2 + WebAuthn
设备要求手机/电脑硬件密钥或设备支持生物识别的设备
离线可用部分支持需要网络同步
防钓鱼域名绑定域名绑定
用户体验良好很好优秀
安全性很高很高
兼容性广泛支持需要浏览器支持新标准,逐步支持
成本免费需要硬件($20-50)免费(需支持设备)

WebAuthn 工作原理

sequenceDiagram
    autonumber
    actor User as 用户
    participant Browser as 浏览器
    participant Server as 服务器
    participant Auth as 生物识别/<br>安全密钥

    rect rgb(200, 220, 250)
    Note over User,Auth: 注册阶段
    User->>Browser: 请求注册
    Browser->>Server: 开始注册
    Server->>Server: 生成挑战(Challenge)
    Server->>Browser: 返回挑战
    Browser->>Auth: 请求创建凭证
    Auth->>User: 要求生物识别
    User->>Auth: 指纹/面容验证
    Auth->>Auth: 生成公私钥对
    Auth->>Browser: 返回公钥 + 认证数据
    Browser->>Server: 提交公钥
    Server->>Server: 保存公钥(绑定域名)
    Server->>Browser: 注册成功
    end

    rect rgb(220, 250, 220)
    Note over User,Auth: 登录阶段
    User->>Browser: 请求登录
    Browser->>Server: 开始认证
    Server->>Server: 生成新挑战
    Server->>Browser: 返回挑战
    Browser->>Auth: 请求签名
    Auth->>User: 要求生物识别
    User->>Auth: 指纹/面容验证
    Auth->>Auth: 使用私钥签名挑战
    Auth->>Browser: 返回签名
    Browser->>Server: 提交签名
    Server->>Server: 使用公钥验证签名
    Server->>Browser: 登录成功
    end

WebAuthn 的牛逼之处:

  1. 防钓鱼:公钥绑定域名,钓鱼网站拿不到
  2. 无密码:可以完全不要密码了
  3. 硬件保护:私钥不离开设备
  4. 抗重放:每次挑战不同

Passkeys 的未来

Passkeys 基于 WebAuthn 和 FIDO2,用公钥加密 + 生物识别,还能跨设备同步(通过 iCloud/Google 账号)。

graph TB
    A[用户访问网站] --> B{选择登录方式}
    B --> C[Passkey 登录]
    C --> D[设备生物识别]
    D --> E{验证成功?}
    E -->|是| F[使用私钥签名]
    F --> G[服务器验证签名]
    G --> H[登录成功]
    E -->|否| I[拒绝访问]

    style D fill:#4ecdc4
    style F fill:#95e1d3
    style H fill:#95e1d3
    style I fill:#ff6b6b

Passkeys 好在哪?

  1. 体验好:按个指纹就登录了
  2. 不怕换手机:云同步
  3. 绝对防钓鱼:密钥绑定域名
  4. 大厂支持:苹果、谷歌、微软都在推

总结

研究完 TOTP,我的理解是:

原理很简单

  • 服务器和客户端各存一份密钥
  • 都用当前时间算,结果一样
  • 每 30 秒变一次
  • 完全离线,不需要网络

安全性不错

  • 密码泄露了,没手机也没用
  • 验证码很快过期,偷了也没用
  • 比短信安全太多(不怕 SIM 卡劫持)
  • 但还是怕钓鱼(这是弱点)

使用建议

  1. 启用时:扫码 → 验证 → 保存恢复码(别忘了!)
  2. 登录时:密码 → 验证码 → 搞定
  3. 手机丢了:用恢复码登录,重新配置

选择建议

graph TD
    A[需要 2FA?] -->|是| B{安全要求级别}
    B -->|基础| C[TOTP App<br>Google Authenticator]
    B -->|中等| D[TOTP + 恢复码]
    B -->|高| E[硬件密钥<br>YubiKey]
    B -->|最高| F[WebAuthn/Passkeys<br>+ 硬件密钥]

    A -->|否| G[高风险<br>强烈建议启用]

    style C fill:#95e1d3
    style D fill:#f9ca24
    style E fill:#6c5ce7
    style F fill:#00b894
    style G fill:#ff6b6b
场景推荐方案理由
个人开发者TOTP App免费、便捷、足够安全
企业账号TOTP + 恢复码多设备备份,防单点故障
金融/支付硬件密钥最高安全性,防钓鱼
高价值账号WebAuthn + 硬件密钥未来标准,绝对防钓鱼
临时测试SMS 2FA仅用于非关键场景

实用建议

启用 2FA 的话

  • 选个靠谱的 App(我用 Authy,能云同步)
  • 扫码后多备份几份(可以扫多个设备)
  • 恢复码打印出来藏好
  • 测试一下能不能用
  • 团队账号记得通知其他人

要开发 2FA 系统

  • 随机数用 crypto.getRandomValues(),别用 Math.random()
  • 服务器端密钥必须加密(AES-256-GCM)
  • 时间窗口 ±1(容忍时钟误差)
  • 速率限制必须有(5 分钟 5 次)
  • Redis 记录用过的验证码
  • 生成恢复码,哈希存储
  • 记日志方便排查

安全相关

  • HTTPS 必须的,不然密钥传输裸奔
  • 开 HSTS,防降级攻击
  • CSP 头加上,防 XSS
  • 监控时钟漂移
  • 教育用户:验证码谁都不给

权威参考资源

官方标准文档

  1. RFC 6238 - TOTP: Time-Based One-Time Password Algorithm

    • TOTP 官方标准
  2. RFC 4226 - HOTP: An HMAC-Based One-Time Password Algorithm

    • HOTP 基础算法
  3. OWASP Multi-Factor Authentication Cheat Sheet

    • MFA 最佳实践

官方实现与文档

  1. GitHub 2FA 官方文档
  2. WebAuthn 官方指南
  3. FIDO Alliance - Passkeys

开源实现库

  1. pyauth/pyotp - Python TOTP 库
  2. yeojz/otplib - JavaScript/TypeScript TOTP 库
  3. susam/mintotp - 20 行 Python TOTP 实现(学习用)

安全研究

  1. Microsoft - 99.9% of account compromises can be blocked by MFA
  2. OWASP - Authentication Vulnerabilities

写在最后

研究 TOTP 最大的收获是:看似简单的东西,设计起来还挺有讲究的。

下次扫二维码时,你会知道:

  • 那是在传密钥
  • 6 位数字是用时间算的
  • 离线能用是因为不需要网络
  • 虽不完美,但比纯密码强太多

WebAuthn 和 Passkeys 确实是趋势,防钓鱼、体验好。但 TOTP 兼容性好、门槛低,短期内还会继续用。

如果还没开 2FA,赶紧开吧。每天 30 亿次密码攻击,别成为那个倒霉蛋。


有问题欢迎交流,参考资料都在文章里了。特别推荐看看 RFC 6238 和 OWASP 的安全指南,写得很清楚。