-
Notifications
You must be signed in to change notification settings - Fork 13.1k
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
Tracking Issue for secure random data generation in std
#130703
Comments
Disclaimer: I am one of the I think it's important for It may be worth to add the following methods to
It's also not clear whether it's allowed to overwrite the default |
Would you consider rust-lang/libs-team#159 to be a better solution? That one used the |
No, I don't think it's an appropriate solution. Firstly, it relies on For the last point I guess we could define a separate |
I don't think this needs to be a blocker. IMO a lot of |
Would it be better to make this explicit with generics? Like |
I (mostly) disagree. CSPRNGs should almost never fail. When they do, users are almost never not qualified to diagnose the problem. For example: golang/go#66821 A compromise is something like this: trait RandomSource {
type Error;
fn fill_bytes(...) {
self.try_fill_bytes(...).unwrap();
}
fn try_fill_bytes(...) -> Result<..., Self::Error>
} This allows most CSPRNGs to use |
@ericlagergren As for design of fallible RNG traits, see the new |
This trait (and the topic of random value generation) should be removed from this discussion entirely in my opinion, focussing only on "secure random data generation" as in the title. Why: because (1) provision of secure random data is an important topic by itself (with many users only wanting a byte slice and with methods like Disclaimer: I am one of the |
Overriding the default source in an application that already has one from linking If overriding the
This wouldn't be a problem if the entire ecosystem could agree to always delegate this problem to on one specific crate (version) with appropriate hooks, like Edit: almost forgot that even |
There is a number of reasons to allow overriding:
Yes. How about following the Either way, overriding is probably can be left for later. I think we both agree that we need a way to expose "system" entropy source in
I agree that ideally we need a unified approach for this kind of problem. I made a similar proposal once upon a time. But I think it fits fine? Targets with
I believe that having
Well, it has happened, sort of. The problem is that
Can we add yet another sysroot crate for |
The RFC (and the competing ones I've looked at) only supports a default implementation in the crate that "declares" the externally-implementable thing. If that crate isn't
I was specifically talking about
Not to point any fingers but a counter example that's fresh on my mind because I looked at its code recently is foldhash. As another example,
Possibly, but people may object to a proliferation of sysroot crates so let's hope there's a better solution. |
Yes, it's not as if RFC is a technical specification which must be followed word-by-word. There is a number of cases where the original RFC vision has somewhat changed during implementation stages. If anything, I would say it's an oversight/deficiency of the RFC to not cover cases like this.
If a crate aims to minimize its number of dependencies as far as possible even at the cost of code quality and security, it obviously will not depend on |
As I said, I have no intention of pointing fingers at any crates. They have to navigate tricky trade-offs and complexities due to Rust's standard library (as a whole, not just |
The Random trait seems weakly motivated in terms of coupling it to RandomSource, as its design seems like it will be a much more hotly contested space, and it is (mostly) unrelated to RandomSource. |
Note that @dhardy (maintainer of rand) wrote some criticism of this at rust-lang/libs-team#393 (comment) |
Could let random_array: [u64; 100] = random(); which is a lot more convenient than using the |
I think it would be useful to have a data type like |
I disagree that it is useful in testing implementations of traits like |
As far as I know there’s no plan to make the |
It seems like one might regret adding For more complicated types (e.g., It seems better to leave this out rather than leave it in this poorly specified state. Just stabilize Although... even |
I really don't think so. The current interface works perfectly fine, it's just slightly suboptimal in some use cases (fewer than in the context of general byte-centric I/O). Providing only a So I think it's pretty clear that the method for filling a |
Perhaps something like this would work: // Users don't need to concern themselves with this type.
pub strut OutBuf([MaybeUninit<u8>]);
impl OutBuf {
// various methods to construct it and write to it, no method to read from it
}
// Users don't need to worry about implementing this, just use it
pub unsafe trait AsOutBuf {
type Init: ?Sized;
fn as_out_buf(&mut self) -> &mut OutBuf;
// Only allowed if the entire buffer was overwritten.
unsafe fn assume_init(&mut self) -> &mut Self::Init;
}
impl AsOutBuf for [u8] { type Init = [u8]; ... }
impl AsOutBuf for [MaybeUninit<u8>] { type Init = [u8]; ... }
impl<const N: usize> AsOutBuf for [u8; N] { type Init = [u8; N]; ... }
impl<const N: usize> AsOutBuf for [MaybeUninit<u8>; N] { type Init = [u8; N]; ... }
// Users can pass in any byte slice, byte array or their uninitialized counterparts
// The entire value is guaranteed to get overwritten, so if using the returned value is undesirable due to borrowing issues the users can simply use the original value safely, if it was an initialized type to begin with or they may just soundly call `assume_init` on it.
pub fn fill_random_bytes<T: AsOutBuf>(buf: &mut T) -> &mut T::Init { ... } This way it already supports all reasonable cases and people are not forced into using uninitialized API. They can write |
That helps the simple call sites remain simple, but it has several downsides:
|
The ACP says this is for "secure" random number generation. I take that to mean this interface is for cases where you need a truly unpredictable value, e.g., an encryption key or some kind of secret token. This is something only the platform can provide, these days, especially if you want protection against VM snapshots and process forks and things like that. There are really no design decisions here (other than this And so it seems like a good idea to expose this from But echoing some comments above, it does feel like this proposal is attempting to support usage for randomized algorithms as well. But there are already well-supported crates in the ecosystem for this, so it's not obvious that this really needs to be in And really the ACP is kind of two-faced about this, as well. It talks about secure random numbers but then uses a bunch of randomized-algorithm like tasks as motivating examples. But anyway, I see that basically my objections mirror the various |
Uh, right? I mean I agree with you that a programmer should not use a normal PRNG for cryptography. That is absolutely not what I was suggesting. |
It's normal (and expected) to read cryptographic keys straight from the system CSPRNG. |
To me that would fall under "similar task", yeah. |
The necessity of the
Even if we are to implement this trait for floating-point types, string types, or I also think it might be a good idea to add the If we want an implementation of this trait for floating-point types, we can do so by defining |
I don't think so. If there is a limited list, there will be more and more questions about "why not also Y". People can also underestimate the complexity here, like, how can you generate a uniform If all we want is to have a way to get random CSPRNG bits from OS, we should stick on getting bytes, and let downstream to implement these "shortcut traits and methods" for their various requirements (someone thinks |
"Not as easy you think" is an understatement. The task is literally impossible, because your RNG could continuously generate zeros for an arbitrary amount of time. In this case your only choice is to map zero to some value, thus making the distribution no longer uniform.
You could generate a uniform random float by just choosing random bits, it's just that such an operation is completely useless in practice (you would get a bunch of big numbers, tiny numbers, and NaNs). People generally want a mathematical distribution like the standard uniform or normal distributions which I think is out of scope of this issue. Generating a random I would recommend implementing |
I removed the implementation of |
"An arbitrary amount of time" is a bit of an overstatement. The probability of a CSPRNG generating N consecutive zero bits is 1/(2^N). Rejection sampling is probably fast enough, but you could also over sample to prevent more than one call to the CSPRNG. Either way, you know what the realistic upper bound is.
Or sample extra bits. |
Regarding errors, this is a simple approach: pub trait RandomSource {
// CSPRNG impls can simply set this to Infallible
type Error;
// ignoring uninit for simplicity
fn fill_bytes(&mut self, bytes: &mut [u8]) -> Result<(), Self::Error>;
/// Returns an adaptor converting all the errors.
///
/// This is useful when one needs to unify multiple different errors (e.g. by boxing them) so that the trait can be used in dynamic dispatch.
fn map_err<E, F: FnMut(Self::Error) -> E>(self, f: F) -> MapErr<Self, F> where Self: Sized { /* ... */ }
} The great thing about this is |
@ericlagergren this is why in
@newpavlov I'm saying we definitely want a fallible interface for the system random source. Whether we also want to support other potentially fallible random sources in ... except, we may want both secure and potentially-insecure external random sources. So this is a design decision: use a simple
@sorairolake can we just not talk about this for now? Or make a new issue for it? The same goes for infallible random sources. We may want a trait like |
Why two traits instead of setting the associated error type to infallible/ |
Do you want to write Much simpler to write Other than this (and the |
I agree. In my comment I wrote that the free standing function may simply panic on potential errors to side-step the error type issue. Right now As I wrote above, I would prefer to have at least 3 sources exposed in As for the trait design, one potential alternative to what we have today in pub trait TryRng {
type Error: core::error::Error = Infallible;
fn try_next_u32(&mut self) -> Result<u32, Self::Error>;
fn try_next_u64(&mut self) -> Result<u64, Self::Error>;
fn try_fill_bytes(&mut self, buf: &mut [u8]) -> Result<(), Self::Error>;
}
pub trait Rng: TryRng<Error = Infallible> {
fn next_u32(&mut self) -> u32;
fn next_u64(&mut self) -> u64;
fn fill_bytes(&mut self, buf: &mut [u8]);
}
impl<T: TryRng<Error = Infallible>> Rng for T {
fn next_u32(&mut self) -> u32 {
let Ok(val) = self.try_next_u32();
val
}
fn next_u64(&mut self) -> u64 {
let Ok(val) = self.try_next_u64();
val
}
fn fill_bytes(&mut self, buf: &mut [u8]) {
let Ok(()) = self.try_fill_bytes(buf);
}
} In other words, all RNGs would implement the |
I most likely want to just pass the error to the caller and keep my API flexible. If this is for some reason not suitable, I'd be fine with that, and once trait aliases are a thing you can have your cake and eat it too.
No, in today's stable Rust I write
Yes but also it's less flexible. You are committing to not support fallible RNGs. Whether it's a problem or not depends on specific case.
That's good to hear, so would you agree that perhaps with trait aliases and stable @newpavlov I think that trait alias might be cleaner but you probably do have a point that infallible RNG implies fallible with |
What applications are we talking about? For a system random source that is only used for a few low-level applications (like seeding a local PRNG), requiring users be pedantic is fine. But if we're talking about replacing
I don't know if we want to call it that, but yes, we might want that too. I was assuming this would be added later, but it may make sense to add now too (but possibly stabilise later). The main concern I have is what implementation would make the most sense: something complex like |
Why? |
Not all system RNGs are infallible. Windows < 10 RNG isn't infallible. Most OS RNGs are not infallible, in fact--especially ones that get FIPS-ified. Anybody can (It is true that people designing system RNGs should aim for them to be as close to infallible as they can get.) BTW, is the "system" RNG really the system RNG, or is it sometimes a libc-provided thing, like is common in BSD? I would love to see an interface that skips completely over libc whenever practical. |
Is it actually fallible, or is it just not documented to be infallible? My view is that the failure mode of thinking you got cryptographic random data in a buffer but you didn't is so bad that we should not support that possibility in |
I think we specifically need it to also possibly be deterministic (or have an alternative interface for hashmaps), since we may want our program to be 100% deterministic for things like synchronously stepping game state, reproducibility (e.g. so everyone agrees on the output of a particular transaction in a cryptocurrency, or for reproducible builds), and similar. this is an explicit goal of WASI's random interface, where a deterministic WASM engine will only implement insecure random and always returns the same sequence every time it's run (well, technically it just provides a constant seed value and lets the WASM code build a RNG from that). |
The problem is that users will fall back to insecure RNGs, which is catastrophic. And if your CSPRNG fails—then what? Outside of very niche situations there isn't anything you can do about it.
Very much in agreement with this. |
For people subject to strict (external) requirements, it's a distinction without a difference because we can't rely on undocumented behavior without a lot of work. My understanding is that Linux |
Sure. But I work for Microsoft. I can probably get documentation corrected if it's really a blocker. I got If there are issues with other OSes, well, I can't help with that :). |
Yes, that would be great, esp for Windows 7 and Windows 10. I would also love better documentation for |
There is no documented facility to bypass userspace PRNG in Windows. I'm not sure why you'd want to, given that it already has rekey logic--it should be no more vulnerable to VM snapshots or similar than the kernel RNG. |
Yes, I don't need to talk about it for now. |
Unused |
There is an alternative: a function that returns a Of course then you'll have people screaming about how it's "inefficient" because they have to copy a few bytes once, despite the entire point being that you would use it to obtain a seed and not repeatedly hammer it to create large quantities of entropy. |
There are ways to mitigate it or solve it completely (at the cost of very weird API). If you take in |
Feature gate:
#![feature(random)]
This is a tracking issue for secure random data generation support in
std
.Central to this feature are the
Random
andRandomSource
traits insidecore::random
. TheRandom
trait defines a method to create a new random value of the implementing type from random bytes generated by aRandomSource
.std
also exposes the platform's secure random number generator via theDefaultRandomSource
type which can be conveniently access via therandom::random
function.Public API
Steps / History
random
feature (alternative version) #129201Unresolved Questions
gen_bytes
andDefaultRng
, the implementation PR usesfill_bytes
andDefaultRandomSource
(see arguments progen_bytes
and profill_bytes
)Footnotes
https://std-dev-guide.rust-lang.org/feature-lifecycle/stabilization.html ↩
The text was updated successfully, but these errors were encountered: