aboutsummaryrefslogtreecommitdiff
path: root/java/com/google/security/wycheproof/testcases/SecureRandomTest.java
blob: 86e7bd8f7debc3b0e40fe244de0b70d8493119f6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
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);
      }
    }
  }
}