using System; using System.Collections.Generic; using System.Security.Cryptography; using System.Text; namespace Confuser.Core.Services { /// /// A seeded SHA256 PRNG. /// public class RandomGenerator { /// /// The prime numbers used for generation /// static readonly byte[] primes = { 7, 11, 23, 37, 43, 59, 71 }; readonly SHA256Managed sha256 = new SHA256Managed(); int mixIndex; byte[] state; //32 bytes int stateFilled; /// /// Initializes a new instance of the class. /// /// The seed. internal RandomGenerator(byte[] seed) { state = (byte[])seed.Clone(); stateFilled = 32; mixIndex = 0; } /// /// Creates a seed buffer. /// /// The seed data. /// The seed buffer. internal static byte[] Seed(string seed) { byte[] ret; if (!string.IsNullOrEmpty(seed)) ret = Utils.SHA256(Encoding.UTF8.GetBytes(seed)); else ret = Utils.SHA256(Guid.NewGuid().ToByteArray()); for (int i = 0; i < 32; i++) { ret[i] *= primes[i % primes.Length]; ret = Utils.SHA256(ret); } return ret; } /// /// Refills the state buffer. /// void NextState() { for (int i = 0; i < 32; i++) state[i] ^= primes[mixIndex = (mixIndex + 1) % primes.Length]; state = sha256.ComputeHash(state); stateFilled = 32; } /// /// Fills the specified buffer with random bytes. /// /// The buffer. /// The offset of buffer to fill in. /// The number of random bytes. /// is null. /// /// or is less than 0. /// /// Invalid or . public void NextBytes(byte[] buffer, int offset, int length) { if (buffer == null) throw new ArgumentNullException("buffer"); if (offset < 0) throw new ArgumentOutOfRangeException("offset"); if (length < 0) throw new ArgumentOutOfRangeException("length"); if (buffer.Length - offset < length) throw new ArgumentException("Invalid offset or length."); while (length > 0) { if (length >= stateFilled) { Buffer.BlockCopy(state, 32 - stateFilled, buffer, offset, stateFilled); offset += stateFilled; length -= stateFilled; stateFilled = 0; } else { Buffer.BlockCopy(state, 32 - stateFilled, buffer, offset, length); stateFilled -= length; length = 0; } if (stateFilled == 0) NextState(); } } /// /// Returns a random byte. /// /// Requested random byte. public byte NextByte() { byte ret = state[32 - stateFilled]; stateFilled--; if (stateFilled == 0) NextState(); return ret; } /// /// Gets a buffer of random bytes with the specified length. /// /// The number of random bytes. /// A buffer of random bytes. public byte[] NextBytes(int length) { var ret = new byte[length]; NextBytes(ret, 0, length); return ret; } /// /// Returns a random signed integer. /// /// Requested random number. public int NextInt32() { return BitConverter.ToInt32(NextBytes(4), 0); } /// /// Returns a nonnegative random integer that is less than the specified maximum. /// /// The exclusive upper bound. /// Requested random number. public int NextInt32(int max) { return (int)(NextUInt32() % max); } /// /// Returns a random integer that is within a specified range. /// /// The inclusive lower bound. /// The exclusive upper bound. /// Requested random number. public int NextInt32(int min, int max) { if (max <= min) return min; return min + (int)(NextUInt32() % (max - min)); } /// /// Returns a random unsigned integer. /// /// Requested random number. public uint NextUInt32() { return BitConverter.ToUInt32(NextBytes(4), 0); } /// /// Returns a random double floating pointer number from 0 (inclusive) to 1 (exclusive). /// /// Requested random number. public double NextDouble() { return NextUInt32() / ((double)uint.MaxValue + 1); } /// /// Returns a random boolean value. /// /// Requested random boolean value. public bool NextBoolean() { byte s = state[32 - stateFilled]; stateFilled--; if (stateFilled == 0) NextState(); return s % 2 == 0; } /// /// Shuffles the element in the specified list. /// /// /// The list to shuffle. public void Shuffle(IList list) { for (int i = list.Count - 1; i > 1; i--) { int k = NextInt32(i + 1); T tmp = list[k]; list[k] = list[i]; list[i] = tmp; } } } /// /// Implementation of . /// internal class RandomService : IRandomService { readonly byte[] seed; //32 bytes /// /// Initializes a new instance of the class. /// /// The project seed. public RandomService(string seed) { this.seed = RandomGenerator.Seed(seed); } /// public RandomGenerator GetRandomGenerator(string id) { if (string.IsNullOrEmpty(id)) throw new ArgumentNullException("id"); byte[] newSeed = seed; byte[] idHash = Utils.SHA256(Encoding.UTF8.GetBytes(id)); for (int i = 0; i < 32; i++) newSeed[i] ^= idHash[i]; return new RandomGenerator(Utils.SHA256(newSeed)); } } /// /// Provides methods to obtain a unique stable PRNG for any given ID. /// public interface IRandomService { /// /// Gets a RNG with the specified identifier. /// /// The identifier. /// The requested RNG. /// is null. RandomGenerator GetRandomGenerator(string id); } }