/** * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.security.wycheproof; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import java.security.Provider; import java.security.SecureRandom; import java.security.Security; import java.util.ArrayList; import java.util.Collection; import java.util.Set; import java.util.TreeSet; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; /** * Checks whether instances of SecureRandom follow the SecureRandom API. * * The tests here are quite limited. They only test that instances are non-deterministic, when * required by the API. The tests do not attempt the determine whether the output is pseudorandom * and whether the seeds used are unpredictable. * * Any instance of SecureRandom must self-seed itself if no seed is provided: hence the following * code must never result in deterministic or predictable behaviour: * <pre>{@code * // Safe use of SecureRandom * SecureRandom secureRandom = SecureRandom.getInstance(ALGORITHM); * byte[] randomOutput = new byte[SIZE]; * secureRandom.nextBytes(randomOutput); * ... * }</pre> * An important point is that the constructur itself does not necessarily seed the SecureRandom * instance and that the self-seeding is only required if caller does not provide any seeds. * Thus the following code snippet can lead to deterministic and predictable behaviour: * <pre>{@code * // Potentially deterministic and predictable outcome. * SecureRandom secureRandom = SecureRandom.getInstance(ALGORITHM); * secureRandom.setSeed(MY_SEED); * byte[] randomOutput = new byte[SIZE]; * secureRandom.nextBytes(randomOutput); * ... * }</pre> * For example "SHA1PRNG" has the property that calling setSeed after the construction gives * a SecureRandom instance with output that only depends on the caller provided seeds. * * Once a SecureRandom instance has been seeded, all further calls of setSeed must add additional * randomness. It is not acceptable setSeed overrides the current seed of an instance. Hence the * following code must always be non-deterministic. * <pre>{@code * SecureRandom secureRandom = SecureRandom.getInstance(ALGORITHM); * // The next line forces secureRandom to self-seed. * secureRandom.nextBytes(new byte[1]); * // Adding and additional seed. The instance remains properly seeded and unpredictable even if * // MY_SEED is known or constant. * secureRandom.setSeed(MY_SEED); * byte[] randomOutput = new byte[SIZE]; * secureRandom.nextBytes(randomOutput); * ... * }</pre> * */ @RunWith(JUnit4.class) public class SecureRandomTest { /** Returns a list of all implemented services of SecureRandom. */ Collection<Provider.Service> secureRandomServices() { // TODO(bleichen): Check if all instances of SecureRandom are actually // listed as services. In particular the default SecureRandom() and // SecureRandom.getInstanceStrong() may not not be registered. ArrayList<Provider.Service> result = new ArrayList<Provider.Service>(); for (Provider p : Security.getProviders()) { for (Provider.Service service : p.getServices()) { if (service.getType().equals("SecureRandom")) { result.add(service); } } } return result; } /** * Uninitialized instances or SecureRandom must self-seed before * their first use. */ @Test public void testSeedUninitializedInstance() throws Exception { final int samples = 10; // the number of samples per SecureRandom. // The size of the generated pseudorandom bytes. An output size of 8 bytes // means that the probability of false positives is about // 2^{-65}*(samples * (#secure random instances))^2. // Hence a random failure of this function is unlikely. final int outputsize = 8; Set<String> seen = new TreeSet<String>(); for (Provider.Service service : secureRandomServices()) { for (int i = 0; i < samples; i++) { SecureRandom random = SecureRandom.getInstance(service.getAlgorithm(), service.getProvider()); byte[] bytes = new byte[outputsize]; random.nextBytes(bytes); String hex = TestUtil.bytesToHex(bytes); assertFalse("Repeated output from " + service.getAlgorithm(), seen.contains(hex)); seen.add(hex); } } } /** * Calling setSeed directly after the initialization may result in deterministic * results. * * The test expects that a SecureRandom instance is either completely deterministic if * seeded or non-deterministic and unpredictable (though the test is much too simple * to give any meaningful result). * * For example the provider "SUN" has the following behaviour: * <pre> * Seeding SHA1PRNG from SUN results in deterministic output. * Seeding NativePRNG from SUN results in non-deterministic output. * Seeding NativePRNGBlocking from SUN results in non-deterministic output. * Seeding NativePRNGNonBlocking from SUN results in non-deterministic output. * </pre> * * jdk9 adds a class java.security.DrbgParameter, which allows to better specify the expected * behaviour of SecureRandom instances. Tests with these parameters are not included here. */ @Test public void testSetSeedAfterConstruction() throws Exception { final int samples = 10; // the number of samples per SecureRandom. // The size of the generated pseudorandom bytes. An output size of 8 bytes // means that the probability of false positives is about // 2^{-65}*(samples * (#secure random instances))^2. // Hence a random failure of this function is unlikely. final int outputsize = 8; final byte[] seed = new byte[32]; for (Provider.Service service : secureRandomServices()) { Provider provider = service.getProvider(); Set<String> seen = new TreeSet<String>(); for (int i = 0; i < samples; i++) { SecureRandom random = SecureRandom.getInstance(service.getAlgorithm(), provider); random.setSeed(seed); byte[] bytes = new byte[outputsize]; random.nextBytes(bytes); String hex = TestUtil.bytesToHex(bytes); seen.add(hex); } if (seen.size() == 1) { System.out.println("Seeding " + service.getAlgorithm() + " from " + provider.getName() + " results in deterministic output."); } else { System.out.println("Seeding " + service.getAlgorithm() + " from " + provider.getName() + " results in non-deterministic output."); // ... and if the implementation is non-determinstic, there should be no repetitions. assertEquals(samples, seen.size()); } } } /** * Calling setSeed after use adds the seed to the current state. It must never replace it. */ @Test public void testSetSeedAfterUse() throws Exception { final int samples = 10; // the number of samples per SecureRandom. // The size of the generated pseudorandom bytes. An output size of 8 bytes // means that the probability of false positives is about // 2^{-65}*(samples * (#secure random instances))^2. // Hence a random failure of this function is unlikely. final int outputsize = 8; Set<String> seen = new TreeSet<String>(); final byte[] seed = new byte[32]; for (Provider.Service service : secureRandomServices()) { for (int i = 0; i < samples; i++) { SecureRandom random = SecureRandom.getInstance(service.getAlgorithm(), service.getProvider()); // Calling nextBytes() self-seeds the instance. byte[] dummy = new byte[0]; random.nextBytes(dummy); // Calling setSeed() adds the seed to the instance. It would be wrong to // replace the current state of the instance with the new seed. random.setSeed(seed); byte[] bytes = new byte[outputsize]; // Hence it would be an error (or an unlikely false positive) if the generated // bytes are already known. random.nextBytes(bytes); String hex = TestUtil.bytesToHex(bytes); assertFalse("Repeated output from " + service.getAlgorithm(), seen.contains(hex)); seen.add(hex); } } } }