Path: blob/master/src/java.base/share/classes/sun/security/provider/SeedGenerator.java
41159 views
/*1* Copyright (c) 1996, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package sun.security.provider;2627/**28* This class generates seeds for the SHA1PRNG cryptographically strong29* random number generator.30* <p>31* The seed is produced using one of two techniques, via a computation32* of current system activity or from an entropy gathering device.33* <p>34* In the default technique the seed is produced by counting the35* number of times the VM manages to loop in a given period. This number36* roughly reflects the machine load at that point in time.37* The samples are translated using a permutation (s-box)38* and then XORed together. This process is non linear and39* should prevent the samples from "averaging out". The s-box40* was designed to have even statistical distribution; it's specific41* values are not crucial for the security of the seed.42* We also create a number of sleeper threads which add entropy43* to the system by keeping the scheduler busy.44* Twenty such samples should give us roughly 160 bits of randomness.45* <p>46* These values are gathered in the background by a daemon thread47* thus allowing the system to continue performing it's different48* activites, which in turn add entropy to the random seed.49* <p>50* The class also gathers miscellaneous system information, some51* machine dependent, some not. This information is then hashed together52* with the 20 seed bytes.53* <p>54* The alternative to the above approach is to acquire seed material55* from an entropy gathering device, such as /dev/random. This can be56* accomplished by setting the value of the {@code securerandom.source}57* Security property to a URL specifying the location of the entropy58* gathering device, or by setting the {@code java.security.egd} System59* property.60* <p>61* In the event the specified URL cannot be accessed the default62* threading mechanism is used.63*64* @author Joshua Bloch65* @author Gadi Guy66*/6768import java.security.*;69import java.io.*;70import java.util.Properties;71import java.util.Enumeration;72import java.net.*;73import java.nio.file.DirectoryStream;74import java.nio.file.Files;75import java.nio.file.Path;76import java.util.Random;77import sun.security.util.Debug;7879abstract class SeedGenerator {8081// Static instance is created at link time82private static SeedGenerator instance;8384private static final Debug debug = Debug.getInstance("provider");8586// Static initializer to hook in selected or best performing generator87static {88String egdSource = SunEntries.getSeedSource();8990/*91* Try the URL specifying the source (e.g. file:/dev/random)92*93* The URLs "file:/dev/random" or "file:/dev/urandom" are used to94* indicate the SeedGenerator should use OS support, if available.95*96* On Windows, this causes the MS CryptoAPI seeder to be used.97*98* On Solaris/Linux/MacOS, this is identical to using99* URLSeedGenerator to read from /dev/[u]random100*/101if (egdSource.equals(SunEntries.URL_DEV_RANDOM) ||102egdSource.equals(SunEntries.URL_DEV_URANDOM)) {103try {104instance = new NativeSeedGenerator(egdSource);105if (debug != null) {106debug.println(107"Using operating system seed generator" + egdSource);108}109} catch (IOException e) {110if (debug != null) {111debug.println("Failed to use operating system seed "112+ "generator: " + e.toString());113}114}115} else if (!egdSource.isEmpty()) {116try {117instance = new URLSeedGenerator(egdSource);118if (debug != null) {119debug.println("Using URL seed generator reading from "120+ egdSource);121}122} catch (IOException e) {123if (debug != null) {124debug.println("Failed to create seed generator with "125+ egdSource + ": " + e.toString());126}127}128}129130// Fall back to ThreadedSeedGenerator131if (instance == null) {132if (debug != null) {133debug.println("Using default threaded seed generator");134}135instance = new ThreadedSeedGenerator();136}137}138139/**140* Fill result with bytes from the queue. Wait for it if it isn't ready.141*/142public static void generateSeed(byte[] result) {143instance.getSeedBytes(result);144}145146abstract void getSeedBytes(byte[] result);147148/**149* Retrieve some system information, hashed.150*/151@SuppressWarnings("removal")152static byte[] getSystemEntropy() {153final MessageDigest md;154155try {156md = MessageDigest.getInstance("SHA");157} catch (NoSuchAlgorithmException nsae) {158throw new InternalError("internal error: SHA-1 not available.",159nsae);160}161162// The current time in millis163byte b =(byte)System.currentTimeMillis();164md.update(b);165166java.security.AccessController.doPrivileged167(new java.security.PrivilegedAction<>() {168@Override169public Void run() {170try {171// System properties can change from machine to machine172Properties p = System.getProperties();173for (String s: p.stringPropertyNames()) {174md.update(s.getBytes());175md.update(p.getProperty(s).getBytes());176}177178// Include network adapter names (and a Mac address)179addNetworkAdapterInfo(md);180181// The temporary dir182File f = new File(p.getProperty("java.io.tmpdir"));183int count = 0;184try (185DirectoryStream<Path> stream =186Files.newDirectoryStream(f.toPath())) {187// We use a Random object to choose what file names188// should be used. Otherwise on a machine with too189// many files, the same first 1024 files always get190// used. Any, We make sure the first 512 files are191// always used.192Random r = new Random();193for (Path entry: stream) {194if (count < 512 || r.nextBoolean()) {195md.update(entry.getFileName()196.toString().getBytes());197}198if (count++ > 1024) {199break;200}201}202}203} catch (Exception ex) {204md.update((byte)ex.hashCode());205}206207// get Runtime memory stats208Runtime rt = Runtime.getRuntime();209byte[] memBytes = longToByteArray(rt.totalMemory());210md.update(memBytes, 0, memBytes.length);211memBytes = longToByteArray(rt.freeMemory());212md.update(memBytes, 0, memBytes.length);213214return null;215}216});217return md.digest();218}219220/*221* Include network adapter names and, if available, a Mac address222*223* See also java.util.concurrent.ThreadLocalRandom.initialSeed()224*/225private static void addNetworkAdapterInfo(MessageDigest md) {226227try {228Enumeration<NetworkInterface> ifcs =229NetworkInterface.getNetworkInterfaces();230while (ifcs.hasMoreElements()) {231NetworkInterface ifc = ifcs.nextElement();232md.update(ifc.toString().getBytes());233if (!ifc.isVirtual()) { // skip fake addresses234byte[] bs = ifc.getHardwareAddress();235if (bs != null) {236md.update(bs);237break;238}239}240}241} catch (Exception ignore) {242}243}244245/**246* Helper function to convert a long into a byte array (least significant247* byte first).248*/249private static byte[] longToByteArray(long l) {250byte[] retVal = new byte[8];251252for (int i=0; i<8; i++) {253retVal[i] = (byte) l;254l >>= 8;255}256257return retVal;258}259260/*261// This method helps the test utility receive unprocessed seed bytes.262public static int genTestSeed() {263return myself.getByte();264}265*/266267268private static class ThreadedSeedGenerator extends SeedGenerator269implements Runnable {270// Queue is used to collect seed bytes271private byte[] pool;272private int start, end, count;273274// Thread group for our threads275ThreadGroup seedGroup;276277/**278* The constructor is only called once to construct the one279* instance we actually use. It instantiates the message digest280* and starts the thread going.281*/282ThreadedSeedGenerator() {283pool = new byte[20];284start = end = 0;285286MessageDigest digest;287288try {289digest = MessageDigest.getInstance("SHA");290} catch (NoSuchAlgorithmException e) {291throw new InternalError("internal error: SHA-1 not available."292, e);293}294295final ThreadGroup[] finalsg = new ThreadGroup[1];296@SuppressWarnings("removal")297Thread t = java.security.AccessController.doPrivileged298(new java.security.PrivilegedAction<>() {299@Override300public Thread run() {301ThreadGroup parent, group =302Thread.currentThread().getThreadGroup();303while ((parent = group.getParent()) != null) {304group = parent;305}306finalsg[0] = new ThreadGroup307(group, "SeedGenerator ThreadGroup");308Thread newT = new Thread(finalsg[0],309ThreadedSeedGenerator.this,310"SeedGenerator Thread",3110,312false);313newT.setPriority(Thread.MIN_PRIORITY);314newT.setDaemon(true);315return newT;316}317});318seedGroup = finalsg[0];319t.start();320}321322/**323* This method does the actual work. It collects random bytes and324* pushes them into the queue.325*/326@Override327public final void run() {328try {329while (true) {330// Queue full? Wait till there's room.331synchronized(this) {332while (count >= pool.length) {333wait();334}335}336337int counter, quanta;338byte v = 0;339340// Spin count must not be under 64000341for (counter = quanta = 0;342(counter < 64000) && (quanta < 6); quanta++) {343344// Start some noisy threads345try {346BogusThread bt = new BogusThread();347Thread t = new Thread348(seedGroup, bt, "SeedGenerator Thread", 0,349false);350t.start();351} catch (Exception e) {352throw new InternalError("internal error: " +353"SeedGenerator thread creation error.", e);354}355356// We wait 250milli quanta, so the minimum wait time357// cannot be under 250milli.358int latch = 0;359long startTime = System.nanoTime();360while (System.nanoTime() - startTime < 250000000) {361synchronized(this){};362// Mask the sign bit and keep latch non-negative363latch = (latch + 1) & 0x1FFFFFFF;364}365366// Translate the value using the permutation, and xor367// it with previous values gathered.368v ^= rndTab[latch % 255];369counter += latch;370}371372// Push it into the queue and notify anybody who might373// be waiting for it.374synchronized(this) {375pool[end] = v;376end++;377count++;378if (end >= pool.length) {379end = 0;380}381382notifyAll();383}384}385} catch (Exception e) {386throw new InternalError("internal error: " +387"SeedGenerator thread generated an exception.", e);388}389}390391@Override392void getSeedBytes(byte[] result) {393for (int i = 0; i < result.length; i++) {394result[i] = getSeedByte();395}396}397398byte getSeedByte() {399byte b;400401try {402// Wait for it...403synchronized(this) {404while (count <= 0) {405wait();406}407}408} catch (Exception e) {409if (count <= 0) {410throw new InternalError("internal error: " +411"SeedGenerator thread generated an exception.", e);412}413}414415synchronized(this) {416// Get it from the queue417b = pool[start];418pool[start] = 0;419start++;420count--;421if (start == pool.length) {422start = 0;423}424425// Notify the daemon thread, just in case it is426// waiting for us to make room in the queue.427notifyAll();428}429430return b;431}432433// The permutation was calculated by generating 64k of random434// data and using it to mix the trivial permutation.435// It should be evenly distributed. The specific values436// are not crucial to the security of this class.437private static final byte[] rndTab = {43856, 30, -107, -6, -86, 25, -83, 75, -12, -64,4395, -128, 78, 21, 16, 32, 70, -81, 37, -51,440-43, -46, -108, 87, 29, 17, -55, 22, -11, -111,441-115, 84, -100, 108, -45, -15, -98, 72, -33, -28,44231, -52, -37, -117, -97, -27, 93, -123, 47, 126,443-80, -62, -93, -79, 61, -96, -65, -5, -47, -119,44414, 89, 81, -118, -88, 20, 67, -126, -113, 60,445-102, 55, 110, 28, 85, 121, 122, -58, 2, 45,44643, 24, -9, 103, -13, 102, -68, -54, -101, -104,44719, 13, -39, -26, -103, 62, 77, 51, 44, 111,44873, 18, -127, -82, 4, -30, 11, -99, -74, 40,449-89, 42, -76, -77, -94, -35, -69, 35, 120, 76,45033, -73, -7, 82, -25, -10, 88, 125, -112, 58,45183, 95, 6, 10, 98, -34, 80, 15, -91, 86,452-19, 52, -17, 117, 49, -63, 118, -90, 36, -116,453-40, -71, 97, -53, -109, -85, 109, -16, -3, 104,454-95, 68, 54, 34, 26, 114, -1, 106, -121, 3,45566, 0, 100, -84, 57, 107, 119, -42, 112, -61,4561, 48, 38, 12, -56, -57, 39, -106, -72, 41,4577, 71, -29, -59, -8, -38, 79, -31, 124, -124,4588, 91, 116, 99, -4, 9, -36, -78, 63, -49,459-67, -87, 59, 101, -32, 92, 94, 53, -41, 115,460-66, -70, -122, 50, -50, -22, -20, -18, -21, 23,461-2, -48, 96, 65, -105, 123, -14, -110, 69, -24,462-120, -75, 74, 127, -60, 113, 90, -114, 105, 46,46327, -125, -23, -44, 64464};465466/**467* This inner thread causes the thread scheduler to become 'noisy',468* thus adding entropy to the system load.469* At least one instance of this class is generated for every seed byte.470*/471private static class BogusThread implements Runnable {472@Override473public final void run() {474try {475for (int i = 0; i < 5; i++) {476Thread.sleep(50);477}478// System.gc();479} catch (Exception e) {480}481}482}483}484485static class URLSeedGenerator extends SeedGenerator {486487private String deviceName;488private InputStream seedStream;489490/**491* The constructor is only called once to construct the one492* instance we actually use. It opens the entropy gathering device493* which will supply the randomness.494*/495496URLSeedGenerator(String egdurl) throws IOException {497if (egdurl == null) {498throw new IOException("No random source specified");499}500deviceName = egdurl;501init();502}503504@SuppressWarnings("removal")505private void init() throws IOException {506final URL device = new URL(deviceName);507try {508seedStream = java.security.AccessController.doPrivileged509(new java.security.PrivilegedExceptionAction<>() {510@Override511public InputStream run() throws IOException {512/*513* return a shared InputStream for file URLs and514* avoid buffering.515* The URL.openStream() call wraps InputStream in a516* BufferedInputStream which517* can buffer up to 8K bytes. This read is a518* performance issue for entropy sources which519* can be slow to replenish.520*/521if (device.getProtocol().equalsIgnoreCase("file")) {522File deviceFile =523SunEntries.getDeviceFile(device);524return FileInputStreamPool525.getInputStream(deviceFile);526} else {527return device.openStream();528}529}530});531} catch (Exception e) {532throw new IOException(533"Failed to open " + deviceName, e.getCause());534}535}536537@Override538void getSeedBytes(byte[] result) {539int len = result.length;540int read = 0;541try {542while (read < len) {543int count = seedStream.read(result, read, len - read);544// /dev/random blocks - should never have EOF545if (count < 0) {546throw new InternalError(547"URLSeedGenerator " + deviceName +548" reached end of file");549}550read += count;551}552} catch (IOException ioe) {553throw new InternalError("URLSeedGenerator " + deviceName +554" generated exception: " + ioe.getMessage(), ioe);555}556}557}558}559560561