如何编写一个 GitHub 二步验证客户端?(附仓库地址及apk下载链接)

如何编写一个 GitHub 二步验证客户端?(附仓库地址及apk下载链接)

一、原理介绍

GitHub 二步验证使用 TOTP(Time-based One-Time Password)算法,基于 RFC 6238 标准。

简单来说:

  1. 开启二步验证时,GitHub 生成一个密钥,通过二维码展示
  2. 用户用客户端扫描二维码,保存密钥
  3. 登录时,客户端用密钥 + 当前时间生成 6 位验证码
  4. GitHub 用同样的算法验证

验证码每 30 秒刷新一次,时间窗口内的验证码是固定的。

二、整体流程

flowchart TD A[用户开启 GitHub 2FA] --> B[GitHub 生成二维码] B --> C[客户端扫描二维码] C --> D[解析 otpauth:// URL] D --> E[提取密钥 Secret] E --> F[安全存储到本地] G[用户登录 GitHub] --> H[要求输入验证码] H --> I[打开客户端] I --> J[从本地读取密钥] J --> K[计算当前时间窗口] K --> L[HMAC-SHA1 计算哈希] L --> M[动态截取生成 6 位码] M --> N[显示验证码] N --> O[用户输入验证]

三、项目结构

bash 复制代码
lib/
├── main.dart                 # 应用入口
├── models/
│   └── account.dart          # 账户数据模型
├── services/
│   ├── totp_service.dart     # TOTP 核心算法
│   ├── storage_service.dart  # 安全存储
│   └── account_provider.dart # 状态管理
├── screens/
│   ├── home_page.dart        # 主页面(显示验证码)
│   ├── scan_qr_page.dart     # 扫描二维码页面
│   └── add_account_page.dart # 添加账户页面
└── widgets/
    └── code_card.dart        # 验证码卡片组件

四、依赖配置

yaml 复制代码
dependencies:
  flutter:
    sdk: flutter

  # TOTP 算法实现
  otp: ^3.1.4

  # Base32 编解码
  base32: ^2.2.0

  # 二维码扫描
  mobile_scanner: ^6.0.2

  # 安全存储(iOS Keychain / Android 加密存储)
  flutter_secure_storage: ^9.2.2

  # 状态管理
  provider: ^6.1.2

  # 唯一 ID 生成
  uuid: ^4.5.1

五、核心实现

5.1 数据模型

首先定义账户模型,存储每个 2FA 账户的信息:

dart 复制代码
class Account {
  final String id;          // 唯一标识
  final String name;        // 账户名称,如 "GitHub"
  final String email;       // 用户标识
  final String secret;      // 密钥(Base32 编码)
  final int digits;         // 验证码位数,默认 6
  final int interval;       // 时间间隔,默认 30 秒
  final String algorithm;   // 算法:SHA1/SHA256/SHA512
  final String? issuer;     // 发行者

  Account({
    String? id,
    required this.name,
    required this.email,
    required this.secret,
    this.digits = 6,
    this.interval = 30,
    this.algorithm = 'SHA1',
    this.issuer,
  });
}

5.2 二维码扫描与解析

GitHub 的二维码内容格式:

ruby 复制代码
otpauth://totp/GitHub:username?secret=JBSWY3DPEHPK3PXP&issuer=GitHub

解析流程:

flowchart LR A[扫描二维码] --> B[获取 URL 字符串] B --> C{是否 otpauth:// 协议?} C -->|否| D[返回错误] C -->|是| E[分离路径和参数] E --> F[解析账户名] E --> G[解析密钥 secret] E --> H[解析其他参数] F --> I[返回 OtpAuthData] G --> I H --> I

代码实现:

dart 复制代码
static OtpAuthData? parseOtpAuthUrl(String url) {
  // 检查协议
  if (!url.startsWith('otpauth://totp/')) {
    return null;
  }

  // 分离路径和查询参数
  final uriString = url.replaceFirst('otpauth://totp/', '');
  final parts = uriString.split('?');

  // 解析标签(issuer:email)
  final label = Uri.decodeComponent(parts[0]);
  String name, email, issuer;
  if (label.contains(':')) {
    final labelParts = label.split(':');
    issuer = labelParts[0];
    email = labelParts[1];
    name = issuer;
  } else {
    email = label;
    name = label;
  }

  // 解析查询参数
  final queryParams = Uri.splitQueryString(parts[1]);
  final secret = queryParams['secret'] ?? '';
  final digits = int.parse(queryParams['digits'] ?? '6');
  final interval = int.parse(queryParams['period'] ?? '30');
  final algorithm = queryParams['algorithm'] ?? 'SHA1';

  return OtpAuthData(
    name: name,
    email: email,
    secret: secret.toUpperCase(),
    digits: digits,
    interval: interval,
    algorithm: algorithm,
  );
}

5.3 TOTP 验证码生成

这是整个项目的核心,TOTP 算法流程:

flowchart TD A[获取当前时间戳] --> B[除以 30 得到时间窗口 T] B --> C[T 转换为 8 字节大端] C --> D[Base32 解码密钥] D --> E[HMAC-SHA1 计算] E --> F[得到 20 字节哈希] F --> G[取最后 1 字节的低 4 位作为 offset] G --> H[从 offset 开始取 4 字节] H --> I[去掉最高位符号位] I --> J[对 10^6 取模] J --> K[得到 6 位验证码]

代码实现:

dart 复制代码
static String generateCode({
  required String secret,
  int digits = 6,
  int interval = 30,
  String algorithm = 'SHA1',
}) {
  // 选择算法
  Algorithm algo;
  switch (algorithm.toUpperCase()) {
    case 'SHA256':
      algo = Algorithm.SHA256;
      break;
    case 'SHA512':
      algo = Algorithm.SHA512;
      break;
    default:
      algo = Algorithm.SHA1;
  }

  // 生成验证码
  final code = OTP.generateTOTPCode(
    secret,
    DateTime.now().millisecondsSinceEpoch,
    length: digits,
    interval: interval,
    algorithm: algo,
    isGoogle: true,  // ⚠️ 必须设置!
  );

  return code.toString().padLeft(digits, '0');
}

5.4 一个巨坑!

开发过程中遇到一个致命问题:验证码始终不对

排查后发现 otp 包的实现有问题:

dart 复制代码
// otp 包源码
var secretList = Uint8List.fromList(utf8.encode(secret));  // 默认行为
if (isGoogle) {
  secretList = base32.decode(secret.toUpperCase());  // 正确行为
}

问题:默认把密钥当 UTF-8 字符串编码,而不是 Base32 解码!

解决 :必须设置 isGoogle: true,才会正确解码 Base32 密钥。

GitHub/Google Authenticator 的密钥都是 Base32 编码的,不设置这个参数,验证码永远错误。

5.5 安全存储

密钥必须安全存储,不能明文保存:

dart 复制代码
class StorageService {
  final FlutterSecureStorage _storage = FlutterSecureStorage(
    aOptions: AndroidOptions(encryptedSharedPreferences: true),
  );

  // 保存账户列表
  Future<void> saveAccounts(List<Account> accounts) async {
    final jsonList = accounts.map((a) => a.toJson()).toList();
    await _storage.write(
      key: 'accounts',
      value: jsonEncode(jsonList),
    );
  }

  // 加载账户列表
  Future<List<Account>> loadAccounts() async {
    final jsonString = await _storage.read(key: 'accounts');
    if (jsonString == null) return [];

    final jsonList = jsonDecode(jsonString) as List;
    return jsonList.map((json) => Account.fromJson(json)).toList();
  }
}

存储方式:

平台 存储方式
iOS Keychain
Android EncryptedSharedPreferences
macOS Keychain
Windows Credential Manager

5.6 状态管理与 UI 刷新

验证码每 30 秒刷新一次,需要定时更新 UI:

dart 复制代码
class HomePage extends StatefulWidget {
  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  Timer? _refreshTimer;

  @override
  void initState() {
    super.initState();
    // 每秒刷新一次
    _refreshTimer = Timer.periodic(Duration(seconds: 1), (_) {
      setState(() {});
    });
  }

  @override
  void dispose() {
    _refreshTimer?.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    // 显示验证码和倒计时
  }
}

六、完整项目

仓库地址:github.com/FuShang114/...

下载地址:github.com/FuShang114/...

七、构建方法

bash 复制代码
# 克隆仓库
git clone https://github.com/FuShang114/Free2FA.git
cd Free2FA

# 安装依赖
flutter pub get

# 构建 APK
flutter build apk --release

APK 输出路径:build/app/outputs/flutter-apk/app-release.apk

八、总结

开发一个 TOTP 客户端的关键点:

  1. 理解 TOTP 算法 - 基于 HMAC-SHA1,时间窗口 30 秒
  2. 正确解析二维码 - otpauth:// 协议格式
  3. Base32 解码密钥 - otp 包必须设置 isGoogle: true
  4. 安全存储 - 使用平台级加密存储
  5. 定时刷新 - 验证码每 30 秒更新

希望这篇文章对你有帮助,欢迎 Star!

相关推荐
MonkeyKing2 小时前
Flutter 自制轻量级状态管理方案
flutter
liulian09162 小时前
【Flutter for OpenHarmony 第三方库】Flutter for OpenHarmony 引导页设计与新用户体验优化实现指南
flutter·华为·学习方法·harmonyos·ux
Lanren的编程日记2 小时前
Flutter 鸿蒙应用内存管理优化实战:对象池+智能缓存+泄漏检测,全方位提升应用稳定性
flutter·缓存·华为·harmonyos
liulian09163 小时前
【Flutter for OpenHarmony 第三方库】Flutter for OpenHarmony 实时聊天功能适配与实现指南
flutter·华为·学习方法·harmonyos
Lanren的编程日记3 小时前
Flutter 鸿蒙应用多设备同步功能实战:完整同步协议+冲突解决机制,打造跨设备一致体验
flutter·华为·harmonyos
张风捷特烈5 小时前
状态管理大乱斗#03 | Provider 源码全面评析
android·前端·flutter
Hello__777718 小时前
开源鸿蒙 Flutter 实战|消息通知功能完整实现
flutter·开源·harmonyos
Hello__777719 小时前
开源鸿蒙 Flutter 实战|仓库评论与点赞功能完整实现
flutter·开源·harmonyos
一个假的前端男1 天前
Flutter 实现 BLE 设备 WiFi 配网流程实践
开发语言·flutter