4

出于兴趣,我正在玩 C# 附带的 Random 类。我试图预测未来。由于它是伪随机的,因此必须有某种方法来预测数字。到目前为止,我能想到的唯一方法是蛮力(获取所有可能的种子并找到其中随机数的模式),但我相信它的处理能力太大,因为种子可以是 -2,147,483,647 到 2,147,483,647 之间的任何值。

到目前为止,我已经确定:

new Random() == new Random(Environment.TickCount)

并且这两个类将以相同的顺序提供相同的随机数。

是否有可能确定 Random 类实例的种子?如果在实例化 Random 类时知道 Environment.TickCount,则可以预测随机数,但我不知道是否可以做到。

4

2 回答 2

7

由于它是伪随机的,因此必须有某种方法来预测数字。

确实; 如果您知道内部状态(尤其是inext,inextpSeedArray- 总共 58int个值),并且您知道将按确切顺序请求的确切操作(例如,请求Next(), Next(), 与请求 ,非常NextBytes()不同, ) - 那么你可以做出完全准确的前向猜测。这就是种子 PRNG 的全部意义——它允许可重复的随机性,这在您需要能够重播事件的许多场景中很有用。Next()NextBytes()Next()

所以:我认为您永远无法取回原始种子,但要预测未来(而不是过去),您不需要种子 - 您只需要 58 个int值。

然而!任何对随机性很重要的东西都应该使用加密随机提供者——这些提供者是不可重复或不可猜测的。

例如:

static class Program {
    static Random Clone(this Random source)
    {
        var clone = new Random();
        var type = typeof(Random);
        var field = type.GetField("inext",
            BindingFlags.Instance | BindingFlags.NonPublic);
        field.SetValue(clone, field.GetValue(source));
        field = type.GetField("inextp",
            BindingFlags.Instance | BindingFlags.NonPublic);
        field.SetValue(clone, field.GetValue(source));
        field = type.GetField("SeedArray",
            BindingFlags.Instance | BindingFlags.NonPublic);
        int[] arr = (int[])field.GetValue(source);
        field.SetValue(clone, arr.Clone());
        return clone;
    }
    static void Main()
    {
        Random rand = new Random();
        var clone = rand.Clone();
        Console.WriteLine("My predictions:");
        Console.WriteLine(clone.Next());
        Console.WriteLine(clone.Next());
        Console.WriteLine(clone.Next());
        Console.WriteLine("Actual:");
        Console.WriteLine(rand.Next());
        Console.WriteLine(rand.Next());
        Console.WriteLine(rand.Next());
    }
}
于 2013-07-02T08:10:03.797 回答
3

的实现Random不会将种子存储在任何地方 - 它使用种子数组代替。所以不幸的是,没有办法确定种子。

带有种子的 Random 的构造函数如下所示:

public Random(int Seed)
{
    this.SeedArray = new int[0x38];
    int num4 = (Seed == 0x80000000) ? 0x7fffffff : Math.Abs(Seed);
    int num2 = 0x9a4ec86 - num4;
    this.SeedArray[0x37] = num2;
    int num3 = 0x1;
    for (int i = 0x1; i < 0x37; i++)
    {
        int index = (0x15 * i) % 0x37;
        this.SeedArray[index] = num3;
        num3 = num2 - num3;
        if (num3 < 0x0)
        {
            num3 += 0x7fffffff;
        }
        num2 = this.SeedArray[index];
    }
    for (int j = 0x1; j < 0x5; j++)
    {
        for (int k = 0x1; k < 0x38; k++)
        {
            this.SeedArray[k] -= this.SeedArray[0x1 + ((k + 0x1e) % 0x37)];
            if (this.SeedArray[k] < 0x0)
            {
                this.SeedArray[k] += 0x7fffffff;
            }
        }
    }
    this.inext = 0x0;
    this.inextp = 0x15;
    Seed = 0x1;
}

您可以看到它不会将种子存储在任何地方。

(奇怪的是,出于某种奇怪的原因,它在最后将 1 分配给了 Seed 变量——但这并不真正相关;它只是有点奇怪。)

您可以编写自己的包装类,Random为其保留种子值以供以后检索。实现只会记住Seed

public class MyRandom: Random
{
    public MyRandom() : this(Environment.TickCount)
    {
    }

    public MyRandom(int seed) : base(seed)
    {
        this.seed = seed;
    }

    public int Seed
    {
        get { return seed; }
    }

    private readonly int seed;
}
于 2013-07-02T08:07:55.330 回答