ThreadLocalRandom clinit troubles

Peter Levart peter.levart at gmail.com
Tue Jun 24 14:03:17 UTC 2014


Hi Martin,

On 06/22/2014 07:12 PM, Martin Buchholz wrote:
> We know that loading the networking machinery is problematic.  On Linux we
> would be content to hard-code a read from /dev/urandom, which is safer and
> strictly more random than the existing network hardware determination, but
> y'all will reject that as too system-dependent (insufficient machinery!).
> Hmmmm .... maybe not .... as long as we code up a good fallback ...
>
> I learned that SecureRandom by default on Unix uses /dev/random for "seed
> bytes" and /dev/urandom for nextBytes.
>
> Here's my proposal, that in the default case on Unix doesn't load any
> machinery, and as a fallback loads the SecureRandom machinery instead of
> the network machinery, while maintaining the ultra-secure behavior of the
> java.util.secureRandomSeed system property:
>
>
>      private static long initialSeed() {
>          byte[] seedBytes = initialSeedBytes();
>          long s = (long)(seedBytes[0]) & 0xffL;
>          for (int i = 1; i < seedBytes.length; ++i)
>              s = (s << 8) | ((long)(seedBytes[i]) & 0xffL);
>          return s ^ mix64(System.currentTimeMillis()) ^
> mix64(System.nanoTime());
>      }
>
>      private static byte[] initialSeedBytes() {
>          String pp = java.security.AccessController.doPrivileged(
>                  new sun.security.action.GetPropertyAction(
>                          "java.util.secureRandomSeed"));
>          boolean secureRandomSeed = (pp != null &&
> pp.equalsIgnoreCase("true"));
>          if (secureRandomSeed)
>              return java.security.SecureRandom.getSeed(8);
>          final byte[] seedBytes = new byte[8];
>          File seedSource = new File("/dev/urandom");
>          if (seedSource.exists()) {
>              try (FileInputStream stream = new FileInputStream(seedSource)) {
>                  if (stream.read(seedBytes) == 8)
>                      return seedBytes;
>              } catch (IOException ignore) { }
>          }
>          new java.security.SecureRandom().nextBytes(seedBytes);
>          return seedBytes;
>      }

So on platforms lacking "/dev/urandom" special file (Windows only?), the 
fall-back would be to use SecureRandom.nextBytes() whatever default 
SecureRandom PRNG is configured to be on such platform. This might be a 
user-supplied SecureRandom PRNG. The user might want it's own default 
SecureRandom but not use it for TLR's seed computation (unless requested 
by "java.util.secureRandomSeed" system property).

I would rather use SecureRandom.generateSeed() instance method instead 
of SecureRandom.nextBytes(). Why? Because every SecureRandom instance 
has to initialize it's seed 1st before getBytes() can provide the next 
random bytes from the PRNG. Since we only need the 1st 8 bytes from the 
SecureRandom instance to initialize TLR's seeder, we might as well 
directly call the SecureRandom.generateSeed() method. That's one reason. 
The other is the peculiar initialization of default SecureRandom 
algorithm on Windows (see below)...

Even if user does not provide it's own default SecureRandom PRNG, there 
are basically two algorithms that are used by default on OpenJDK:

On Solaris/Linux/Mac/AIX:

the "NativePRNG" algorithm from "SUN" provider (implemented by 
sun.security.provider.NativePRNG) which uses /dev/random for getBytes() 
and /dev/urandom (or whatever is configured with "java.security.egd" or 
"securerandom.source" system properties) for generateSeed(), and

On Windows:

the "SHA1PRNG" algorithm from "SUN" provider (implemented by 
sun.security.provider.SecureRandom) which uses SHA1 message digest for 
generating random numbers with seed computed by gathering system entropy...


The most problematic one is the default on Windows platform (the 
platform that does not have the "/dev/urandom" special file and would be 
used as a fall-back by your proposal) - 
sun.security.provider.SecureRandom. This one seeds itself by 
constructing an instance of itself with the result returned from 
SeedGenerator.getSystemEntropy() method. This method, among other 
things, uses networking code to gather system entropy:

                         ...
                         md.update
(InetAddress.getLocalHost().toString().getBytes());
                         ...

This is problematic since it not only initializes NameService providers 
but also uses them to resolve local host name. This can block for 
several seconds on unusual configurations and was the main motivation to 
replace similar code in TLR with code that uses 
NetworkInterface.getHardwareAddress() instead. Using 
SecureRandom.generateSeed() instead of SecureRandom.getBytes() does not 
invoke SeedGenerator.getSystemEntropy(), but uses just 
SeedGenerator.generateSeed(), which by default on Windows uses 
ThreadedSeedGenerator.getSeedBytes()...

I showed how we could suppress NameService providers initialization 
while still using NetworkInterface.getHardwareAddress() for TLR's seeder 
initialization. If that's not enough and we would like to get-away 
without using networking code at all, then I propose the following:


     private static byte[] initialSeedBytes() {

         String secureRandomSeed = 
java.security.AccessController.doPrivileged(
             new sun.security.action.GetPropertyAction(
                 "java.util.secureRandomSeed", ""))
             .toLowerCase(Locale.ROOT);

         String osName = java.security.AccessController.doPrivileged(
             new sun.security.action.GetPropertyAction(
                 "os.name"))
             .toLowerCase(Locale.ROOT);

         if (!secureRandomSeed.equals("true") && 
!secureRandomSeed.equals("blocking")) {
             secureRandomSeed = "nonblocking"; // default
         }

         SecureRandom srnd = null;

         if (secureRandomSeed.equals("nonblocking")) { // the default
             try {
                 if (osName.startsWith("windows")) {
                     // native implementation using MSCAPI implemented by
                     // sun.security.mscapi.PRNG
                     srnd = SecureRandom.getInstance("Windows-PRNG", 
"SunMSCAPI");
                 } else { // Solaris/Linux/Mac/AIX
                     // a non-blocking native implementation using 
/dev/urandom for both
                     // generateSeed() and nextBytes() implemented by
                     // sun.security.provider.NativePRNG$NonBlocking
                     srnd = 
SecureRandom.getInstance("NativePRNGNonBlocking", "SUN");
                 }
             } catch (NoSuchProviderException | NoSuchAlgorithmException 
ignore) {}
         } else if (secureRandomSeed.equals("blocking")) {
             try {
                 if (osName.startsWith("windows")) {
                     // native implementation using MSCAPI implemented by
                     // sun.security.mscapi.PRNG
                     srnd = SecureRandom.getInstance("Windows-PRNG", 
"SunMSCAPI");
                 } else { // Solaris/Linux/Mac/AIX
                     // a blocking native implementation using 
/dev/random for both
                     // generateSeed() and nextBytes() implemented by
                     // sun.security.provider.NativePRNG$Blocking
                     srnd = 
SecureRandom.getInstance("NativePRNGBlocking", "SUN");
                 }
             } catch (NoSuchProviderException | NoSuchAlgorithmException 
ignore) {}
         } else {
             assert secureRandomSeed.equals("true");
         }

         if (srnd == null) { // fall back to default SecureRandom 
algorithm / provider
             srnd = new SecureRandom();
         }

         return srnd.generateSeed(8);
     }


By default (or when "java.util.secureRandomSeed" is set to "nonblocking" 
or unrecognized value) this would use /dev/urandom on UNIX-es and MSCAPI 
on Windows. More entropy for TLR's initial seeder could be provided on 
UNIX-es by risking some blocking with "java.util.secureRandomSeed" set 
to "blocking". This would still be independend of user's choice of 
default SecureRandom provider. The backward-compatible 
("java.util.secureRandomSeed" set to "true") or fall-back would be to 
use default SecureRandom algorithm / provider.

Regards, Peter

>
>
>
> On Sat, Jun 21, 2014 at 9:05 PM, Martin Buchholz <martinrb at google.com>
> wrote:
>
>> While looking at NativePRNG, I filed
>>
>> https://bugs.openjdk.java.net/browse/JDK-8047769
>>
>> SecureRandom should be more frugal with file descriptors
>>
>> If I run this java program on Linux
>>
>> public class SecureRandoms {
>>      public static void main(String[] args) throws Throwable {
>>          new java.security.SecureRandom();
>>      }
>> }
>>
>> it creates 6 file descriptors for /dev/random and /dev/urandom, as shown
>> by:
>>
>> strace -q -ff -e open java SecureRandoms |& grep /dev/
>> [pid 20769] open("/dev/random", O_RDONLY) = 5
>> [pid 20769] open("/dev/urandom", O_RDONLY) = 6
>> [pid 20769] open("/dev/random", O_RDONLY) = 7
>> [pid 20769] open("/dev/random", O_RDONLY) = 8
>> [pid 20769] open("/dev/urandom", O_RDONLY) = 9
>> [pid 20769] open("/dev/urandom", O_RDONLY) = 10
>>
>> Looking at jdk/src/solaris/classes/sun/security/provider/NativePRNG.java
>> it looks like 2 file descriptors are created for every variant of
>> NativePRNG, whether or not they are ever used. Which is wasteful. In fact,
>> you only ever need at most two file descriptors, one for /dev/random and
>> one for /dev/urandom.
>>
>> Further, it would be nice if the file descriptors were closed when idle
>> and lazily re-created. Especially /dev/random should typically be used at
>> startup and never thereafter.
>>
>>
>> On Fri, Jun 20, 2014 at 7:59 AM, Alan Bateman <Alan.Bateman at oracle.com>
>> wrote:
>>
>>> On 20/06/2014 15:02, Peter Levart wrote:
>>>
>>>> And, as Martin pointed out, it seems to be used for tests that exercise
>>>> particular responses from NameService API to test the behaviour of JDK
>>>> classes. It would be a shame for those tests to go away.
>>>>
>>> We've been talking about removing it for many years because it has been
>>> so troublesome. If we really need to having something for testing then I
>>> don't think it needs to be general purpose, we can get right of the lookup
>>> at least.
>>>
>>> -Alan.
>>>
>>



More information about the security-dev mailing list