/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.harmony.security.tests.provider.crypto;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import junit.framework.TestCase;
/**
* Tests against methods in SecureRandom class object using
* SHA1PRNG_SecureRandomImpl.
*/
public class SHA1PRNG_SecureRandomTest extends TestCase {
private static final int LENGTH = 20; // constant defining loop limit
private static final int INCR = 2; // constant defining loop increment
private static final String algorithm = "SHA1PRNG"; // algorithm's name
private static final String provider = "Crypto"; // provider's name
private static SecureRandom sr; // fields used by tests
private static SecureRandom sr2; //
/*
* @see TestCase#setUp()
*/
protected void setUp() throws Exception {
super.setUp();
sr = SecureRandom.getInstance(algorithm, provider);
sr2 = SecureRandom.getInstance(algorithm, provider);
}
/**
* test against the "void generateSeed(int)" method; it checks out that the
* method throws NegativeArraySizeException if argument <0
*/
public final void testGenerateSeedint01() {
try {
sr.generateSeed(-1);
fail("generateSeed(-1) :: No NegativeArraySizeException");
} catch (NegativeArraySizeException e) {
}
}
/**
* test against the "void generateSeed(int)" method; it checks out that
* number of bits returned is equal to one requested; the check includes
* case for argument's value == 0;
*/
public final void testGenerateSeedint02() {
for (int i = 0; i < LENGTH; i++) {
byte[] myBytes = sr.generateSeed(i);
assertFalse("unexpected: myBytes.length != i :: i==" + i + " myBytes.length="
+ myBytes.length, myBytes.length != i);
}
}
/**
* test against the "void generateSeed(int)" method; it checks out the
* quality of entropy (# of different bytes in sequential calls is more or
* equal to 50%)
*/
public final void testGenerateSeedint03() {
byte[] myBytes1;
byte[] myBytes2;
for (int i = 0; i < LENGTH; i += INCR) {
int n = 0;
myBytes1 = sr.generateSeed(i);
myBytes2 = sr.generateSeed(i);
for (int j = 0; j < i; j++) {
if (myBytes1[j] == myBytes2[j]) {
n++;
}
}
assertFalse("unexpected: n*2 > i :: i=" + i + " n=" + n, n * 2 > i);
}
}
/**
* test against the "void nextBytes(byte[])" method; it checks out that the
* method throws NPE if argument supplied is null
*/
public final void testNextBytesbyteArray01() {
try {
sr.nextBytes(null);
fail("unexpected: nextBytes(null) :: No NullPointerException");
} catch (NullPointerException e) {
}
}
/**
* test against the "void nextBytes(byte[])" method; it checks out that
* different SecureRandom objects being supplied with the same seed return
* the same sequencies of bytes as results of their "nextBytes(byte[])"
* methods
*/
public final void testNextBytesbyteArray02() {
byte[] myBytes;
byte[] myBytes1;
byte[] myBytes2;
// case1: sequencies are of the same length
for (int i = 1; i < LENGTH; i += INCR) {
myBytes = new byte[i];
for (int j = 1; j < i; j++) {
myBytes[j] = (byte) (j & 0xFF);
}
sr.setSeed(myBytes);
sr2.setSeed(myBytes);
for (int k = 1; k < LENGTH; k += INCR) {
myBytes1 = new byte[k];
myBytes2 = new byte[k];
sr.nextBytes(myBytes1);
sr2.nextBytes(myBytes2);
for (int l = 0; l < k; l++) {
assertFalse("unexpected: myBytes1[l] != myBytes2[l] :: l==" + l + " k=" + k
+ " i=" + i + " myBytes1[l]=" + myBytes1[l] + " myBytes2[l]="
+ myBytes2[l], myBytes1[l] != myBytes2[l]);
}
}
}
// case2: sequencies are of different lengths
for (int n = 1; n < LENGTH; n += INCR) {
int n1 = 10;
int n2 = 20;
int n3 = 100;
byte[][] bytes1 = new byte[10][n1];
byte[][] bytes2 = new byte[5][n2];
for (int k = 0; k < bytes1.length; k++) {
sr.nextBytes(bytes1[k]);
}
for (int k = 0; k < bytes2.length; k++) {
sr2.nextBytes(bytes2[k]);
}
for (int k = 0; k < n3; k++) {
int i1 = k / n1;
int i2 = k % n1;
int i3 = k / n2;
int i4 = k % n2;
assertTrue("non-equality: i1=" + i1 + " i2=" + i2 + " i3=" + i3 + " i4=" + i4,
bytes1[i1][i2] == bytes2[i3][i4]);
}
}
}
/**
* test against the "void nextBytes(byte[])" method; it checks out that
* different SecureRandom objects being supplied with seed by themselves
* return different sequencies of bytes as results of their
* "nextBytes(byte[])" methods
*/
public final void testNextBytesbyteArray03() throws NoSuchAlgorithmException,
NoSuchProviderException {
/* these are needed to test new SecureRandom objects in loop */
SecureRandom sr1;
SecureRandom sr2;
byte[] myBytes1;
byte[] myBytes2;
for (int i = 1; i < LENGTH / 2; i += INCR) {
sr1 = SecureRandom.getInstance(algorithm, provider);
sr2 = SecureRandom.getInstance(algorithm, provider);
boolean flag = true;
myBytes1 = new byte[i];
myBytes2 = new byte[i];
sr1.nextBytes(myBytes1);
sr2.nextBytes(myBytes2);
for (int j = 0; j < i; j++) {
flag &= myBytes1[j] == myBytes2[j];
}
// check again to avoid intermittent failures
sr1.nextBytes(myBytes1);
sr2.nextBytes(myBytes2);
for (int j = 0; j < i; j++) {
flag &= myBytes1[j] == myBytes2[j];
}
if (flag) {
// probability of false failure is 1.5*10^-5 per run for i=1 or
// less for i > 1
fail("TESTING RANDOM NUMBER GENERATOR QUALITY: IGNORE THIS FAILURE IF INTERMITTENT :: i="
+ i);
}
}
}
/**
* test against the "void nextBytes(byte[])" method; it checks out behavior
* of SecureRandom object in cases of passing byte array of zero length to
* "nextBytes(byte[])" method. The test contains two testcases: - first
* testcase checks out that if for two newly created SecureRandom objects
* invocation of "nextBytes(new byte[0])" method are first ones then further
* calls to nextBytes(..) methods return different byte arrays, that is,
* first "nextBytes(new byte[0])" aslo randomizes internal state; - second
* testcase checks out that if for two newly created SecureRandom objects
* invocation of "setSeed(..)" methods are first ones then further calls to
* "nextBytes(new byte[0])" methods has no effect
*/
public final void testNextBytesbyteArray04() throws NoSuchAlgorithmException,
NoSuchProviderException {
/*
* these are needed to test new SecureRandom objects in loop
*/
SecureRandom sr1;
SecureRandom sr2;
byte[] myBytes;
byte[] myBytes1;
byte[] myBytes2;
// case 1:
for (int i = 1; i < LENGTH / 2; i += INCR) {
sr1 = SecureRandom.getInstance(algorithm, provider);
sr2 = SecureRandom.getInstance(algorithm, provider);
sr1.nextBytes(new byte[0]);
sr2.nextBytes(new byte[0]);
boolean flag = true;
myBytes1 = new byte[i];
myBytes2 = new byte[i];
sr1.nextBytes(myBytes1);
sr2.nextBytes(myBytes2);
for (int j = 0; j < i; j++) {
flag &= myBytes1[j] == myBytes2[j];
}
// check again to avoid intermittent failures
sr1.nextBytes(myBytes1);
sr2.nextBytes(myBytes2);
for (int j = 0; j < i; j++) {
flag &= myBytes1[j] == myBytes2[j];
}
if (flag) {
// probability of false failure is 1.5*10^-5 per run for i=1 or
// less for i > 1
fail("TESTING RANDOM NUMBER GENERATOR QUALITY: IGNORE THIS FAILURE IF INTERMITTENT :: i="
+ i);
}
}
myBytes = new byte[] {
(byte) 0
};
// case2:
for (int n = 1; n < LENGTH; n += INCR) {
byte[][] bytes1 = new byte[2][n];
byte[][] bytes2 = new byte[2][n];
sr1 = SecureRandom.getInstance(algorithm, provider);
sr2 = SecureRandom.getInstance(algorithm, provider);
sr1.setSeed(myBytes);
sr2.setSeed(myBytes);
sr1.nextBytes(bytes1[0]);
sr1.nextBytes(bytes1[1]);
sr2.nextBytes(bytes2[0]);
sr2.nextBytes(new byte[0]);
sr2.nextBytes(bytes2[1]);
for (int k = 0; k < 2; k++) {
for (int j = 0; j < n; j++) {
assertTrue("non-equality: k=" + k + " j=" + j + " bytes1[k][j]=" + bytes1[k][j]
+ " bytes2[k][j]=" + bytes2[k][j], bytes1[k][j] == bytes2[k][j]);
}
}
}
}
/**
* test against the "void setSeed(byte[])" method; it checks out that the
* method throws NPE if argument supplied is null
*/
public final void testSetSeedbyteArray01() {
try {
sr.setSeed(null);
fail("setSeed(null) :: No NullPointerException");
} catch (NullPointerException e) {
}
}
/**
* test against the "void setSeed(byte[])" method; it checks out that
* "setSeed(byte[])" method supplements its argument to current seed rather
* than replaces current seed
*/
public final void testSetSeedbyteArray02() throws NoSuchFieldException, SecurityException,
IllegalAccessException {
byte[] seed = new byte[LENGTH];
byte[] bytes1 = new byte[LENGTH];
byte[] bytes2 = new byte[LENGTH];
boolean b;
for (int i = 0; i < seed.length; i++) {
seed[i] = (byte) i;
}
sr.setSeed(seed);
sr.setSeed(seed);
sr2.setSeed(seed);
sr.nextBytes(bytes1);
sr2.nextBytes(bytes2);
b = true;
for (int j = 0; j < bytes1.length; j++) {
b &= bytes1[j] == bytes2[j];
}
assertFalse("unexpected: sequences are equal", b);
}
/**
* test against the "void setSeed(byte[])" method; it checks out that the
* "byte[0]" argument has no effect; there are two testcases: - if one of
* two SecureRandom objects supplied with the same seed is additionally
* supplied with such array, "nextBytes(..)" of both objects return the same
* bytes; - two byte arrays returned by "nextBytes(..)" in following
* sequence nextBytes(..); setSeed(new byte[0]); nextBytes(..); don't
* contain the same byte sequencies.
*/
public final void testSetSeedbyteArray03() throws NoSuchFieldException, SecurityException,
IllegalAccessException {
byte[] seed = new byte[LENGTH];
byte[] bytes1;
byte[] bytes2;
for (int i = 0; i < seed.length; i++) {
seed[i] = (byte) i;
}
// testcase begins with "bytes1" and "bytes2" of zero length
for (int i = 0; i < LENGTH; i++) {
bytes1 = new byte[i];
bytes2 = new byte[i];
sr.setSeed(seed);
sr.setSeed(new byte[0]);
sr.nextBytes(bytes1);
sr2.setSeed(seed);
sr2.nextBytes(bytes2);
for (int j = 0; j < bytes1.length; j++) {
assertEquals("bytes1[j] != bytes2[j] :: j=" + j, bytes1[j], bytes2[j]);
}
}
for (int i = 1; i < LENGTH; i++) {
bytes1 = new byte[i];
bytes2 = new byte[i];
sr.setSeed(seed);
sr.nextBytes(bytes1);
sr.setSeed(new byte[0]);
sr.nextBytes(bytes2);
boolean b = true;
for (int j = 0; j < bytes1.length; j++) {
b &= bytes1[j] == bytes2[j];
}
assertFalse("sequences are equal i=" + i, b);
}
}
public void testSeedIsFullLength() throws Exception {
Class<?> srClass = Class.forName(
"org.apache.harmony.security.provider.crypto.SHA1PRNG_SecureRandomImpl");
Field seedField = srClass.getDeclaredField("seed");
seedField.setAccessible(true);
Method nextBytesMethod = srClass.getDeclaredMethod("engineNextBytes", byte[].class);
nextBytesMethod.setAccessible(true);
byte[] bytes = new byte[1];
// Iterate 8 times to make sure the probability of a false positive is
// extremely rare.
for (int i = 0; i < 8; i++) {
Object sr = srClass.newInstance();
nextBytesMethod.invoke(sr, bytes);
int[] seed = (int[]) seedField.get(sr);
// If the first integer is not zero, it is fixed.
if (seed[0] != 0) {
return; // Success
}
}
fail("Fallback SHA1PRNG_SecureRandomImpl should not clobber seed internally");
}
}