MathHelpers: add CryptoRandom()

Random() is biased and generates predictable numbers (seeded with time by default), using RNGCryptoServiceProvider fixes this.
This commit is contained in:
tinybarks 2018-10-01 15:11:26 +02:00
parent 52e3ec4a87
commit ed40360491
No known key found for this signature in database
GPG key ID: D999045C35D5795A
2 changed files with 64 additions and 2 deletions

View file

@ -243,7 +243,7 @@ public static string HourTo12(int hour)
public static char GetRandomChar(string chars)
{
return chars[MathHelpers.Random(chars.Length - 1)];
return chars[MathHelpers.CryptoRandom(chars.Length - 1)];
}
public static string GetRandomString(string chars, int length)
@ -283,7 +283,7 @@ public static string GetRandomLine(string text)
string[] lines = text.Trim().Lines();
if (lines != null && lines.Length > 0)
{
return lines[MathHelpers.Random(0, lines.Length - 1)];
return lines[MathHelpers.CryptoRandom(0, lines.Length - 1)];
}
return null;
}

View file

@ -24,6 +24,7 @@
#endregion License Information (GPL v3)
using System;
using System.Security.Cryptography;
namespace ShareX.HelpersLib
{
@ -36,6 +37,19 @@ public static class MathHelpers
private static readonly object randomLock = new object();
private static readonly Random random = new Random();
private static readonly object cryptoRandomLock = new object();
private static readonly RNGCryptoServiceProvider cryptoRandom = new RNGCryptoServiceProvider();
private static byte[] rngBuf = new byte[4];
/// <summary>
/// Returns a random number between 0 and <c>max</c> (inclusive).
/// </summary>
/// <remarks>
/// This uses <c>System.Random()</c>, which does not provide safe random numbers. This function
/// should not be used to generate things that should be unique, like random file names.
/// </remarks>
/// <param name="max">The upper limit of the number (inclusive).</param>
/// <returns>A random number.</returns>
public static int Random(int max)
{
lock (randomLock)
@ -52,6 +66,54 @@ public static int Random(int min, int max)
}
}
/// <summary>
/// Returns a random number between 0 and <c>max</c> (inclusive) generated with a cryptographic PRNG.
/// </summary>
/// <param name="max">The upper limit of the number (inclusive).</param>
/// <returns>A cryptographically random number.</returns>
public static int CryptoRandom(int max)
{
return CryptoRandom(0, max);
}
/// <summary>
/// Returns a random number between <c>min</c> and <c>max</c> (inclusive) generated with a cryptographic PRNG.
/// </summary>
/// <param name="min">The lower limit of the number.</param>
/// <param name="max">The upper limit of the number (inclusive).</param>
/// <returns>A cryptographically random number.</returns>
public static int CryptoRandom(int min, int max)
{
// this code avoids bias in random number generation, which is important when generating random filenames, etc.
// adapted from https://web.archive.org/web/20150114085328/http://msdn.microsoft.com:80/en-us/magazine/cc163367.aspx
if (min > max)
{
throw new ArgumentOutOfRangeException("min");
}
if (min == max)
{
return min;
}
lock (cryptoRandomLock)
{
var diff = (long)max - min;
long ceiling = (1 + (long)uint.MaxValue);
long remainder = ceiling % diff;
// this should only iterate once unless we generate really large numbers
uint r;
do
{
cryptoRandom.GetBytes(rngBuf);
r = BitConverter.ToUInt32(rngBuf, 0);
} while (r >= ceiling - remainder);
return (int)(min + (r % diff));
}
}
public static float RadianToDegree(float radian)
{
return radian * RadianPI;