I'm working on a cleanup of the SecureRandom implementations, and found the previous evaluation to be not very helpful.
The fix for this bug was:
Added a new algorithm in the Sun provider called NativePRNG.java on Solaris/Linux (no Windows), which calls into /dev/random and /dev/urandom depending on whether seed or nextBytes() are needed (respectively). It currently (Jan 2012) does not take into account java.security.egd/securerandom.source, except when starting up the Sun provider: if the value is "file:/dev/urandom", this provider is placed before SHA1PRNG. If not, then it goes in after SHA1PRNG.
engineSetSeed(byte seed): First tries to to write to /dev/random. This file is typically owned by root, so in addition, it also (re)creates SHA1PRNG (seeding with /dev/urandom if needed), then with seed parameter. This SHA1PRNG is used by later nextBytes calls.
engineGenerateSeed(int numbBytes): Does a direct read on /dev/random.
engineNextBytes(byte bytes): Using the same code called by setSeed (getMixRandom), it (re)creates a SHA1PRNG (seeding from /dev/urandom if needed). It then reads from /dev/urandom and XOR's with data from the SHA1PRNG mixRandom.
In SHA1PRNG, there is a SeedGenerator which does various things depending on the configuration.
1. If java.security.egd or securerandom.source point to "file:/dev/random" or "file:/dev/urandom", we will use NativeSeedGenerator, which calls super() which calls SeedGenerator.URLSeedGenerator(/dev/random). (A nested class within SeedGenerator.) The only things that changed in this bug was that urandom will also trigger use of this code path.
2. If those properties point to another URL that exists, we'll initialize SeedGenerator.URLSeedGenerator(url). This is why "file:///dev/urandom
", "file:/./dev/random", etc. will work.
3. If neither were successful, then we'll fall back to the ThreadedSeedGenerator, which does the measurement heuristic.
Calls to SeedGenerator.URLSeedGenerator will then read from the file and provide bytes.
Since we're here, what does SHA1PRNG do?
engineSetSeed(byte seed): If there is existing state, it will put that state back into the SHA1 digest, add the seed to it, cdigest() and store new value to the state.
engineGenerateSeed(int numBytes): Calls directly to SeedGenerator.generateSeed(b), which reads from from whatever the seed generator points to. Recall that if it's one of the two reserved strings, it will go to /dev/random.
engineNextBytes(int num): If there has been no state assigned yet, it needs to call the SeedGenerator to get something. It does this by calling SeedGenerator.getSystemEntropy(), which creates a SHA1, then adds in a bunch of system dependent values (time, properties, hostname, tmpdir file names, memory amounts, etc).
This SHA value is then used to create/seed a *DIFFERENT* SecureRandom implementation. This *DIFFERENT* SecureRandom impl is then additionally seeded again by a call to *THIS* instance's engineGenerateSeed(b) call, which goes to whatever URL/method was established in the logic above. So if an app calls "new SecureRandom().nextBytes()", it will get into the /dev/random here. If an app calls "new SecureRandom().setSeed(b).nextBytes()" it will short circuit the need to go initialize the seeder, and thus won't go off to /dev/random.
Then it adds the existing state back to SHA1 digest, then digests it again, doing some manipulations between the old and new state, and then outputs the result.
What a confusing mess.