28

Java の Random 関数はシードを取り、「疑似乱数」のシーケンスを生成します。( で説明したアルゴリズムに基づいて実装されていますDonald Knuth, The Art of Computer Programming, Volume 3, Section 3.2.1.)が、この記事は技術的すぎて理解できません)

それの逆関数はありますか?つまり、一連の数字が与えられた場合、シードが何であるかを数学的に決定することは可能でしょうか? (つまり、総当たり攻撃は有効な方法としてカウントされません)

[編集] ここにはかなりの数のコメントがあるようです...探しているものを明確にしたいと思いました。

したがって、たとえば、関数y = f(x) = 3xには逆関数があり、これはy = g(x) = x/3です。

しかし、この関数には逆関数がありません。なぜなら (ここで完全な数学的証明を行うことはできますが、主な質問を脇道に逸らしたくありません)、直感的に言えば、そのようなz = f(x, y) = x * yのペアが複数存在するからです。(x, y)(x * y) == z

私の質問に戻りますが、関数が可逆ではないという場合は、その理由を説明してください。

(そして、本当に記事を読んで理解している人からの回答を得たいと思っています。「それは不可能です」などの回答は実際には役に立ちません)

4

3 に答える 3

28

の Oracle (旧姓 Sun) 実装について話している場合はjava.util.Random、十分な知識があれば可能です。

Random48 ビットのシードと線形合同ジェネレーターを使用します。これらは暗号的に安全なジェネレーターではありません。これは、状態のサイズが小さいため (ブルートフォース可能です!)、出力がそれほどランダムではないという事実です (多くのジェネレーターは、特定のビットで短いサイクル長を示します。つまり、これらのビットは、他のビットがランダムに見える場合)。

Randomのシード更新は次のとおりです。

nextseed = (seed * 0x5DEECE66DL + 0xBL) & ((1L << 48) - 1)

これは非常に単純な関数であり、計算によってシードのすべてのビットがわかっている場合は逆にすることができます。

seed = ((nextseed - 0xBL) * 0xdfe05bcb1365L) & ((1L << 48) - 1)

0x5DEECE66DL * 0xdfe05bcb1365L = 1mod 2 48以降。これにより、任意の時点での単一のシード値で、過去および将来のすべてのシードを回復できます。

Randomただし、シード全体を明らかにする関数はないため、少し賢くする必要があります。

さて、明らかに、48 ビットのシードでは、少なくとも 48 ビットの出力を観察する必要があります。そうしないと、操作する単射 (したがって可逆) 関数が明らかにありません。運が良かった: returnであるため、64 ビットの出力 (必要以上) が生成されnextLongます。((long)(next(32)) << 32) + next(32);実際、おそらくnextDouble(53 ビットを生成する) で間に合わせるか、他の関数を繰り返し呼び出すだけで済みます。これらの関数は、シードのサイズが限られているため、2 48を超える一意の値を出力できないことに注意してください (したがって、たとえば、決して生成されない 2 64 -2 48 longが存在します)。nextLong

を具体的に見てみましょうnextLong。とが両方とも 32 ビット量(a << 32) + bである数値を返します。が呼び出される前にシードにしましょう。次に、 をの上位 32 ビットとし、 をの上位32 ビットとします。と をそれぞれとの下位 16 ビットとする。absnextLongt = s * 0x5DEECE66DL + 0xBLatu = t * 0x5DEECE66DL + 0xBLbucdtu

cdは 16 ビットの量であるため、(1 つしか必要ないため) 力ずくで実行するだけで済むことに注意してください。2 16はわずか 65536 であり、コンピューターとしては非常に小さいため、かなり安価です。しかし、もう少し賢く、もっと速い方法がないか見てみましょう。

あります(b << 16) + d = ((a << 16) + c) * 0x5DEECE66DL + 11。したがって、いくつかの代数を実行する(b << 16) - 11 - (a << 16)*0x5DEECE66DL = c*0x5DEECE66DL - dと、mod 2 48が得られます。cdはどちらも 16 ビット量であるためc*0x5DEECE66DL、最大 51 ビットです。これは便利なことに、

(b << 16) - 11 - (a << 16)*0x5DEECE66DL + (k<<48)

は、多くても 6 に等しいものc*0x5DEECE66DL - dがあります (とkを計算するより洗練された方法がありますが、境界が非常に小さいため、ブルートフォースする方が簡単です)。cdk

残りの modが 16 ビット (再び mod 2 48k ) である値を否定する値が得られるまで、 のすべての可能な値をテストして、との両方の下位 16 ビットを回復します。その時点で、完全なシードがあるため、最初の式を使用して将来のシードを見つけるか、2 番目の式を使用して過去のシードを見つけることができます。0x5DEECE66DLtu

アプローチを示すコード:

import java.util.Random;

public class randhack {
    public static long calcSeed(long nextLong) {
        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;
                }
            }
        }
        throw new RuntimeException("Failed!!");
    }

    public static void main(String[] args) {
        Random r = new Random();
        long next = r.nextLong();
        System.out.println("Next long value: " + next);
        long seed = calcSeed(next);
        System.out.println("Seed " + seed);
        // setSeed mangles the input, so demangle it here to get the right output
        Random r2 = new Random((seed ^ 0x5DEECE66DL) & ((1L << 48)-1));
        System.out.println("Next long value from seed: " + r2.nextLong());
    }
}
于 2013-03-06T01:54:43.533 に答える
4

普段は記事をリンクするだけではないのですが…でも、誰かがこれを深く調べて投稿する価値があると思ったサイトを見つけました。http://jazzy.id.au/default/2010/09/20/cracking_random_number_generators_part_1.html

この方法でシードを計算できるようです。

seed = (seed * multiplier + addend) mod (2 ^ precision)

ここで、乗数は25214903917、加数は11、精度は48(ビット)です。1つの数字だけではシードが何であるかを計算することはできませんが、2つの数字で計算することはできます。

編集:nhahtdhが言ったように、彼が種の背後にある数学の詳細を掘り下げるパート2があります。

于 2013-03-05T23:51:15.103 に答える
3

によって生成された整数のシーケンスを逆にする実装を提示したいと思いますnextInt()

このプログラムは、 によって破棄された下位 16 ビットに対してブルート フォースを実行し、nextInt()James Roper のブログで提供されているアルゴリズムを使用して前のシードを見つけ、48 ビット シードの上位 32 ビットが前の数値と同じであることを確認します。前のシードを導出するには、少なくとも2 つの整数が必要です。それ以外の場合、前のシードには 2 16の可能性があり、少なくとももう 1 つの数が得られるまで、それらはすべて等しく有効です。

nextLong()これは簡単に拡張でき、生成方法により、シードの上位 32 ビットが 2 つあるため、シードを見つけるには1 つ の数値で十分です。longlong

SEED変数にシークレット シードとして設定した結果とは異なる場合があることに注意してください。シークレット シードとして設定した数値が 48 ビット (内部で乱数を生成するために使用されるビット数) を超える場合、64 ビットの上位 16 ビットがメソッドlongで削除されsetSeed()ます。このような場合、返される結果は最初に設定したものと同じではなく、下位 48 ビットが同じになる可能性があります。

以下のサンプル コードを可能にしたこのブログ記事の著者である James Roper に最大限の敬意を表したいと思います。

import java.util.Random;
import java.util.Arrays;

class TestRandomReverse {
  // 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;

  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;
  }

  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");
    }
  }

  private static long combine(int rand, int suffix) {
    return (unsignedIntToLong(rand) << 16) | (suffix & ((1L << 16) - 1));
  }

  private static long unsignedIntToLong(int num) {
    return num & ((1L << 32) - 1);
  }

  // This function finds 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 {
            System.out.println("Failed");
            break;
          }
        }
      }
    }

    return null;
  }

  private static long ADDEND = 0xBL;
  private static long MULTIPLIER = 0x5DEECE66DL;

  // Credit to James Roper
  // http://jazzy.id.au/default/2010/09/21/cracking_random_number_generators_part_2.html
  private static long getPreviousSeed(long currentSeed) {
    long seed = currentSeed;
    // reverse the addend from the seed
    seed -= ADDEND; // reverse the addend
    long result = 0;
    // iterate 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);
  }
}
于 2013-03-06T01:22:32.607 に答える