You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何逆向工程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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 06:57:49