[swift-evolution] [Proposal] Random Unification

Xiaodi Wu xiaodi.wu at gmail.com
Tue Sep 26 18:14:58 CDT 2017


On Tue, Sep 26, 2017 at 11:26 AM, Félix Cloutier <felixcloutier at icloud.com>
wrote:

>
>
> Le 26 sept. 2017 à 07:31, Xiaodi Wu via swift-evolution <
> swift-evolution at swift.org> a écrit :
>
>
> * The distinction to be made here is CSPRNGs versus non-cryptographically
> secure PRNGs, where CSPRNG : PRNG. “Reproducible” is not the right word.
> Based on my understanding, some CSPRNGs can be “reproducible” if the seed
> is known; what makes it cryptographically secure is that observing its
> previous *outputs* does not provide information useful to predict future
> outputs. Along those lines, it may be important to securely delete the seed
> from memory as soon as possible; there is some way of doing so in C (it’s
> used in the ChaCha20 reference implementation) but I don’t believe any way
> of doing so in Swift.
>
>
> It's possible to use a CSPRNG-grade algorithm and seed it once to get a
> reproducible sequence, but when you use it as a CSPRNG, you typically feed
> entropy back into it at nondeterministic points to ensure that even if you
> started with a bad seed, you'll eventually get to an alright state. Unless
> you keep track of when entropy was mixed in and what the values were,
> you'll never get a reproducible CSPRNG.
>
> We would give developers a false sense of security if we provided them
> with CSPRNG-grade algorithms that we called CSPRNGs and that they could
> seed themselves. Just because it says "crypto-secure" in the name doesn't
> mean that it'll be crypto-secure if it's seeded with time(). Therefore,
> "reproducible" vs "non-reproducible" looks like a good distinction to me.
>

I disagree here, in two respects:

First, whether or not a particular PRNG is cryptographically secure is an
intrinsic property of the algorithm; whether it's "reproducible" or not is
determined by the published API. In other words, the distinction between
CSPRNG vs. non-CSPRNG is important to document because it's semantics that
cannot be deduced by the user otherwise, and it is an important one for
writing secure code because it tells you whether an attacker can predict
future outputs based only on observing past outputs. "Reproducible" in the
sense of seedable or not is trivially noted by inspection of the published
API, and it is rather immaterial to writing secure code. If your attacker
can observe your seeding once, chances are that they can observe your
reseeding too; then, they can use their own implementation of the PRNG
(whether CSPRNG or non-CSPRNG) and reproduce your pseudorandom sequence
whether or not Swift exposes any particular API.

Secondly, I see no reason to justify the notion that, simply because a PRNG
is cryptographically secure, we ought to hide the seeding initializer
(because one has to exist internally anyway) from the public. Obviously,
one use case for a deterministic PRNG is to get reproducible sequences of
random-appearing values; this can be useful whether the underlying
algorithm is cryptographically secure or not. There are innumerably many
ways to use data generated from a CSPRNG in non-cryptographically secure
ways and omitting or including a public seeding initializer does not change
that; in other words, using a deterministic seed for a CSPRNG would be a
bad idea in certain applications, but it's a deliberate act, and someone
who would mistakenly do that is clearly incapable of *using* the output
from the PRNG in a secure way either; put a third way, you would be hard
pressed to find a situation where it's true that "if only Swift had not
made the seeding initializer public, this author would have written secure
code, but instead the only security hole that existed in the code was
caused by the availability of a public seeding initializer mistakenly
used." The point of having both explicitly instantiable PRNGs and a layer
of simpler APIs like "Int.random()" is so that the less experienced user
can get the "right thing" by default, and the experienced user can
customize the behavior; any user that instantiates his or her own
ChaCha20Random instance is already calling for the power user interface; it
is reasonable to expose the underlying primitive operations (such as
seeding) so long as there are legitimate uses for it.

> * On the issue of consuming entropy: a glaring underlying inconvenience in
> the API needs to be reckoned with. Sometimes, there simply isn’t enough
> entropy to generate another random number. If cryptographic security were
> not default, then it might be OK to fall back to some other method that
> produces a low-quality result. However, if we are to do the secure thing,
> we must decide whether the lack of entropy results in a call to a random
> method to (a) return nil; (b) throw; (c) fatalError; or (d) block. There is
> no way to paper over this problem; not enough entropy means you can’t get a
> random number when you want it. The debate over blocking versus
> non-blocking error, for example, held up the addition of getrandom() to
> Glibc for some time. In my proposed design, initializing a PRNG from the
> system’s secure stream of random bytes is failable; therefore, a user can
> choose how to handle the lack of entropy. However, it is desirable to have
> a thread-local CSPRNG that is used for calls, say, to Int.random(). It
> would be unfortunate if Int.random() itself was failable; however, that
> leads to an uncomfortable question: if there is insufficient entropy,
> should Int.random() block or fatalError? That seems pretty terrible too.
> However, one cannot simply write this off as an edge case: if this is to be
> a robust part of the standard library, it must do the “right” thing.
> Particularly if Swift is to be a true systems programming language and it
> must accommodate the case when a system is first booted and there is very
> little entropy.
>
>
> That's not really the case anymore these days. You're probably thinking of
> /dev/urandom vs. /dev/random.
>

I'm also talking about getrandom() on Linux, getentropy() on BSD and Linux,
and SecCopyRandomBytes() on Apple platforms.


> On Darwin, they're the same thing and never run out (see man urandom). On
> Linux, the state of the art is that you leave /dev/random alone. Don't take
> it from me, Prof. Daniel J. Bernstein
> <https://en.wikipedia.org/wiki/Daniel_J._Bernstein> wrote this
> <https://www.mail-archive.com/cryptography@randombit.net/msg04763.html> a
> while ago:
>
> Think about this for a moment: whoever wrote the /dev/random manual page
> seems to simultaneously believe that
>
>    (1) we can't figure out how to deterministically expand one 256-bit
> /dev/random output into an endless stream of unpredictable keys (this is
> what we need from urandom), but
>
>    (2) we _can_ figure out how to use a single key to safely encrypt many
> messages (this is what we need from SSL, PGP, etc.).
>
> For a cryptographer this doesn't even pass the laugh test.
>
>
> So that shouldn't be too concerning.
>

I'm fully aware of the myths surrounding /dev/urandom and /dev/random.
/dev/urandom might never run out, but it is also possible for it not to be
initialized at all, as in the case of some VM setups. In some older
versions of iOS, /dev/[u]random is reportedly sandboxed out. On systems
where it is available, it can also be deleted, since it is a file. The
point is, all of these scenarios cause an error during seeding of a CSPRNG.
The question is, how to proceed in the face of inability to access entropy.
We must do something, because we cannot therefore return a
cryptographically secure answer. Rare trapping on invocation of
Int.random() or permanently waiting for a never-to-be-initialized
/dev/urandom would be terrible to debug, but returning an optional or
throwing all the time would be verbose. How to design this API?

* What should the default CSPRNG be? There are good arguments for using a
> cryptographically secure device random. (In my proposed implementation, for
> device random, I use Security.framework on Apple platforms (because
> /dev/urandom is not guaranteed to be available due to the sandbox, IIUC).
> On Linux platforms, I would prefer to use getrandom() and avoid using file
> system APIs, but getrandom() is new and unsupported on some versions of
> Ubuntu that Swift supports. This is an issue in and of itself.) Now, a
> number of these facilities strictly limit or do not guarantee availability
> of more than a small number of random bytes at a time; they are recommended
> for seeding other PRNGs but *not* as a routine source of random numbers.
> Therefore, although device random should be available to users, it probably
> shouldn’t be the default for the Swift standard library as it could have
> negative consequences for the system as a whole. There follows the
> significant task of implementing a CSPRNG correctly and securely for the
> default PRNG.
>
>
> Theo give a talk a few years ago
> <https://www.youtube.com/watch?v=aWmLWx8ut20> on randomness and how these
> problems are approached in LibreSSL.
>

Certainly, we can learn a lot from those like Theo who've dealt with the
issue. I'm not in a position to watch the talk at the moment; can you
summarize what the tl;dr version of it is?
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170926/82553074/attachment.html>


More information about the swift-evolution mailing list