blob: 26e66b437243ea2e540becc7e1bab2d9bf7fe2c9 [file] [log] [blame]
// 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.crypto.tink.util;
import java.security.GeneralSecurityException;
import java.util.Arrays;
/** Provides various utility methods for testing. */
public class TestUtil {
private TestUtil() {}
/**
* Uses a z test on the given byte string, expecting all bits to be uniformly set with probability
* 1/2. Returns non ok status if the z test fails by more than 10 standard deviations.
*
* <p>With less statistics jargon: This counts the number of bits set and expects the number to be
* roughly half of the length of the string. The law of large numbers suggests that we can assume
* that the longer the string is, the more accurate that estimate becomes for a random string.
* This test is useful to detect things like strings that are entirely zero.
*
* <p>Note: By itself, this is a very weak test for randomness.
*
* @throws GeneralSecurityException if uniformity error is detected, otherwise returns normally.
*/
public static void ztestUniformString(byte[] string) throws GeneralSecurityException {
final double minAcceptableStdDevs = 10.0;
double totalBits = string.length * 8;
double expected = totalBits / 2.0;
double stddev = Math.sqrt(totalBits / 4.0);
// This test is very limited at low string lengths. Below a certain threshold it tests nothing.
if (expected < stddev * minAcceptableStdDevs) {
throw new GeneralSecurityException(
"Test will always succeed with strings of the given length "
+ string.length
+ ". Use more bytes.");
}
long numSetBits = 0;
for (byte b : string) {
int unsignedInt = toUnsignedInt(b);
// Counting the number of bits set in byte:
while (unsignedInt != 0) {
numSetBits++;
unsignedInt = (unsignedInt & (unsignedInt - 1));
}
}
// Check that the number of bits is within 10 stddevs.
if (Math.abs((double) numSetBits - expected) < minAcceptableStdDevs * stddev) {
return;
}
throw new GeneralSecurityException(
"Z test for uniformly distributed variable out of bounds; "
+ "Actual number of set bits was "
+ numSetBits
+ " expected was "
+ expected
+ " 10 * standard deviation is 10 * "
+ stddev
+ " = "
+ 10.0 * stddev);
}
/**
* Tests that the crosscorrelation of two strings of equal length points to independent and
* uniformly distributed strings. Returns non ok status if the z test fails by more than 10
* standard deviations.
*
* <p>With less statistics jargon: This xors two strings and then performs the ZTestUniformString
* on the result. If the two strings are independent and uniformly distributed, the xor'ed string
* is as well. A cross correlation test will find whether two strings overlap more or less than it
* would be expected.
*
* <p>Note: Having a correlation of zero is only a necessary but not sufficient condition for
* independence.
*
* @throws GeneralSecurityException if uniformity error is detected, otherwise returns normally.
*/
public static void ztestCrossCorrelationUniformStrings(byte[] string1, byte[] string2)
throws GeneralSecurityException {
if (string1.length != string2.length) {
throw new GeneralSecurityException("Strings are not of equal length");
}
byte[] crossed = new byte[string1.length];
for (int i = 0; i < string1.length; i++) {
crossed[i] = (byte) (string1[i] ^ string2[i]);
}
ztestUniformString(crossed);
}
/**
* Tests that the autocorrelation of a string points to the bits being independent and uniformly
* distributed. Rotates the string in a cyclic fashion. Returns non ok status if the z test fails
* by more than 10 standard deviations.
*
* <p>With less statistics jargon: This rotates the string bit by bit and performs
* ZTestCrosscorrelationUniformStrings on each of the rotated strings and the original. This will
* find self similarity of the input string, especially periodic self similarity. For example, it
* is a decent test to find English text (needs about 180 characters with the current settings).
*
* <p>Note: Having a correlation of zero is only a necessary but not sufficient condition for
* independence.
*
* @throws GeneralSecurityException if uniformity error is detected, otherwise returns normally.
*/
public static void ztestAutocorrelationUniformString(byte[] string)
throws GeneralSecurityException {
byte[] rotated = Arrays.copyOf(string, string.length);
for (int i = 1; i < string.length * 8; i++) {
rotate(rotated);
ztestCrossCorrelationUniformStrings(string, rotated);
}
}
/** Manual implementation of Byte.toUnsignedByte. The Android JDK does not have this method. */
private static int toUnsignedInt(byte b) {
return b & 0xff;
}
private static void rotate(byte[] string) {
byte[] ref = Arrays.copyOf(string, string.length);
for (int i = 0; i < string.length; i++) {
string[i] =
(byte)
((toUnsignedInt(string[i]) >> 1)
| ((1 & toUnsignedInt(ref[(i == 0 ? string.length : i) - 1])) << 7));
}
}
}