Implementation change to .NET's Random()

2020-03-08 09:01发布

问题:

I am migrating a method that is used for decoding from .NET Framework 1.1 to .NET Framework 4. I noticed that implementation of Random changed. So given the same seed, Random.NextBytes returns different result.

So if I run the following code.

byte[] bytes = new byte[4];
System.Random random = new System.Random(50);
random.NextBytes(bytes);

for(int i=0; i< bytes.Length; i++)
{
  Console.WriteLine("bytes[" + i + "] = " + bytes[i]);
}

Under .NET Framework 1.1 it returns:

bytes[0] = 216
bytes[1] = 124
bytes[2] = 183
bytes[3] =  58

Under .NET framework 4 it returns:

bytes[0] = 154
bytes[1] =  49
bytes[2] = 183
bytes[3] =  48

What is the best way to resolve this problem?

回答1:

You can just use Reflector to copy the Random class from the 1.1 mscorlib.

public class Random1_1
{
    // Fields
    private int inext;
    private int inextp;
    private const int MBIG = 0x7fffffff;
    private const int MSEED = 0x9a4ec86;
    private const int MZ = 0x0;
    private int[] SeedArray;

    // Methods
    public Random1_1()
        : this(Environment.TickCount)
    {
    }

    public Random1_1(int Seed)
    {
        this.SeedArray = new int[0x38];
        int num2 = 0x9a4ec86 - Math.Abs(Seed);
        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;
    }

    public virtual int Next()
    {
        return (int)(this.Sample() * 2147483647.0);
    }

    public virtual int Next(int maxValue)
    {
        if (maxValue < 0x0)
        {
            throw new ArgumentOutOfRangeException("maxValue");
        }
        return (int)(this.Sample() * maxValue);
    }

    public virtual int Next(int minValue, int maxValue)
    {
        if (minValue > maxValue)
        {
            throw new ArgumentOutOfRangeException("minValue");
        }
        int num = maxValue - minValue;
        if (num < 0x0)
        {
            long num2 = maxValue - minValue;
            return (((int)((long)(this.Sample() * num2))) + minValue);
        }
        return (((int)(this.Sample() * num)) + minValue);
    }

    public virtual void NextBytes(byte[] buffer)
    {
        if (buffer == null)
        {
            throw new ArgumentNullException("buffer");
        }
        for (int i = 0x0; i < buffer.Length; i++)
        {
            buffer[i] = (byte)(this.Sample() * 256.0);
        }
    }

    public virtual double NextDouble()
    {
        return this.Sample();
    }

    protected virtual double Sample()
    {
        int inext = this.inext;
        int inextp = this.inextp;
        if (++inext >= 0x38)
        {
            inext = 0x1;
        }
        if (++inextp >= 0x38)
        {
            inextp = 0x1;
        }
        int num = this.SeedArray[inext] - this.SeedArray[inextp];
        if (num < 0x0)
        {
            num += 0x7fffffff;
        }
        this.SeedArray[inext] = num;
        this.inext = inext;
        this.inextp = inextp;
        return (num * 4.6566128752457969E-10);
    }
}

Tested and it gives the desired output.



回答2:

This is not a problem with Random, it satisfies its documented interface perfectly fine. This is a problem with your software relying on an implementation detail. Learn from this mistake and don't do it again.

As far as fixing the problem, you can implement your own version of 1.1's pseudorandom number generation for decoding and then implement a new encoding/decoding algorithm that doesn't rely on unstable behavior (such as the implementation of Random or GetHashCode) for your new version of the software.



回答3:

If you're absolutely reliant on the .NET 1.1 version of Random then the only thing I can think of is to create a new assembly that targets 1.1 and call that from your upgraded .NET 4 application.

However, can you detail why it is so essential for you to maintain this seed? There might be a better way.



回答4:

No answer here but contrary to many people here I don't think that documenting ridiculous behaviour is enough to justify it.

Because why would you provide a seeding mechanism in the first place? Well I'll tell you: so that you can always reproduce a random sequence from a single seed rather than having to persist perhaps millions of random numbers. Note that I said 'always', and not 'until you upgrade to the next version of .NET'. By not being consistent across versions the current .NET random number generators do not provide this functionality. Microsoft should have done a better job of implementing this (or not have implemented it at all) instead of just documenting the defective behaviour.

And by the way, although the algorithm is indeed an implementation detail, how on earth can you call the result of a method call an implementation detail? Should I really have to check the documentation of every method in the .NET framework to be sure that in the next version I do not risk getting a different result from concatenating two strings or calculating a square root?

So in my opinion what we have here is simply a badly implemented random number generator. And of course the entire problem could have been easily avoided by giving the new functionality (based on the new implementation) a different name.



回答5:

Alternatively, might I suggest using System.Security.Cryptography.RandomNumberGenerator class to generate cryptographically strong random byte arrays?

RandomNumberGenerator rng = RandomNumberGenerator.Create();
byte[] bytes = new byte[128];
rng.GetBytes(bytes);

I will join the rest of the comments, and mention that relying on an undocumented implementation is bad. More so, if you're actually relying on a predictable "randomness" - if you're using this for anything that should be "secure" - it is totally wrong.