New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
API Proposal: CSPRNG integers with ranges #26716
Comments
I don't think we need to make this cancelable. Assuming our implementation generates a random 64-bit value internally, and assuming that value is evenly drawn from the domain of all 64-bit integers, the odds of us needing to try again for bias elimination are around 1 in 8 billion. |
Something like if (toExclusive <= fromInclusive)
throw something();
int range = toExclusive - fromInclusive - 1;
if (range == 0)
return fromInclusive;
int lzcnt = Intrinsics.SSE4.lzcnt(range);
int mask = (1 << 32-lzcnt) - 1;
int val = int.MaxValue;
Span<int> valSpan = stackalloc int[1];
while (val > range)
{
GetBytes(valSpan.AsBytes());
val = valSpan[0] & mask;
}
return val + fromInclusive; ? Spinning with the lzcnt/mask approach never happens if the total range was a power of 2 (range == mask), and at approaching 50% (from below) probability in the case where the total range is a power of 2 plus one. 4 spins would have a ~6.25% chance, and only consume 32 bytes from the CSPRNG. If someone wanted to hook it up to a blocking TRNG I guess maybe they'd have a concern; but any CSPRNG should be able to handle that at no worse than "F10 fast" (aka if you're in the debugger you never see this call lag). |
Clearly we also need a method |
I had considered adding a proposal for different shuffling algorithms, however decided that I may do those later as a separate API proposal to keep this one succinct and have a higher chance of the proposal moving forward. The broader issue I see is confusion about when to use random and when to use a shuffle. "I want to randomly generate a string with no repeating characters" would make good use for a proper shuffle.
Yes. This API proposal is dependent on A case I might see is a developer that derives Speaking to some offline comments I got asked, "why is the upper bound exclusive", that's to match existing behaviors of many other APIs and frameworks. |
I see the value, and it might be the right shape for what it is. There are two drawbacks I see:
Yahtzee's 5d6 can be accomplished in 15 bits, 3 per die (with a 1/4 chance of a "cocked die" in each set). 5 * 3 + 5 * 3 / 4 + 5 * 3 / 16 = 15 + 3 + 0 = 18 bits EV. 5 calls to Certainly the code could be modified to ask only the "relevant bytes" to be filled, which would drop Yahtzee to 5 * 8 + 5 * 8 / 4 + 5 * 8 / 16 + 5 * 8 / 64 = 40 + 10 + 2 + 0 = 52 bits EV., down to only 200% overhead. A "bit bank" would also reduce it, but that would allow a segment of memory to spy on to know what the next random value of something would be, and that feels wrong. |
I see. Would we want additional static members that defer to
Or should we not bother with the instance members at all now and rely on
Yes. As you mentioned, it can be improved to only request the necessary bytes instead of filling a whole integer. The trade off for me is that some developers will continue to do the wrong thing unless something is in the box. |
The problem is just that you can't have a static and an instance member with the same name. I don't know that it's "wrong" to add this as an instance member, but it means that the caller is back to managing an object lifetime. So it's just a thing to think about. |
I updated the proposal to remove the instance methods and instead propose
|
We discussed if we should add a We considered a parameterless overload We also considered namespace System.Security.Cryptography
{
public abstract class RandomNumberGenerator
{
public static int GetInt32(int fromInclusive, int toExclusive);
public static int GetInt32(int toExclusive);
}
} |
I'm happy to implement this if someone can assign it to me. |
Currently, there are two main ways of getting random data. The insecure
System.Random
, andSystem.Security.Cryptography.RandomNumberGenerator
and its derivatives.The Problem
A common issue I find when doing reviews is that developers are unsure of the best way to get random integers in a secure manner, specifically when they need to clamp the integer to a specific range. They will usually take a few incorrect approaches.
Use
System.Random
because it offers aNext(int, int)
option that has exactly what they want, at the cost of insecurity. They may seedSystem.Random
with output from a CSPRNG, however this is also problematic for a variety of reasons.Use
RandomNumberGenerator
incorrectly. They may useGetBytes
to fill an integer and use modulus to clamp it to a specific range. Modulus usually introduces selection bias. I've seen all manners of trying to use S.S.C.RNG to coerce its values into integers, some better than others, but many of which are flawed.Developers usually want to do this for a variety of reasons. A common reason I see is to build a random string with a specific character set. Something like:
This can be done for reset codes, confirmation codes, temporary passwords, etc.
Proposal
We should offer an API that allows developers to get random integers, securely, without having to think too hard about it.
Add the following APIs:
S.S.C.RNG
will implement these and rely onRandomNumberGeneratorImplementation
'sGetBytes
implementation.Other thoughts
Is it worth making this cancellable? It's very likely that the implementation will have handle the case of making multiple attempts to generate a number that falls within the correct range. Those implementations often distribute binomially, so it's unlikely to be a source of a DoS, but possible. If this is a concern, perhaps an overload returning
ValueTask<int>
and accepting aCancellationToken
are desirable.Is there prior art somewhere in .NET that we can make public, or move in to CoreFX? (Perhaps aspnet has already solved this.)
/cc @bartonjs @GrabYourPitchforks
The text was updated successfully, but these errors were encountered: