问问你的编程助手
该应用程序显示更改的登录代码。 我们想找到 是什么让它改变 再现这种行为。
用 REA 分析 aegis-v3.4.3.apk,找出六位登录验证码是怎么生成的,解释为什么它每 30 秒变化一次,再做一个可以调整时间的小演示。
这是用于下载的 APK 的示例 prompt,需要先配置 REA 的 Android 分析后端。获取 APK 和分析配置 ↓
试试代码生成器
应用把时间划分为每块 30 秒,再结合时间块编号和账户密钥生成验证码。同样的密钥和时间块,会得到同样的验证码。
将时钟移动60秒
- 时间 秒数我们的可调节示例时钟
-
30秒区块时间块
floor(59 / 30)
30-59秒 - 生成的验证码帐户密钥+区块→六位数字
在59秒: 287082. 在60秒: 359152. 在一个块内移动会使代码保持不变。
用REA查找代码
-
将APK缩小到三个类
编程助手→ REAinspect_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.TotpAuthenticatorImporterTotpInfo是计算逻辑的候选方法;另外两个涉及进度条和导入的账户。TOTP 指基于时间的一次性密码。 -
将生成器连接到应用程序的显示器
编程助手→ REAinspect_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.copyEntryCodeREA 找到接受时间戳的方法重载,并定位基础方法的调用方。继续检查
EntryHolder.getOtp,就能看到显示逻辑调用了TotpInfo.getOtp(timestamp)。
请求显示缩短的输入路径。 结果类名省略常用 com.beemdevelopment.aegis 前缀。
阅读计算
REA 返回时间转换、默认设置和生成方法。构造函数把间隔设为 30 秒、位数设为 6;接受时间戳的方法将秒数转换为时间块编号。
// 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。
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:
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;图示、可读摘要和浏览器重建为新增教学材料。