|
|
|
@@ -0,0 +1,205 @@
|
|
|
|
|
/**
|
|
|
|
|
* 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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|