SecurityDriven.Core
What's wrong with Random
and RandomNumberGenerator
?
Random
is slow and not thread-safe (fails miserably and silently on concurrent access)
Random
is incorrectly implemented:
Random r = new Random( ) ; // new CryptoRandom();
const int mod = 2 ;
int [ ] hist = new int [ mod] ;
for ( int i = 0 ; i < 10000000 ; i ++ )
{
int num = r. Next ( 0x55555555 ) ;
int num2 = num % 2 ;
++ hist[ num2] ;
}
for ( int i = 0 ; i < mod ; i ++ )
Console. WriteLine ( $" { i} : { hist[ i] } " ) ;
// Run this on .NET 5 or below. Surprised? Now change to CryptoRandom
Random
/.NET 6 unseeded is fast (new algorithm), with a safe .Shared
property, but instances are not thread-safe
Random
/.NET 6 seeded falls back to legacy slow non-thread-safe .NET algorithm
Neither Random
implementation aims for cryptographically-strong results
RandomNumberGenerator
can be much faster with intelligent wrapping and more useful Random
API
CryptoRandom : Random
.NET Random done right
[Fast], [Thread-safe], [Cryptographically strong]: choose 3
Subclasses and replaces System.Random
Also replaces System.Security.Cryptography.RandomNumberGenerator
(link )
CryptoRandom
is (unlike System.Random
):
Fast (much faster than Random
or RandomNumberGenerator
)
Thread-safe (all APIs)
Cryptographically strong (seeded or unseeded)
Implements Fast-Key-Erasure RNG for seeded CryptoRandom
Wraps RandomNumberGenerator
for unseeded (with additional smarts)
Achieves ~1.3cpb (cycles-per-byte) performance, similar to AES-NI
Scales per-CPU/Core
Example: CryptoRandom.NextGuid()
vs. Guid.NewGuid()
[BenchmarkDotNet]:
2~4x faster on Windows-x64
5~30x faster on Linux-x64
3~4x faster on Linux-ARM64 (AWS Graviton-2)
Built for .NET 5.0+ and 6.0+
Extensive test coverage & correctness validation
CryptoRandom API :
All APIs of System.Random
(link )
CryptoRandom.Shared
static shared instance for convenience (thread-safe of course)
Seeded constructors:
CryptoRandom(ReadOnlySpan<byte> seedKey)
CryptoRandom(int Seed)
(just like seeded Random
ctor)
byte[] NextBytes(int count)
Guid NextGuid()
Guid SqlServerGuid()
Returns new Guid well-suited to be used as a SQL-Server clustered key
Guid structure is [8 random bytes][8 bytes of SQL-Server-ordered DateTime.UtcNow]
Each Guid should be sequential within 100-nanoseconds UtcNow
precision limits
64-bit entropy for reasonable unguessability and protection against online brute-force attacks
long NextInt64()
long NextInt64(long maxValue)
long NextInt64(long minValue, long maxValue)
Random struct
's (make sure you know what you're doing):
void Next<T>(ref T @struct) where T : unmanaged
T Next<T>() where T : unmanaged
Utils API (SecurityDriven.Core.Utils
):
static Span<byte> AsSpan<T>(ref T @struct) where T : unmanaged
Casts unmanaged struct T as equivalent Span<byte>
static ref T AsStruct<T>(Span<byte> span) where T : unmanaged
Casts Span<byte>
as equivalent unmanaged struct T
int StructSizer<T>.Size
Returns byte-size of struct T
Quick benchmark :
// using System.Threading.Tasks;
// using SecurityDriven.Core;
var sw = new Stopwatch( ) ;
const long ITER = 100_000_000 , REPS = 5 ;
for ( int i = 0 ; i < REPS ; ++ i )
{
sw. Restart ( ) ;
Parallel. For ( 0 , ITER, static i => CryptoRandom. Shared. NextGuid ( ) ) ;
Console. WriteLine ( $" { sw. Elapsed} cryptoRandom.NextGuid() " ) ;
}
Console. WriteLine ( new string ( '=' , 40 ) ) ;
for ( int i = 0 ; i < REPS ; ++ i )
{
sw. Restart ( ) ;
Parallel. For ( 0 , ITER, i => Guid. NewGuid ( ) ) ;
Console. WriteLine ( $" { sw. Elapsed} Guid.NewGuid() " ) ;
}