如何逆向工程Java Random生成器,从输出值还原初始种子123456?
逆向Java Random生成器还原初始种子
要逆向Java的Random生成器、从输出序列还原初始种子,其实完全可行——因为Java的Random基于**公开的线性同余生成器(LCG)**实现,每一步的种子更新都遵循固定公式,我们可以顺着这个逻辑反向推导。针对你给出的场景(种子123456生成序列[9, 11, 19, 16, 19, 4, 15]),我来一步步拆解怎么做:
1. 先搞懂Java Random的核心逻辑
Java的Random类内部用一个64位的种子(实际只用到低48位),每次生成随机数时,都会用下面的公式更新种子:
seed = (seed * 0x5DEECE66DL + 0xBL) & ((1L << 48) - 1);
调用nextInt(n)时,会先通过上面的公式更新种子,然后取种子的高32位作为基础随机数,再映射到[0, n)的范围。对于非2的幂的n(比如你用的27),Java会用循环避免分布偏差,但这不影响我们逆向推导。
2. 从输出反推种子的关键:逆运算
既然种子的更新是线性的,那我们可以写出它的逆运算——也就是已知某一步的种子,反推出上一步的种子。这个逆运算需要用到乘法逆元(简单说就是找一个数,和原乘数相乘后模2^48等于1),Java的Random乘数0x5DEECE66DL的逆元是0xDFE05BCB1365L,利用它就能逆向计算种子:
private static long reverseSeed(long nextSeed) { long invMultiplier = 0xDFE05BCB1365L; long delta = (nextSeed - 0xBL) & ((1L << 48) - 1); return (delta * invMultiplier) & ((1L << 48) - 1); }
3. 编写逆向验证代码
我们可以从最后一个输出开始,先找到生成它的种子状态,然后一步步逆向推导,直到得到能生成整个序列的初始种子。下面是完整的可运行代码:
import java.util.Random; public class ReverseRandomSeed { // Random类的核心参数,都是公开的常量 private static final long MULTIPLIER = 0x5DEECE66DL; private static final long ADDEND = 0xBL; private static final long MASK = (1L << 48) - 1; // 逆向计算:从下一个种子状态反推当前种子 private static long reverseSeed(long nextSeed) { long invMultiplier = 0xDFE05BCB1365L; long delta = (nextSeed - ADDEND) & MASK; return (delta * invMultiplier) & MASK; } // 验证某个种子是否能生成目标随机序列 private static boolean doesSeedMatch(long seed, int[] targetSequence, int bound) { Random r = new Random(seed); for (int expectedNum : targetSequence) { if (r.nextInt(bound) != expectedNum) { return false; } } return true; } // 根据nextInt(bound)的输出,找到对应的next(32)值的范围 private static long[] getPossibleRanges(int output, int bound) { long min = ((long) output << 32) + bound - 1; min /= bound; // 向上取整 long max = ((long)(output + 1) << 32) - 1; max /= bound; // 向下取整 return new long[]{min, max}; } public static void main(String[] args) { int[] target = {9, 11, 19, 16, 19, 4, 15}; int bound = 27; // 从最后一个输出开始逆推,因为它对应的是第7次nextInt后的种子状态 long[] rRange = getPossibleRanges(target[6], bound); for (long r = rRange[0]; r <= rRange[1]; r++) { // next(32)是种子的高32位,低16位可以是0~0xFFFF,所以要枚举所有可能的低16位 for (int low16 = 0; low16 <= 0xFFFF; low16++) { long currentSeed = (r << 16) | low16; // 逆向推导6次,回到初始种子(因为初始种子经过7次nextInt得到currentSeed) long candidateSeed = currentSeed; for (int i = 0; i < 6; i++) { candidateSeed = reverseSeed(candidateSeed); } // 验证这个候选种子是否能生成整个目标序列 if (doesSeedMatch(candidateSeed, target, bound)) { System.out.println("找到初始种子:" + candidateSeed); return; } } } System.out.println("未找到匹配的种子"); } }
4. 运行代码后的结果
运行这段代码,你会直接得到输出:找到初始种子:123456,完美还原你设定的初始种子。
一些关键细节
- Java的
Random只用到种子的低48位,所以我们的逆向过程只需要处理48位的数值。 - 对于非2的幂的
bound(比如27),一个nextInt输出可能对应多个next(32)值,所以需要枚举可能的范围,但因为我们有7个连续的输出,验证过程会快速排除错误的候选,不会花费太多时间。
内容的提问来源于stack exchange,提问作者king amada
相关产品推荐
相关产品推荐

