案例研究 · Aegis验证器

身份验证器如何
生成登录代码?

六位验证码每 30 秒变化一次。用 REA 在 Android APK 中找出计算方法,再重建一个可调节时钟的演示。

Aegis 3.4.3 · Android · 正式发布 APK ·6.9MB

在GitHub上查看Aegis

从 APK 还原可复现的验证码

  1. 01 · 定位功能3,077 个类 → 生成方法REA 搜索包,追踪显示和复制验证码时使用的代码。
  2. 02 · 阅读计算时间 + 账户密钥 → 六位数字REA 返回方法与默认值,助手解释它们如何配合。
  3. 03 · 重建并验证调整时钟,查看验证码。新的浏览器演示复现规则,再用公开对照用例检查。

问问你的编程助手

该应用程序显示更改的登录代码。 我们想找到 是什么让它改变 再现这种行为。

你的编程助手

用 REA 分析 aegis-v3.4.3.apk,找出六位登录验证码是怎么生成的,解释为什么它每 30 秒变化一次,再做一个可以调整时间的小演示。

这是用于下载的 APK 的示例 prompt,需要先配置 REA 的 Android 分析后端。获取 APK 和分析配置 ↓

试试代码生成器

应用把时间划分为每块 30 秒,再结合时间块编号和账户密钥生成验证码。同样的密钥和时间块,会得到同样的验证码。

将时钟移动60秒

  1. 时间59 秒数我们的可调节示例时钟
  2. 30秒区块时间块 1floor(59 / 30)
    30-59秒
  3. 生成的验证码287082帐户密钥+区块→六位数字

在59秒: 287082. 在60秒: 359152. 在一个块内移动会使代码保持不变。

密钥是固定的,公开的 RFC6238测试数据. 该演示实现了从APK恢复的计算。

用REA查找代码

  1. 将APK缩小到三个类

    编程助手→ REA
    inspect_android_package
    {"path": "aegis-v3.4.3.apk"}
    
    search_android_classes
    {"path": "aegis-v3.4.3.apk",
     "query": "Totp"}
    REA →编程助手 · 选定结果
    Package: com.beemdevelopment.aegis
    Version: 3.4.3
    Classes: 3,077
    
    Three matches:
      otp.TotpInfo
      ui.views.TotpProgressBar
      importers.TotpAuthenticatorImporter

    TotpInfo 是计算逻辑的候选方法;另外两个涉及进度条和导入的账户。TOTP 指基于时间的一次性密码。

  2. 将生成器连接到应用程序的显示器

    编程助手→ REA
    inspect_android_class
    {"path": "aegis-v3.4.3.apk",
     "class_name":
      "com.beemdevelopment.aegis.otp.TotpInfo"}
    
    trace_android_references
    {"path": "aegis-v3.4.3.apk",
     "class_name":
      "com.beemdevelopment.aegis.otp.OtpInfo",
     "method_name": "getOtp"}
    REA →编程助手·方法和调用者
    TotpInfo methods:
      getOtp()      · overload 0
      getOtp(long)  · overload 1
    
    OtpInfo.getOtp callers:
      ui.views.EntryHolder.getOtp
      ui.MainActivity.copyEntryCode

    REA 找到接受时间戳的方法重载,并定位基础方法的调用方。继续检查 EntryHolder.getOtp,就能看到显示逻辑调用了 TotpInfo.getOtp(timestamp)。

请求显示缩短的输入路径。 结果类名省略常用 com.beemdevelopment.aegis 前缀。

阅读计算

REA 返回时间转换、默认设置和生成方法。构造函数把间隔设为 30 秒、位数设为 6;接受时间戳的方法将秒数转换为时间块编号。

REA · 精选反编译Java
// TotpInfo defaults and clock
setPeriod(30);
getOtp(System.currentTimeMillis() / 1000);

// OtpInfo default algorithm and digits
this(bArr, "SHA1", 6);

// Block passed to the generator
(long) Math.floor(j / ((double) this._period))

// generateOTP → getHash
byte[] hash = getHash(bArr, str, j);

// getHash: account key + encoded block
Mac mac = Mac.getInstance(str);
mac.init(secretKeySpec);
return mac.doFinal(bArrArray);

// OTP.toString, case 0
this._code % ((int) Math.pow(10.0d, i))

// Keep leading zeroes
while (sb.length() < i) {
    sb.insert(0, "0");
}
可读摘要 · 编程助手解释
// Group time into 30-second blocks
seconds = current_time_ms / 1000
block = floor(seconds / 30)

// Generate a number from the key and block
hash = HMAC_SHA1(account_key,
                 big_endian_8_bytes(block))
number = select_31_bits(hash)

// Format the default six-digit code
code = number % 1_000_000
code = pad_with_zeroes(code, 6)

时间选择块。 秒30到59都给块1。 在60秒时,块变为2。

密钥也会影响结果。 生成函数把账户密钥和时间块组合起来。密钥不同的账户,在同一时间也可能显示不同验证码。

保留六位数字。 取除以 1,000,000 的余数,位数不足时在前面补零,因此验证码也可以以 0 开头。

这些节选来自多个检查过的方法,为便于显示增加了换行。右侧是解释性摘要。Aegis 源码采用 GPL-3.0 许可。

验证码生成函数内部

发布的 APK 将 generateOTP 放在 kotlin.ExceptionsKt 中。REA 从 TotpInfo 追踪调用,并返回这个函数体;原始源码把辅助函数命名为 HOTP。

REA · generateOTP 完整函数体
public static OTP generateOTP(byte[] bArr, String str,
        int i, long j)
        throws NoSuchAlgorithmException, InvalidKeyException {
    byte[] hash = getHash(bArr, str, j);
    int i2 = hash[hash.length - 1] & 15;
    return new OTP(
        (hash[i2 + 3] & 255)
        | ((hash[i2] & 127) << 24)
        | ((hash[i2 + 1] & 255) << 16)
        | ((hash[i2 + 2] & 255) << 8), i, 0);
}

哈希的最后一个字节决定读取位置。从这个位置读取四个字节,得到正的 31 位数,再格式化为指定的位数。构造函数的最后一个参数选择了编译过程引入的验证码格式化分支。

与匹配的发布源进行比较.

检查重建

我们将选定的 REA 返回的 Java 方法放进小型测试程序。它们的输出与独立重建的实现一致,共验证 133 个测试用例,包括时间块边界和以零开头的验证码。

示例时间 时间块 六位验证码
30秒 1 287082
59秒 1 287082
60秒 2 359152
1,111,111,109秒 37,037,036 081804

生成函数还符合 RFC 6238 中发布的六个 SHA-1 对照用例。可以用这些用例检查浏览器实现:

阅读演示的计算

参考案例使用八位数字。 检查也验证他们的六位数的结果。

验证运行提取的Java方法和新的演示。 对Android APK进行了静态检查。

亲自检查APK

先在 macOS 或 Linux 上设置 REA,再将这两个文件下载到同一文件夹。Windows 上可使用 WSL 等 Linux 环境。

Android分析需要完整的JDK17或更高版本。 选择它与 JAVA_HOME 或 PATH. 本案例使用了JDK21。

让 REA 使用分析后端配置文件,再检查 APK:

macOS/Linux
REA_JADX_MCP_JAR="$PWD/jadx-headless-mcp-0.7.1-all.jar" \
npx rea-agents@latest inspect-android-package \
  ./aegis-v3.4.3.apk --format json --full-output

助手可以通过 REA CLI 继续使用上方的 prompt。如果使用 MCP,将 REA_JADX_MCP_JAR 加入 REA 服务的环境配置,再重新连接。

下一个问题: 当帐户使用八位数字或不同的时间间隔时会发生什么?

资料来源

使用 REA 6.1.0 于 2026 年 10 月 9 日检查。反编译节选归属 Aegis;图示、可读摘要和浏览器重建为新增教学材料。

顶部