Repository navigation
Expand file tree
/
Copy pathCryptoRandom.cs
More file actions
106 lines (87 loc) · 3.42 KB
/
Copy pathCryptoRandom.cs
File metadata and controls
106 lines (87 loc) · 3.42 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
using System;
using System.Buffers.Binary;
using System.Security.Cryptography;
namespace Opserver.Helpers
{
/// <summary>
/// Just a "safe" random that looks like System.Random.
///
/// Most of the time, it doesn't matter what random we use.
///
/// But rather than try and _justify_ that for auditing purposes,
/// let's just use the "no, it's really secure the NIST promises"-version.
/// </summary>
public class CryptoRandom
{
private static CryptoRandom _instance;
public static CryptoRandom Instance => _instance ??= new CryptoRandom();
private readonly RandomNumberGenerator _rng;
private CryptoRandom()
{
_rng = RandomNumberGenerator.Create();
}
private int NextPositiveOrNegativeInt()
{
Span<byte> intSizedBuffer = stackalloc byte[sizeof(int)];
NextBytes(intSizedBuffer);
return BinaryPrimitives.ReadInt32LittleEndian(intSizedBuffer);
}
public void NextBytes(Span<byte> buffer) => _rng.GetBytes(buffer);
public int Next()
{
const int MSB = 1 << 31;
// by dropping the MSB, everything has a 2-in-4-billion(ish)
// chance; contrast to using Abs etc, when we'd need to think
// about int.MinValue (can't use Math.Abs) and making sure
// that zero still has 2-in-4-billion chance
return NextPositiveOrNegativeInt() & ~MSB;
}
public double NextDouble()
{
const double SCALE = 1.0 / int.MaxValue;
var randomPosInt = Next();
var ret = randomPosInt * SCALE;
return ret;
}
public int Next(int exclusiveMax)
{
if (exclusiveMax < 0)
{
throw new ArgumentOutOfRangeException($"Must be >= 0, was {exclusiveMax}", nameof(exclusiveMax));
}
var randomZeroToOneExclusiveDouble = NextDouble();
var ret = (int) (randomZeroToOneExclusiveDouble * exclusiveMax);
return ret;
}
public int Next(int inclusiveMin, int exclusiveMax)
{
if (inclusiveMin > exclusiveMax)
{
throw new ArgumentOutOfRangeException(
$"Must be <= {nameof(exclusiveMax)}, was {inclusiveMin} ({nameof(exclusiveMax)} = {exclusiveMax})",
nameof(inclusiveMin));
}
// based on actual Random:
// https://github.com/dotnet/corefx/blob/d3911035f2ba3eb5c44310342cc1d654e42aa316/src/Common/src/CoreLib/System/Random.cs
long range = (long) exclusiveMax - (long) inclusiveMin;
if (range <= int.MaxValue)
{
var offset = (int) (NextDouble() * range);
return inclusiveMin + offset;
}
else
{
const int SHIFT = int.MaxValue - 1;
const uint SCALE = (2 * (uint) int.MaxValue) - 1;
var part1 = NextPositiveOrNegativeInt();
var makeNegative = NextPositiveOrNegativeInt() % 2 == 0;
part1 = makeNegative ? -part1 : part1;
double rangeScale = part1;
rangeScale += SHIFT;
rangeScale /= SCALE;
var offset = (long) (rangeScale * range);
return (int) (inclusiveMin + offset);
}
}
}
}