Repository navigation
OsRng crate? (or optional PRNG dependencies) #648
Description
Activity
Just my two cents on this issue: I think a safe default would be for rand to only provide CSPRNGs by default. If you need an insecure PRNG, it should be really explicit.
I don't know about the current crate hierarchy, but if it's not secure by default, if it's not blatantly obvious when using an insecure PRNG, we should move in that direction. My opinion, of course, but I have laid out why I feel this way.
A crate name like insecure_random would be nice :)
Security is one of the many selling points about Rust, so we should try to make it hard to mess up.
Reacted by Tony Arcieri, Sergey "Shnatsel" Davidoff and PauanSee #643. We may move forward with this but I'd like us to explore moving the
OsRnginterface itself into thestdlib.There's a lot of history involved and not wanting to break
randtoo much. But moving more functionality out and makingranda facade may be the way to go (though I suspect we'll keep things like theDistributiontrait andRnginrand).@naftulikay there are so many ways to mess up security. We now have a
CryptoRngmarker trait and I regret it because that alone is not enough to guarantee security.randis probably more about stats/sampling than anything else. But at least we make efforts to makethread_rngsecure (though without forward secrecy, and possibly not using the most trusted algorithm; IIRC @tarcieri volunteered to write a fasterChaChaRngfor us at one point).Reacted by Naftuli Kay and Sergey "Shnatsel" DavidoffI think we should move forward with
OsRngcrate. Even ifstdwill include it later (which I doubt, because a simplegetrandom-like function is a more suitable candidate for inclusion) we always can deprecate this crate.We now have a CryptoRng marker trait and I regret it because that alone is not enough to guarantee security.
CryptoRngnever was about guaranteeing security (after all you always can use wrapper withCryptoRngimplementation), but about communicating expectations. In other words it's a way to indicate that application expect CSRNG and not a weak PRNG.Reacted by Diggory Hardy, hcpl, Queer Supervillainess, Naftuli Kay, Johannes and Henry de ValenceI'm 💯 percent behind OsRng being part of std.
On my i7 8650U laptop CPU on Linux 4.15 I can read /dev/urandom at around 112MB/s. As you described above, I do imagine that we could build some stream or block cipher based RNG which could be a lot faster; generate a large random key from /dev/urandom, use that with a symmetric cipher, and the upper bound on performance would then be the initialization cost (once) plus the speed that the cipher can run at. I could be misinformed (there are actually cryptographers around these parts @tarcieri), but using a symmetric cipher as a CSPRNG has been done in the past pretty well as long as your input key has a strong randomness guarantee.
The only use case that isn't covered here is if you are concerned about renewing randomness. IIRC the kernel is constantly resampling randomness sources and mixing them into the RNG, whereas this contract is dependent on /dev/urandom being secure at the time of key generation.
One thing I've always wondered is how fast PRNGs can be if they aren't CSPRNGs. ChaCha20 is pretty damn fast for a stream cipher, the original white paper claims that it's around one CPU clock cycle per byte, and I'm not sure if this is using SIMD.
I hope that my comment above didn't come across as accusatory or anything, I'm grateful for all the work the rand team does on this incredibly useful crate.
@naftulikay run
cargo bench --bench generatorsto get some numbers 😃 (though apparently ChaCha should be faster; you can see though that CSPRNGs are not slow)Our
thread_rngdoes periodic reseeding, and also has fork protection on UNIX. As most things security, there are compromises to make, and we choose to compromise such that performance is barely affected.Reacted by Naftuli KayI'm sorry to say (and perhaps this is a bad forum to say it), but I'm kind of "losing faith" in this crate for the purposes I want it for, which is a thin abstraction over the OS-provided cryptographically secure random number generator.
There's an awful lot of complexity and code surface I don't want, which is pretty much anything besides the thinnest possible wrapper over the OS-provided cryptographically secure random number generator.
Looking over the comments in #643, and in this thread, I'm confused what plan there is, if any, to get any parts of this crate into
std.If this crate isn't prototyping functionality that is eventually destined for
std, I feel like perhaps my particular problem would be better solved by a crate solely focused on providing the thinnest possible wrapper to the operating system's cryptographically secure random number generator, as opposed to an abstraction layer over both cryptographically secure and insecure random number generators, or bespoke userspace "CSPRNGs".(As I write this, I'm presently investigating a critical RNG-related vulnerability in a project I contribute to, and the extant churn around
randis already making that difficult)@tarcieri I appreciate the criticism. This project doesn't really get enough input from the security/crypto perspective, so your input is definitely useful.
Does #643 meet your requirements? Do you have other expectations?
I don't believe there has been any serious effort to get any more random-number functionality into
stdyet. There are several unanswered questions. It would be useful if you could write a design proposal for this (either an RFC or an issue here sketching the requirements). Questions like:- do we want an RNG (with trait like
RngCore) or a simple "fill bytes" function? - do we want some kind of fallback like
JitterRngor not at all? - what behaviour do we want if
getrandomwould block? - do we try to support
no_stdby allowing "lang items" to override the backend?
- do we want an RNG (with trait like
So first, apologies, at the time I wrote my previous post I thought
randmight be implicated in a critical bug, but that turned out to be a red herring. I've since fixed an unrelated problem elsewhere, so I'm in a better state now at least.Regarding #643 (and perhaps we should just take the conversation back over there), I'm with @newpavlov on this comment:
If users want only the basic RNG functionality why make their live harder?
I would like for the API to be as straightforward as possible, so abstractions which aren't necessary for purely CSRNG-cases to stay out of the way. #643 mostly accomplishes that but per your first question, and the linked comment:
I think providing a direct interface to the OS's secure random facility is a reasonable primitive to provide in the standard library personally
This is what I want the overwhelming majority of the time personally, and would love to see it in
std. It seems likerand_oscould still exist in such a case, but would implement the relevantrand_coretraits using this API.There are a handful of cases where I do want the trait, namely when I'm testing cryptographic primitives which internally rely on randomness (e.g. ECDSA) and need to have something which implements
CryptoRngbut supplies a fixed test vector in lieu of true randomness.do we want some kind of fallback like JitterRng or not at all?
I would personally prefer no fallback, at least for something called
OsRng.what behaviour do we want if getrandom would block?
Ideally I'd like to see the knobs
getrandom()has exposed if possible, i.e.GRND_RANDOMvsGRND_NONBLOCK. You could imagine ablocking: booloption or thereabouts for that.do we try to support no_std by allowing "lang items" to override the backend?
That would be ideal, yes. I say this having written a
#![no_std]app which wrapped a proprietary/nonstandard RNG API.There is an interface much like what you want internally to
OsRngso maybe anrand_oscrate should expose it along with definingOsRng? An option might be OS specific constructors forOsRng?Just noticed clicking
[src]fromOsRnggives an empty file.As an aside, we should probably adopt the
BlockRngmachinery for stream ciphers because it's much better than anything else currently available there. It'd suffice to add aRngCore::xor_bytesfunction really.I wonder if we should cover
GRND_RANDOM+GRND_NONBLOCKcombination. It will make safe API more complex, as we'll have to keep in mind that buffer can be filled partially, which AFAIK can not happen on other platforms.Thanks @tarcieri for the feedback. Sounds like we should proceed with #643 as is, and possibly explore adding a function like
pub fn secure_random(buf: &mut [u8], blocking: bool) -> Result<(), Error>(but behind a feature-gate because it probably would be removed after integration intostd).But @newpavlov has a point — a correct non-blocking API might partially fill a buffer. IIRC the current internal API may do this, but simply assumes no data was collected and retries?
@burdges adding
RngCore::xor_bytesis an interesting idea, and probably deserves its own issue. I do have some reservations though. This is a primitive, not a complete encryption scheme, and DIY encryption is not generally encouraged (to put it mildly). E.g., a naive user might seed a CSPRNG directly from a key without nonce and use the same stream to encrypt multiple pieces of data — this is unsafe.Agreed this is not the issue for that question, but.. I said "stream cipher", which is a low level notion. while normally "modes" deal with iv, macs, etc.
I mostly wanted to highlight the
BlockRngmachinery in the contest of stream ciphers. It's also likely the right way to approach what the RustCryptoDigesttraits do.Wouldn't
xor_bytesjust be a wrapper aroundfill_bytesanyway? Perhaps it could have a specialised implementation forBlockRng, but later we can achieve that with specialization.There may still be an issue with using
RngCorefor ciphers though; the specification allows implementations to drop extra bytes as they see fit, e.g. if a byte-buffer does not use an exact number of words. This isn't really a problem in stochastic simulations since there are easier ways to affect reproducibility, but may be a problem for ciphers (if the text may be split into chunks arbitrarily).14 remaining items
Thanks Alex. I guess that gives me enough to write an RFC at least.
@dhardy keep us posted, happy to contribute to that 👍
In any case we don't really have a process or way right now for std to require a new symbol on the
wasm32-unknown-unknowntarget.Would using a new lang item for a system CSRNG address this issue? (while also providing e.g. embedded platforms a way to leverage proprietary vendor-specific RNGs, possibly through closed-source SDKs)
Good question — can lang items be specified multiple times (with placeholder and full implementations) or do they need to be specified exactly once?
I don't fully understand how they work; it looks like they need hooks within the compiler, which isn't ideal just for a library function (though this was also part of the motivation for this RFC, since external linkage is only available for C FFI functions and all other options are run-time).
@naftulikay I updated the RFC text but am still not happy with the
no_std/ WASM / lang item part. Although it may be worth making the RFC PR soon anyway.@dhardy love it! I don't really have much to add, all of this seems perfectly reasonable, only to ask for some clarifications.
Before I dump this wall of text, would it be helpful for us to all get together on IRC or something so we can discuss in real time? I'm not sure what the process is around previous RFCs which are much bigger than this, and long issue comment histories on GitHub are hard to weed through. We could produce our findings after the meeting in a summary comment here or on the RFC. Just a thought.
API interface
As far as API naming is concerned, I'm ambivalent about the different options.
secure_randomis nice as it serves as a reminder of the CSPRNG quality, but there are non-system CSPRNGs so IDK.system_randomis nice because it's the system's RNG, but it may not be immediately clear that its contract is a CSPRNG.- I don't favor
os_randombecausesystem_randomexpresses this same idea.
Both
secure_randomandsystem_randomare both good choices, but perhaps somebody has more insight on this than I do.Error handling
The error enum is a great idea, returning
ErrorKind::Unavailablewhen unimplemented orErrorKind::NotReadywhen it would block.- Implementors should always choose a non-blocking backend if at all possible, as you have noted.
- In the case where something would block (even though as above it really shouldn't), there's a race condition.
I'll be speaking to Linux because it's the system CSPRNG that I'm most familiar with.
NOTE: here is some background on Linux's cryptographically-secure random number generator (abbreviated as CSPRNG). Somewhat surprisingly,
/dev/randomand/dev/urandomboth source their randomness from the same randomness pool, which is fed by a bunch of different sources, many around I/O and non-deterministic things like that. The difference between the two, besides/dev/randomblocking and/dev/urandomnot blocking, is that/dev/randomreads an integer within the kernel which keeps track of how much "calculated entropy" in bits is available at the time. When the bits of calculated entropy are exhausted, it blocks. "Calculated entropy" increases over time as the randomness pool is sourced with new randomness data. They're both the same data, just with one being spread out over time.So here's the race condition. Implementors, as noted above, should always choose a non-blocking randomness source, but if they don't, there's a problem. If for some reason,
/dev/randomon Linux is used as the randomness implementation (don't do it!),std::random::is_readycan return true and thenstd::random::{system,secure}_randomis called and immediately returnsErrorKind::WouldBlock. This can happen because of other processes/threads reading from the device file and reducing the calculated amount of available entropy.Let's say that we want to read two bytes of randomness:
let mut a = [0u8; 2]; let result = std::random::secure_random(&mut a);
What happens here if there is only one byte available of randomness? It returns
ErrorKind::WouldBlock, yetstd::random::is_readywill return true. It won't read that one byte, in fact it won't read anything until at least n bytes are available where n is the size of your array. Other processes/threads/things can keep exhausting the randomness and we don't have a way of preventing blocking.Of course, the goal is to not have blocking implementations. If we put it in
core::randomthough, we might have to account for blocking implementations at the API level. If we can guarantee non-blocking always forstdimplementations, then the race condition doesn't exist anymore.Also, I hope I'm understanding properly, this is about
/dev/randomvs/dev/urandomas on Linux, notO_NONBLOCK, right?This whole thing could be a non-issue, but it should at least be considered.
Core vs Std
The RFC does a great job of handling
core::randomnot being implemented by returningErrorKind::Unavailable. IIRC Linux at least does some interesting things where there's a point in time during boot where the RNG is considered "ready." I'm not sure how this actually works though; does the RNG just block until it's declared ready internally? Does the device file not exist until it's "ready?"
@dhardy excellent work on the RFC, seriously A+.
Again, if it'd be beneficial to get all of us in the same room (virtually of course, hangouts or IRC or something), it might save a lot of discussion time 👍
secure_randomis nice as it serves as a reminder of the CSPRNG quality, but there are non-system CSPRNGs so IDK.
I think this is clearly the better name because it emphasizes that if you swap out the PRNG for a different one, it better be secure.
system_randomis nice because it's the system's RNG, but it may not be immediately clear that its contract is a CSPRNG.- I don't favor
os_randombecausesystem_randomexpresses this same idea.
Neither of these names really make sense in the case of having a lang item that allows one to swap in a non-system allocator.
Reacted by Diggory HardyWe propose that libcore provide an implementation of secure_random which always fails, but that libstd uses lang items to override this with a real implementation, depending on the platform. Users of no_std must provide an implementation themselves (likely via a third-party crate) or avoid dependence on this function.
It would be nice to guarantee that one can only swap in a new implementation in the top-level crate, not in a library. Also it would be nice to guarantee that the built-in implementation for a given target will always be used when it is available, so that only platforms that don't have any system PRNG would be able to use the lang item.
@naftulikay please check the linux source: we use
/dev/randomonly to check this has been seeded, since some versions of Linux may be deterministic during early boot. So for us it's either "blocking" or "never blocking again". I believegetrandomworks the same way. So I don't care about the case when it might block mid-read.As for other systems — I don't know; there are a lot and also embedded devices with some type of RNG.
We already have a Gitter room but don't use it much. Potentially we could piggyback https://rust-lang.zulipchat.com/# or make our own, or IRC....
It would be nice to guarantee that one can only swap in a new implementation in the top-level crate, not in a library. Also it would be nice to guarantee that the built-in implementation for a given target will always be used when it is available, so that only platforms that don't have any system PRNG would be able to use the lang item.
Stuff I'm not sure we can do with lang items... perhaps we should go back to @pitdicker's original idea of an
extern(C)function as a fallback forEntropyRng(prioritised overJitterRngbut underOsRng).Reacted by Naftuli KayI had a go at writing an RFC: https://github.com/dhardy/rfcs/blob/system-random/text/0000-system-random.md
How about creating an issue for it in the
getrandomrepo?I don't see any point prioritising this until after
getrandomis published and a dependency of Rand. Even after that, I'm not convinced whether "more code in std" is the right approach.stddepends on several external crates now, and could potentially depend ongetrandom.As for
no_stdsupport, since we have such a simple function propotype now, using anextern "C"function is a very easy solution and I don't believe lang-items would help, even if neither solution is perfect. See here: rust-random/getrandom#4 (comment)I still believe having
getrandominstd/coreis the right call. Not only it will allowHashMapand co to use the same entropy source as the rest of an application, but also will make switching source more straightforward and less hack-y. And Ruststdalready contains code very similar togetrandom(though it does not have to work with large buffers, so code a bit simpler), so including it intostdwill somewhat help with code de-duplication as well.But I guess this discussion could indeed wait until the crate gets published and used a bit.
There is a lot of good discussion here, but this isn't the right place to continue it, and long GitHub threads are a pain to read. Lets move future discussion to rust-random/getrandom#21 for now.
rand0.6 seems to have split all of the PRNGs into their own separate crates.However, these crates are mandatory dependencies of the
randcrate, which is still home toOsRng.I think it'd be great if one of two things happened:
OsRngwere split into its own crate which is only dependent onrand_corerandcrate provided cargo features for the numerous PRNGs so I don't need to pull them into my project when all I want isOsRng.Sidebar: I'm a little confused why
randandrand_coreboth exist. It would personally make more sense to me ifrandcontained the core traits, and anyone wanting an RNG can pick a crate with the one they want which only depends onrand. It seems like right nowrandis both a facade and the sole home ofOsRng