An easy-to-use Python script to sync up with the state of a java.util.Random
generator after supplying a few samples. The internal seed has 48 bits, and every sample you give to the program will give some amount of bits of information. Here is a table to get an idea of how many samples you should provide per amount of bits in your input number:
bits | bound | samples (±) |
---|---|---|
4 | 16 | 25 |
6 | 64 | 12 |
8 | 256 | 8 |
16 | 65536 | 4 |
32 | 4294967296 | 2 |
It tries to recover the state from numbers generated using the Linear Congruential Generator (LCG) in Java. For example:
Random random = new Random();
int sample = random.nextInt(256); // Single 8-bit number
int[] samples = random.ints(8, 0, 256).toArray(); // 8x 8-bit numbers
Recovering the state from nextInt()
or nextLong()
calls without arguments is trivial, as they give 32 bits of information in one number, while the whole seed is only 48 bits. Then only 16 bits have to be brute-forced.
It gets harder when numbers are truncated meaning only 8 bits are shown per number for example (0-255). The program is meant for these types of situations and can recover the state perfectly from only a few samples, cloning the generator and allowing you to generate the future values beforehand.
Warning: This script currently only works for numbers generated with an upper bound of a power of 2. This means a number generated like
random.nextInt(256)
will work, but something likerandom.nextInt(200)
likely won't. This is because, in the case of not having a power of 2, the LCG might generate multiple numbers per call, giving us an unknown amount of missing numbers in the samples.
Random random = new Random(1337);
int[] samples = random.ints(8, 0, 256).toArray(); // Generate 8x 8-bit numbers to give the program
System.out.println(samples); // { 168, 44, 176, 223, 226, 247, 230, 207 }
int[] secrets = random.ints(8, 0, 256).toArray(); // Generate 8 more secret numbers
System.out.println(secrets); // { 44, 164, 241, 235, 37, 5, 81, 252 }
Now we will feed the samples
into the Python program. The numbers are generated from 0-255, which is 8 bits:
$ python3 truncated_java_random.py
Known bits: 8
Input: 168, 44, 176, 223, 226, 247, 230, 207
States found: [185753720734415, 48973695446062, 194004564009889, 246107133972888, 248619153362371, 272076196875794, 252907395951029, 228694819430428]
Guesses: [44, 164, 241, 235, 37, 5, 81, 252]
Tip: To get more control over what numbers are guessed after the state has been cracked, you can use the
java-random
library to clone the generator by setting its internal state to one of the found states, and then calling the function on it to extract the numbers you need.