实现随机数生成反演方法的Java程序
在这里,我们将介绍Java随机数生成的反转方法。所以基本上我们都很开心我会来说明两种方法,内容如下:
- 打乱数组中的元素
- 使用 Collection.shuffle() 方法
这里的重要方法是 setSeed()。它用于通过使用单个长种子的随机数生成器的 Random 类集的setSeed() 方法设置种子。
句法:
setSeed()
示例 1:
Java
// Java Program to implement inversion for random number
// generation
// Importing Random class from java.util package
import java.util.Random;
// Main class
class GFG {
// Method 1
// To calculate seed value
public static long calcSeed(long nextLong)
{
// Declaring and initializing variables
// Custom initialization
final long x = 0x5DEECE66DL;
final long xinv = 0xdfe05bcb1365L;
final long y = 0xBL;
final long mask = ((1L << 48) - 1);
long a = nextLong >>> 32;
long b = nextLong & ((1L << 32) - 1);
if ((b & 0x80000000) != 0)
a++;
// b had a sign bit, so we need to restore a
long q = ((b << 16) - y - (a << 16) * x) & mask;
for (long k = 0; k <= 5; k++) {
long rem = (x - (q + (k << 48))) % x;
long d = (rem + x) % x; // force positive
if (d < 65536) {
long c = ((q + d) * xinv) & mask;
if (c < 65536) {
return ((((a << 16) + c) - y) * xinv)
& mask;
}
}
}
// Throws keyword pops up the message
// as exception is encountered during runtime
throw new RuntimeException("Failed!!");
}
// Method 2
// Main driver method
public static void main(String[] args)
{
// Setting a random number in main() method by
// creating an object of Random class
Random r = new Random();
// Getting the next random number
long next = r.nextLong();
// Print and display the next long value
System.out.println("Next long value: " + next);
// Calling method 1
long seed = calcSeed(next);
// Print and display the ssed value
System.out.println("Seed " + seed);
// setSeed mangles the input,
// so demangling here to get the right output by
// creating another object of Random class
Random r2 = new Random((seed ^ 0x5DEECE66DL)
& ((1L << 48) - 1));
// Printing the next ssed value
// using nextLong() method
System.out.println("Next long value from seed: "
+ r2.nextLong());
}
}
Java
// Java Program to Implement Inversion Method for
// Random Number Generation
// Importing required classes
import java.util.Arrays;
import java.util.Random;
import javax.sql.RowSetEvent;
import javax.sql.RowSetListener;
import javax.sql.rowset.JdbcRowSet;
import javax.sql.rowset.RowSetProvider;
// Main class
// To test random reverse
class GFG {
// The secret seed that we want to find
private static long SEED = 782634283105L;
// Number of random numbers to be generated
private static int NUM_GEN = 5;
// Method 1
private static int[] genNum(long seed)
{
Random rand = new Random(seed);
int arr[] = new int[NUM_GEN];
for (int i = 0; i < arr.length; i++) {
arr[i] = rand.nextInt();
}
return arr;
}
// Method 2
// Main driver method
public static void main(String args[])
{
int arr[] = genNum(SEED);
System.out.println(Arrays.toString(arr));
Long result = reverse(arr);
if (result != null) {
System.out.println(
Arrays.toString(genNum(result)));
}
else {
System.out.println("Seed not found");
}
}
// Method 3
private static long combine(int rand, int suffix)
{
return (unsignedIntToLong(rand) << 16)
| (suffix & ((1L << 16) - 1));
}
// Method 4
private static long unsignedIntToLong(int num)
{
return num & ((1L << 32) - 1);
}
// Method 5
// To find the seed of a sequence of
// integer, generated by nextInt() Can be easily
// modified to find the seed of a sequence of long,
// generated by nextLong()
private static Long reverse(int arr[])
{
// Need at least 2 numbers.
assert (arr.length > 1);
int end = arr.length - 1;
// Brute force lower 16 bits, then compare
// upper 32 bit of the previous seed generated
// to the previous number.
for (int i = 0; i < (1 << 16); i++) {
long candidateSeed = combine(arr[end], i);
long previousSeed
= getPreviousSeed(candidateSeed);
if ((previousSeed >>> 16)
== unsignedIntToLong(arr[end - 1])) {
System.out.println("Testing seed: "
+ previousSeed + " --> "
+ candidateSeed);
for (int j = end - 1; j >= 0; j--) {
candidateSeed = previousSeed;
previousSeed
= getPreviousSeed(candidateSeed);
if (j > 0
&& (previousSeed >>> 16)
== unsignedIntToLong(
arr[j - 1])) {
System.out.println(
"Verifying: " + previousSeed
+ " --> " + candidateSeed);
}
else if (j == 0) {
// The XOR is done when the seed is
// set, need to reverse it
System.out.println(
"Seed found: "
+ (previousSeed ^ MULTIPLIER));
return previousSeed ^ MULTIPLIER;
}
else {
// Print statement
System.out.println("Failed");
// break keyword
break;
}
}
}
}
return null;
}
private static long ADDEND = 0xBL;
private static long MULTIPLIER = 0x5DEECE66DL;
// Method 6
private static long getPreviousSeed(long currentSeed)
{
long seed = currentSeed;
// Reverse the addend from the seed
// Reversing the addend
seed -= ADDEND;
long result = 0;
// Iterating through the seeds bits
for (int i = 0; i < 48; i++) {
long mask = 1L << i;
// find the next bit
long bit = seed & mask;
// add it to the result
result |= bit;
if (bit == mask) {
// if the bit was 1, subtract its effects
// from the seed
seed -= MULTIPLIER << i;
}
}
return result & ((1L << 48) - 1);
}
}
Next long value: 3738641717178320652
Seed 158514749661962
Next long value from seed: 3738641717178320652
输出说明:
Random 使用 48 位种子和线性同余生成器。这些不是加密安全的生成器,因为状态大小很小,而且输出不是那么随机的事实(许多生成器在某些位中会表现出很小的循环长度,这意味着即使其他位也可以很容易地预测这些位看起来是随机的)。
Random的种子更新如下:
nextseed = (seed * 0x5DEECE66DL + 0xBL) & ((1L << 48) - 1)
这是一个非常简单的函数,如果您通过计算知道种子的所有位,则可以将其反转
seed = ((nextseed - 0xBL) * 0xdfe05bcb1365L) & ((1L << 48) - 1)
由于 0x5DEECE66DL * 0xdfe05bcb1365L = 1 mod 248。这样,任何时间点的单个种子值都足以恢复所有过去和未来的种子。
然而,Random 没有显示整个种子的函数,所以我们必须有点聪明。
现在,很明显,对于 48 位种子,您必须至少观察 48 位输出,否则您显然没有单射(因此可逆)函数可以使用。我们很幸运:nextLong 返回 ((long)(next(32)) << 32) + next(32);,所以它产生了 64 位的输出(比我们需要的多)。实际上,我们可能可以使用 nextDouble (产生 53 位),或者只是重复调用任何其他函数。请注意,由于种子的大小有限,这些函数不能输出超过 248 个唯一值(因此,例如,nextLong 永远不会产生 264-248 个 long)。
我们具体来看nextLong。它返回一个数字 (a << 32) + b,其中 a 和 b 都是 32 位数量。让 s 成为调用 nextLong 之前的种子。然后,让 t = s * 0x5DEECE66DL + 0xBL,所以 a 是 t 的高 32 位,让 u = t * 0x5DEECE66DL + 0xBL,所以 b 是 u 的高 32 位。设 c 和 d 分别是 t 和 u 的低 16 位。
注意,因为c和d是16位的量,我们就可以蛮力-迫使他们(因为我们只需要一个),并用它做。这非常便宜,因为 216 仅为 65536 — 对于计算机来说很小。但是让我们更聪明一点,看看是否有更快的方法。
我们有 (b << 16) + d = ((a << 16) + c) * 0x5DEECE66DL + 11。因此,做一些代数,我们得到 (b << 16) – 11 – (a << 16)* 0x5DEECE66DL = c*0x5DEECE66DL – d, mod 248。因为 c 和 d 都是 16 位的量,所以 c*0x5DEECE66DL 最多有 51 位。这很有用地意味着
(b << 16) - 11 - (a << 16)*0x5DEECE66DL + (k<<48)
等于 c*0x5DEECE66DL – d 对于某些 k 至多 6。(有更复杂的方法来计算 c 和 d,但由于 k 上的界限非常小,因此更容易暴力破解)。
我们可以测试 k 的所有可能值,直到我们得到一个取反余数 mod 0x5DEECE66DL 为 16 位的值(再次 mod 248),这样我们就可以恢复 t 和 u 的低 16 位。那时,我们有一个完整的种子,所以我们可以使用第一个方程找到未来的种子,或者使用第二个方程找到过去的种子。如下面的例子所示:
示例 2:
Java
// Java Program to Implement Inversion Method for
// Random Number Generation
// Importing required classes
import java.util.Arrays;
import java.util.Random;
import javax.sql.RowSetEvent;
import javax.sql.RowSetListener;
import javax.sql.rowset.JdbcRowSet;
import javax.sql.rowset.RowSetProvider;
// Main class
// To test random reverse
class GFG {
// The secret seed that we want to find
private static long SEED = 782634283105L;
// Number of random numbers to be generated
private static int NUM_GEN = 5;
// Method 1
private static int[] genNum(long seed)
{
Random rand = new Random(seed);
int arr[] = new int[NUM_GEN];
for (int i = 0; i < arr.length; i++) {
arr[i] = rand.nextInt();
}
return arr;
}
// Method 2
// Main driver method
public static void main(String args[])
{
int arr[] = genNum(SEED);
System.out.println(Arrays.toString(arr));
Long result = reverse(arr);
if (result != null) {
System.out.println(
Arrays.toString(genNum(result)));
}
else {
System.out.println("Seed not found");
}
}
// Method 3
private static long combine(int rand, int suffix)
{
return (unsignedIntToLong(rand) << 16)
| (suffix & ((1L << 16) - 1));
}
// Method 4
private static long unsignedIntToLong(int num)
{
return num & ((1L << 32) - 1);
}
// Method 5
// To find the seed of a sequence of
// integer, generated by nextInt() Can be easily
// modified to find the seed of a sequence of long,
// generated by nextLong()
private static Long reverse(int arr[])
{
// Need at least 2 numbers.
assert (arr.length > 1);
int end = arr.length - 1;
// Brute force lower 16 bits, then compare
// upper 32 bit of the previous seed generated
// to the previous number.
for (int i = 0; i < (1 << 16); i++) {
long candidateSeed = combine(arr[end], i);
long previousSeed
= getPreviousSeed(candidateSeed);
if ((previousSeed >>> 16)
== unsignedIntToLong(arr[end - 1])) {
System.out.println("Testing seed: "
+ previousSeed + " --> "
+ candidateSeed);
for (int j = end - 1; j >= 0; j--) {
candidateSeed = previousSeed;
previousSeed
= getPreviousSeed(candidateSeed);
if (j > 0
&& (previousSeed >>> 16)
== unsignedIntToLong(
arr[j - 1])) {
System.out.println(
"Verifying: " + previousSeed
+ " --> " + candidateSeed);
}
else if (j == 0) {
// The XOR is done when the seed is
// set, need to reverse it
System.out.println(
"Seed found: "
+ (previousSeed ^ MULTIPLIER));
return previousSeed ^ MULTIPLIER;
}
else {
// Print statement
System.out.println("Failed");
// break keyword
break;
}
}
}
}
return null;
}
private static long ADDEND = 0xBL;
private static long MULTIPLIER = 0x5DEECE66DL;
// Method 6
private static long getPreviousSeed(long currentSeed)
{
long seed = currentSeed;
// Reverse the addend from the seed
// Reversing the addend
seed -= ADDEND;
long result = 0;
// Iterating through the seeds bits
for (int i = 0; i < 48; i++) {
long mask = 1L << i;
// find the next bit
long bit = seed & mask;
// add it to the result
result |= bit;
if (bit == mask) {
// if the bit was 1, subtract its effects
// from the seed
seed -= MULTIPLIER << i;
}
}
return result & ((1L << 48) - 1);
}
}
输出:
[826100673, 702667566, 238146028, -1525439028, -133372456]
Testing seed: 181503804585936 --> 272734279476123
Verifying: 15607138131897 --> 181503804585936
Verifying: 46050021665190 --> 15607138131897
Verifying: 54139333749543 --> 46050021665190
Seed found: 782634283105
[826100673, 702667566, 238146028, -1525439028, -133372456]
输出说明:程序会对下16位进行蛮力
- -bit 被 nextInt() 丢弃,使用 James Roper 在博客中提供的算法找到前一个种子,然后检查 48 位种子的高 32 位是否与前一个数字相同。我们至少需要 2 个整数来推导前一个种子。否则,前一个种子将有 216 种可能性,并且所有这些都同样有效,直到我们至少再有一个数字。
- 它可以很容易地扩展到 nextLong() ,并且 1 个 long 数字足以找到种子,因为由于它的生成方式,我们在一个 long 中有 2 个上 32 位的种子。
Note: There are cases where the result is not the same as what you set as secret seed in the SEED variable. If the number you set as secret seed occupies more than 48-bit (which is the number of bits used for generating random numbers internally), then the upper 16 bits of 64 bit of long will be removed in the setSeed() method. In such cases, the result returned will not be the same as what you have set initially, it is likely that the lower 48-bit will be the same.