MasterMann / SecurityDriven.Core

Modern, fast, safe, cryptographically strong .NET replacement for Random and RandomNumberGenerator.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

SecurityDriven.Core NuGet

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()");
}

About

Modern, fast, safe, cryptographically strong .NET replacement for Random and RandomNumberGenerator.

License:MIT License


Languages

Language:C# 100.0%