Update #5: JEP 123: SecureRandom Draft and Implementation.
Brad Wetmore
bradford.wetmore at oracle.com
Wed Apr 10 22:07:07 UTC 2013
Hi Florian,
> I wonder if this change to src/share/lib/security/java.security-linux
>
> -securerandom.source=file:/dev/urandom
> +securerandom.source=file:/dev/random
>
> causes the return of the blocking behavior.
Welcome to my "can of worms." [1] I hope everything I've said below is
correct, and haven't made any typos!
> In the past, I saw
> /dev/random-related blocking during server start-up because too many
> SecureRandom instances needed seeding. If I follow the code
> correctly,
If you came to this code fresh and understood it all in 7 hours, I would
either give you a medal, or hand in my badge. :) It's really twisted
stuff.
> seeding of non-strong generators now uses /dev/random again, which is
> subject to blocking.
Given the long history of the JDK and concerns about backwards
compatibility, our SecureRandom implementations have become *REALLY*
hard to understand. Trying to make sense of all this, and now cleaning
it up, was actually more than 1/2 of the project. It took me a couple
weeks of intense code scrutiny/archeology, and then needed to write a
wiki page just to keep my head wrapped around it. Likely the most
confusing issue I've ever worked on.
In a nutshell, the current implementation of SHA1PRNG does indeed read
from /dev/random while seeding itself and supplying bytes via
generateSeed(), which is in opposition to the documented behavior of the
security properties: Remember the infamous "file:/dev/./urandom"
workaround for SHA1PRNG? [2] (sigh...)
I'll try to simplify as much as I can. I'll be including parts of this
for the JDK 8 Oracle's Provider Impl page.
In the current environment, here's our current list of providers, in
default preference order.
Solaris:
PKCS11/NativePRNG/SHA1PRNG
Linux/Mac:
NativePRNG/SHA1PRNG
Windows:
SHA1PRNG/Windows-PRNG
As you probably know, "new SecureRandom()" obtains the first
implementation in the list, unless you call
"SecureRandom.getInstance("alg") or SecureRandom.getInstance("alg",
"provider"), which gets a specific algorithm, either most preferred
version or by a specific provider, respectively.
The securerandom.source/java.security.egd currently only affects
SHA1PRNG, but there's a slight wrinkle in that certain property values
promote NativePRNG ahead of SHA1PRNG in the list of algorithms in the
SUN provider. More on this later.
Current behavior:
=================
NativePRNG:
-----------
does not use securerandom.source/java.security.egd at all
engineGenerateSeed: always reads from /dev/random
engineNextBytes: always reads from /dev/urandom
SHA1PRNG:
---------
Since this is a real PRNG [3], SHA1PRNG requires a very strong seed
(initial) value, as strong as possible. If not initially seeded by the
user via setSeed, SHA1PRNG will call SeedGenerator.getSystemEntropy +
SeedGenerator.generateSeed() to generate a strong seed for itself.
There are three seed generators which have SeedGenerator as their parent:
ThreadedSeedGenerator was the original Seeder which uses
system load to generate seed values, and is only used as a
last resort. (SLOW!)
URLSeedGenerator takes a URL and reads from it.
NativeSeedGenerator uses /dev/random.
When the SeedGenerator class initializes, if the properties
(securerandom.source/java.security.egd) resolve to either the exact
strings "file:/dev/random" or "file:/dev/urandom", it will use
NativeSeedGenerator (/dev/random). (Yeah, try explaining that to
customers!) If the properties resolve to any other URL, then we use
URLSeedGenerator with that URL. Note, this is why the infamous
workaround "file:///dev/urandom" or "file:/dev/./urandom" works. [2]
As someone once said, this has got to be the most confusing workaround
in the history of the JDK. I would agree! :) If neither SeedGenerator
is available, then we use ThreadedSeedGenerator as the last resort.
Once seeded, then later calls to this SHA1PRNG SecureRandom instance are:
engineGenerateSeed: always reads from SeedGenerator
engineNextBytes: Number generation using the current state as
input into SHA1.
So even though the EGD properties point to file:/dev/urandom, it's not
correct when it comes to the seeder.
Back in JDK 5, adding NativePRNG was the "workaround" for this problem.
When setting up the default order of implementations in the SUN
provider, if the properties URL is "file:/dev/urandom", then NativePRNG
becomes the most preferred provider (over SHA1PRNG). So for those Linux
implementations which just asked for the "most preferred" (i.e. new
SecureRandom()), they get an instance of NativePRNG which uses
/dev/urandom for nextBytes. (Note well:
NativeSeedGenerator.generateSeed() still reads from /dev/random).
So since you're not seeding a SHA1PRNG, using NativePRNG.nextBytes()
makes it appear as though there is no more stalling, as long as you only
call nextBytes() and not generateSeed(). But if you specifically asked
for SHA1PRNG (which a lot of customers had baked into their code), you
still go through the above, and will stall when SHA1PRNG tried to seed
itself.
(Again, try to explain this to customers. We've had many escalations
over the years!)
PKCS11/Windows-PRNG:
--------------------
Didn't touch. Their behavior doesn't allow have the concept of a EGD.
New Behavior:
=============
So, what do my change do?
. Corrects the java.security files to reflect the new implementation,
and corrects vagueness/long-standing errors. In particular, the
security property default becomes "file:/dev/random" which matches the
old AND new implementations, which is what triggered this long discussion!
. SUN provider: If the properties are *EITHER* "file:/dev/random" or
"file:/dev/urandom", then it promotes NativePRNG ahead of SHA1PRNG in
the list of preferred impls. If it's not one of those, then SHA1PRNG is
preferred over NativePRNG.
. NativePRNG: Now respects the seeder properties. If the property
isn't "file:/" or doesn't exist, we'll fall back to /dev/random.
. Adds blocking and nonblocking variants of NativePRNG:
NativePRNGBlocking/NativePRNGJNonBlocking. The
generateSeed()/nextBytes() methods both use /dev/random or /dev/urandom,
respectively.
. NativeSeedGenerator: essentially, this becomes the same as a
URLSeedGenerator. Instead of only using "/dev/random", it will the
specified value of "file:/dev/random" or "file:/dev/urandom"
. Adds SecureRandom.getStrongSecureRandom. Uses a security property to
specify the strongest impl(s) on this system. See the API for more details.
. Minor style cleanups
I hope this is clear.
Brad
[1] https://www.google.com/search?q=define%3A+can+of+worms
[2]
http://security.stackexchange.com/questions/14386/what-do-i-need-to-configure-to-make-sure-my-software-uses-dev-urandom
[3] http://en.wikipedia.org/wiki/Pseudorandom_number_generator
More information about the security-dev
mailing list