You need to sign in or sign up before continuing.
SeedGenerator.java 20.9 KB
Newer Older
D
duke 已提交
1
/*
2
 * Copyright (c) 1996, 2014, Oracle and/or its affiliates. All rights reserved.
D
duke 已提交
3 4 5 6
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
7
 * published by the Free Software Foundation.  Oracle designates this
D
duke 已提交
8
 * particular file as subject to the "Classpath" exception as provided
9
 * by Oracle in the LICENSE file that accompanied this code.
D
duke 已提交
10 11 12 13 14 15 16 17 18 19 20
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
21 22 23
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
D
duke 已提交
24 25 26 27 28
 */

package sun.security.provider;

/**
29 30 31 32
 * This class generates seeds for the SHA1PRNG cryptographically strong
 * random number generator.
 * <p>
 * The seed is produced using one of two techniques, via a computation
D
duke 已提交
33
 * of current system activity or from an entropy gathering device.
34 35
 * <p>
 * In the default technique the seed is produced by counting the
D
duke 已提交
36 37 38 39 40 41 42 43 44 45
 * number of times the VM manages to loop in a given period. This number
 * roughly reflects the machine load at that point in time.
 * The samples are translated using a permutation (s-box)
 * and then XORed together. This process is non linear and
 * should prevent the samples from "averaging out". The s-box
 * was designed to have even statistical distribution; it's specific
 * values are not crucial for the security of the seed.
 * We also create a number of sleeper threads which add entropy
 * to the system by keeping the scheduler busy.
 * Twenty such samples should give us roughly 160 bits of randomness.
46 47
 * <p>
 * These values are gathered in the background by a daemon thread
D
duke 已提交
48 49
 * thus allowing the system to continue performing it's different
 * activites, which in turn add entropy to the random seed.
50 51
 * <p>
 * The class also gathers miscellaneous system information, some
D
duke 已提交
52 53
 * machine dependent, some not. This information is then hashed together
 * with the 20 seed bytes.
54 55
 * <p>
 * The alternative to the above approach is to acquire seed material
D
duke 已提交
56
 * from an entropy gathering device, such as /dev/random. This can be
57 58 59 60 61
 * accomplished by setting the value of the {@code securerandom.source}
 * Security property to a URL specifying the location of the entropy
 * gathering device, or by setting the {@code java.security.egd} System
 * property.
 * <p>
D
duke 已提交
62
 * In the event the specified URL cannot be accessed the default
63
 * threading mechanism is used.
D
duke 已提交
64 65 66 67 68 69 70 71 72 73
 *
 * @author Joshua Bloch
 * @author Gadi Guy
 */

import java.security.*;
import java.io.*;
import java.util.Properties;
import java.util.Enumeration;
import java.net.*;
74 75 76
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
77
import java.util.Random;
D
duke 已提交
78 79 80 81 82 83 84 85 86 87 88 89 90
import sun.security.util.Debug;

abstract class SeedGenerator {

    // Static instance is created at link time
    private static SeedGenerator instance;

    private static final Debug debug = Debug.getInstance("provider");

    // Static initializer to hook in selected or best performing generator
    static {
        String egdSource = SunEntries.getSeedSource();

91 92 93 94 95 96 97 98 99 100 101 102 103
        /*
         * Try the URL specifying the source (e.g. file:/dev/random)
         *
         * The URLs "file:/dev/random" or "file:/dev/urandom" are used to
         * indicate the SeedGenerator should use OS support, if available.
         *
         * On Windows, this causes the MS CryptoAPI seeder to be used.
         *
         * On Solaris/Linux/MacOS, this is identical to using
         * URLSeedGenerator to read from /dev/[u]random
         */
        if (egdSource.equals(SunEntries.URL_DEV_RANDOM) ||
                egdSource.equals(SunEntries.URL_DEV_URANDOM)) {
D
duke 已提交
104
            try {
105
                instance = new NativeSeedGenerator(egdSource);
D
duke 已提交
106
                if (debug != null) {
107 108
                    debug.println(
                        "Using operating system seed generator" + egdSource);
D
duke 已提交
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123
                }
            } catch (IOException e) {
                if (debug != null) {
                    debug.println("Failed to use operating system seed "
                                  + "generator: " + e.toString());
                }
            }
        } else if (egdSource.length() != 0) {
            try {
                instance = new URLSeedGenerator(egdSource);
                if (debug != null) {
                    debug.println("Using URL seed generator reading from "
                                  + egdSource);
                }
            } catch (IOException e) {
124
                if (debug != null) {
D
duke 已提交
125 126
                    debug.println("Failed to create seed generator with "
                                  + egdSource + ": " + e.toString());
127
                }
D
duke 已提交
128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
            }
        }

        // Fall back to ThreadedSeedGenerator
        if (instance == null) {
            if (debug != null) {
                debug.println("Using default threaded seed generator");
            }
            instance = new ThreadedSeedGenerator();
        }
    }

    /**
     * Fill result with bytes from the queue. Wait for it if it isn't ready.
     */
    static public void generateSeed(byte[] result) {
        instance.getSeedBytes(result);
    }

147
    abstract void getSeedBytes(byte[] result);
D
duke 已提交
148 149 150 151 152 153 154 155 156 157 158

    /**
     * Retrieve some system information, hashed.
     */
    static byte[] getSystemEntropy() {
        byte[] ba;
        final MessageDigest md;

        try {
            md = MessageDigest.getInstance("SHA");
        } catch (NoSuchAlgorithmException nsae) {
159 160
            throw new InternalError("internal error: SHA-1 not available."
                    , nsae);
D
duke 已提交
161 162 163 164 165 166 167 168
        }

        // The current time in millis
        byte b =(byte)System.currentTimeMillis();
        md.update(b);

        java.security.AccessController.doPrivileged
            (new java.security.PrivilegedAction<Void>() {
169
                @Override
D
duke 已提交
170 171 172 173 174 175 176 177 178 179 180 181
                public Void run() {
                    try {
                        // System properties can change from machine to machine
                        String s;
                        Properties p = System.getProperties();
                        Enumeration<?> e = p.propertyNames();
                        while (e.hasMoreElements()) {
                            s =(String)e.nextElement();
                            md.update(s.getBytes());
                            md.update(p.getProperty(s).getBytes());
                        }

182 183
                        // Include network adapter names (and a Mac address)
                        addNetworkAdapterInfo(md);
D
duke 已提交
184 185 186

                        // The temporary dir
                        File f = new File(p.getProperty("java.io.tmpdir"));
187
                        int count = 0;
188 189 190
                        try (
                            DirectoryStream<Path> stream =
                                Files.newDirectoryStream(f.toPath())) {
191 192 193 194 195 196
                            // We use a Random object to choose what file names
                            // should be used. Otherwise on a machine with too
                            // many files, the same first 1024 files always get
                            // used. Any, We make sure the first 512 files are
                            // always used.
                            Random r = new Random();
197 198
                            for (Path entry: stream) {
                                if (count < 512 || r.nextBoolean()) {
199 200
                                    md.update(entry.getFileName()
                                        .toString().getBytes());
201 202 203
                                }
                                if (count++ > 1024) {
                                    break;
204 205 206
                                }
                            }
                        }
D
duke 已提交
207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223
                    } catch (Exception ex) {
                        md.update((byte)ex.hashCode());
                    }

                    // get Runtime memory stats
                    Runtime rt = Runtime.getRuntime();
                    byte[] memBytes = longToByteArray(rt.totalMemory());
                    md.update(memBytes, 0, memBytes.length);
                    memBytes = longToByteArray(rt.freeMemory());
                    md.update(memBytes, 0, memBytes.length);

                    return null;
                }
            });
        return md.digest();
    }

224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248
    /*
     * Include network adapter names and, if available, a Mac address
     *
     * See also java.util.concurrent.ThreadLocalRandom.initialSeed()
     */
    private static void addNetworkAdapterInfo(MessageDigest md) {

        try {
            Enumeration<NetworkInterface> ifcs =
                NetworkInterface.getNetworkInterfaces();
            while (ifcs.hasMoreElements()) {
                NetworkInterface ifc = ifcs.nextElement();
                md.update(ifc.toString().getBytes());
                if (!ifc.isVirtual()) { // skip fake addresses
                    byte[] bs = ifc.getHardwareAddress();
                    if (bs != null) {
                        md.update(bs);
                        break;
                    }
                }
            }
        } catch (Exception ignore) {
        }
    }

D
duke 已提交
249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271
    /**
     * Helper function to convert a long into a byte array (least significant
     * byte first).
     */
    private static byte[] longToByteArray(long l) {
        byte[] retVal = new byte[8];

        for (int i=0; i<8; i++) {
            retVal[i] = (byte) l;
            l >>= 8;
        }

        return retVal;
    }

    /*
    // This method helps the test utility receive unprocessed seed bytes.
    public static int genTestSeed() {
        return myself.getByte();
    }
    */


272 273
    private static class ThreadedSeedGenerator extends SeedGenerator
            implements Runnable {
D
duke 已提交
274 275 276 277 278 279 280 281
        // Queue is used to collect seed bytes
        private byte[] pool;
        private int start, end, count;

        // Thread group for our threads
        ThreadGroup seedGroup;

        /**
282 283 284 285
         * The constructor is only called once to construct the one
         * instance we actually use. It instantiates the message digest
         * and starts the thread going.
         */
D
duke 已提交
286 287 288 289 290 291 292 293 294
        ThreadedSeedGenerator() {
            pool = new byte[20];
            start = end = 0;

            MessageDigest digest;

            try {
                digest = MessageDigest.getInstance("SHA");
            } catch (NoSuchAlgorithmException e) {
295 296
                throw new InternalError("internal error: SHA-1 not available."
                        , e);
D
duke 已提交
297 298 299 300 301
            }

            final ThreadGroup[] finalsg = new ThreadGroup[1];
            Thread t = java.security.AccessController.doPrivileged
                (new java.security.PrivilegedAction<Thread>() {
302
                        @Override
D
duke 已提交
303 304 305
                        public Thread run() {
                            ThreadGroup parent, group =
                                Thread.currentThread().getThreadGroup();
306
                            while ((parent = group.getParent()) != null) {
D
duke 已提交
307
                                group = parent;
308
                            }
D
duke 已提交
309 310 311
                            finalsg[0] = new ThreadGroup
                                (group, "SeedGenerator ThreadGroup");
                            Thread newT = new Thread(finalsg[0],
312 313
                                ThreadedSeedGenerator.this,
                                "SeedGenerator Thread");
D
duke 已提交
314 315 316 317 318 319 320 321 322 323 324 325 326
                            newT.setPriority(Thread.MIN_PRIORITY);
                            newT.setDaemon(true);
                            return newT;
                        }
                    });
            seedGroup = finalsg[0];
            t.start();
        }

        /**
         * This method does the actual work. It collects random bytes and
         * pushes them into the queue.
         */
327
        @Override
D
duke 已提交
328 329 330 331 332
        final public void run() {
            try {
                while (true) {
                    // Queue full? Wait till there's room.
                    synchronized(this) {
333
                        while (count >= pool.length) {
D
duke 已提交
334
                            wait();
335
                        }
D
duke 已提交
336 337 338 339 340 341
                    }

                    int counter, quanta;
                    byte v = 0;

                    // Spin count must not be under 64000
342 343
                    for (counter = quanta = 0;
                            (counter < 64000) && (quanta < 6); quanta++) {
D
duke 已提交
344 345 346 347 348 349 350 351 352

                        // Start some noisy threads
                        try {
                            BogusThread bt = new BogusThread();
                            Thread t = new Thread
                                (seedGroup, bt, "SeedGenerator Thread");
                            t.start();
                        } catch (Exception e) {
                            throw new InternalError("internal error: " +
353
                                "SeedGenerator thread creation error.", e);
D
duke 已提交
354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376
                        }

                        // We wait 250milli quanta, so the minimum wait time
                        // cannot be under 250milli.
                        int latch = 0;
                        long l = System.currentTimeMillis() + 250;
                        while (System.currentTimeMillis() < l) {
                            synchronized(this){};
                            latch++;
                        }

                        // Translate the value using the permutation, and xor
                        // it with previous values gathered.
                        v ^= rndTab[latch % 255];
                        counter += latch;
                    }

                    // Push it into the queue and notify anybody who might
                    // be waiting for it.
                    synchronized(this) {
                        pool[end] = v;
                        end++;
                        count++;
377
                        if (end >= pool.length) {
D
duke 已提交
378
                            end = 0;
379
                        }
D
duke 已提交
380 381 382 383 384 385

                        notifyAll();
                    }
                }
            } catch (Exception e) {
                throw new InternalError("internal error: " +
386
                    "SeedGenerator thread generated an exception.", e);
D
duke 已提交
387 388 389
            }
        }

390 391 392 393 394 395 396
        @Override
        void getSeedBytes(byte[] result) {
            for (int i = 0; i < result.length; i++) {
                result[i] = getSeedByte();
            }
        }

D
duke 已提交
397
        byte getSeedByte() {
398
            byte b;
D
duke 已提交
399 400 401 402

            try {
                // Wait for it...
                synchronized(this) {
403
                    while (count <= 0) {
D
duke 已提交
404
                        wait();
405
                    }
D
duke 已提交
406 407
                }
            } catch (Exception e) {
408
                if (count <= 0) {
D
duke 已提交
409
                    throw new InternalError("internal error: " +
410 411
                        "SeedGenerator thread generated an exception.", e);
                }
D
duke 已提交
412 413 414 415 416 417 418 419
            }

            synchronized(this) {
                // Get it from the queue
                b = pool[start];
                pool[start] = 0;
                start++;
                count--;
420
                if (start == pool.length) {
D
duke 已提交
421
                    start = 0;
422
                }
D
duke 已提交
423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470

                // Notify the daemon thread, just in case it is
                // waiting for us to make room in the queue.
                notifyAll();
            }

            return b;
        }

        // The permutation was calculated by generating 64k of random
        // data and using it to mix the trivial permutation.
        // It should be evenly distributed. The specific values
        // are not crucial to the security of this class.
        private static byte[] rndTab = {
            56, 30, -107, -6, -86, 25, -83, 75, -12, -64,
            5, -128, 78, 21, 16, 32, 70, -81, 37, -51,
            -43, -46, -108, 87, 29, 17, -55, 22, -11, -111,
            -115, 84, -100, 108, -45, -15, -98, 72, -33, -28,
            31, -52, -37, -117, -97, -27, 93, -123, 47, 126,
            -80, -62, -93, -79, 61, -96, -65, -5, -47, -119,
            14, 89, 81, -118, -88, 20, 67, -126, -113, 60,
            -102, 55, 110, 28, 85, 121, 122, -58, 2, 45,
            43, 24, -9, 103, -13, 102, -68, -54, -101, -104,
            19, 13, -39, -26, -103, 62, 77, 51, 44, 111,
            73, 18, -127, -82, 4, -30, 11, -99, -74, 40,
            -89, 42, -76, -77, -94, -35, -69, 35, 120, 76,
            33, -73, -7, 82, -25, -10, 88, 125, -112, 58,
            83, 95, 6, 10, 98, -34, 80, 15, -91, 86,
            -19, 52, -17, 117, 49, -63, 118, -90, 36, -116,
            -40, -71, 97, -53, -109, -85, 109, -16, -3, 104,
            -95, 68, 54, 34, 26, 114, -1, 106, -121, 3,
            66, 0, 100, -84, 57, 107, 119, -42, 112, -61,
            1, 48, 38, 12, -56, -57, 39, -106, -72, 41,
            7, 71, -29, -59, -8, -38, 79, -31, 124, -124,
            8, 91, 116, 99, -4, 9, -36, -78, 63, -49,
            -67, -87, 59, 101, -32, 92, 94, 53, -41, 115,
            -66, -70, -122, 50, -50, -22, -20, -18, -21, 23,
            -2, -48, 96, 65, -105, 123, -14, -110, 69, -24,
            -120, -75, 74, 127, -60, 113, 90, -114, 105, 46,
            27, -125, -23, -44, 64
        };

        /**
         * This inner thread causes the thread scheduler to become 'noisy',
         * thus adding entropy to the system load.
         * At least one instance of this class is generated for every seed byte.
         */
        private static class BogusThread implements Runnable {
471
            @Override
D
duke 已提交
472 473
            final public void run() {
                try {
474
                    for (int i = 0; i < 5; i++) {
D
duke 已提交
475
                        Thread.sleep(50);
476
                    }
D
duke 已提交
477 478 479 480 481 482 483 484 485 486
                    // System.gc();
                } catch (Exception e) {
                }
            }
        }
    }

    static class URLSeedGenerator extends SeedGenerator {

        private String deviceName;
487
        private InputStream seedStream;
D
duke 已提交
488 489 490 491 492 493 494 495

        /**
         * The constructor is only called once to construct the one
         * instance we actually use. It opens the entropy gathering device
         * which will supply the randomness.
         */

        URLSeedGenerator(String egdurl) throws IOException {
496
        if (egdurl == null) {
D
duke 已提交
497 498 499 500 501 502 503 504
                throw new IOException("No random source specified");
            }
            deviceName = egdurl;
            init();
        }

        private void init() throws IOException {
            final URL device = new URL(deviceName);
505
            try {
506
                seedStream = java.security.AccessController.doPrivileged
507
                    (new java.security.PrivilegedExceptionAction<InputStream>() {
508
                        @Override
509 510 511 512 513 514 515 516 517 518
                        public InputStream run() throws IOException {
                            /*
                             * return a FileInputStream for file URLs and
                             * avoid buffering. The openStream() call wraps
                             * InputStream in a BufferedInputStream which
                             * can buffer up to 8K bytes. This read is a
                             * performance issue for entropy sources which
                             * can be slow to replenish.
                             */
                            if (device.getProtocol().equalsIgnoreCase("file")) {
519 520
                                File deviceFile =
                                    SunEntries.getDeviceFile(device);
521 522 523
                                return new FileInputStream(deviceFile);
                            } else {
                                return device.openStream();
D
duke 已提交
524 525 526
                            }
                        }
                    });
527
            } catch (Exception e) {
528 529
                throw new IOException(
                    "Failed to open " + deviceName, e.getCause());
D
duke 已提交
530 531 532
            }
        }

533 534 535 536
        @Override
        void getSeedBytes(byte[] result) {
            int len = result.length;
            int read = 0;
D
duke 已提交
537
            try {
538
                while (read < len) {
539
                    int count = seedStream.read(result, read, len - read);
540
                    // /dev/random blocks - should never have EOF
541 542 543 544 545
                    if (count < 0) {
                        throw new InternalError(
                            "URLSeedGenerator " + deviceName +
                            " reached end of file");
                    }
546 547
                    read += count;
                }
D
duke 已提交
548 549
            } catch (IOException ioe) {
                throw new InternalError("URLSeedGenerator " + deviceName +
550
                    " generated exception: " + ioe.getMessage(), ioe);
D
duke 已提交
551 552 553 554
            }
        }
    }
}