diff options
Diffstat (limited to 'client/java/com/google/rappor/HmacDrbg.java')
-rw-r--r-- | client/java/com/google/rappor/HmacDrbg.java | 264 |
1 files changed, 264 insertions, 0 deletions
diff --git a/client/java/com/google/rappor/HmacDrbg.java b/client/java/com/google/rappor/HmacDrbg.java new file mode 100644 index 0000000..db99700 --- /dev/null +++ b/client/java/com/google/rappor/HmacDrbg.java @@ -0,0 +1,264 @@ +package com.google.android.rappor; + +// BEGIN android-changed: Removed guava dependency +// import com.google.common.hash.HashFunction; +// import com.google.common.hash.Hashing; +// import com.google.common.primitives.Bytes; +// END android-changed + +import java.security.SecureRandom; +import java.util.Arrays; +import javax.annotation.concurrent.NotThreadSafe; +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; + +/** + * Deterministic Random Bit Generator based on HMAC-SHA256. + * + * Also known as: HMAC_DRBG. + * See http://csrc.nist.gov/publications/nistpubs/800-90A/SP800-90A.pdf for thorough specification. + * + * Reseeding is not supported. Instead, construct a new DRBG when reseeding is required. + * See http://csrc.nist.gov/publications/nistpubs/800-90A/SP800-90A.pdf Section 8.6.8. + */ +@NotThreadSafe +public class HmacDrbg { + // "V" from the the spec. + private byte[] value; + + // BEGIN android-changed + // An instance of HMAC-SHA256 configured with "Key" from the spec. + // private HashFunction hmac; + private Mac hmac; + // END android-changed + + // The total number of bytes that have been generated from this DRBG so far. + private int bytesGenerated; + + // Assume maximum security strength for HMAC-256, which is 256. + // See: http://csrc.nist.gov/publications/nistpubs/800-90A/SP800-90A.pdf D.2 #1. + public static final int SECURITY_STRENGTH = 256; + + /** + * Personalization strings should not exceed this many bytes in length. + * + * See: http://csrc.nist.gov/publications/nistpubs/800-90A/SP800-90A.pdf D.2 #7. + */ + public static final int MAX_PERSONALIZATION_STRING_LENGTH_BYTES = 160 / 8; + + /** + * The constructor's entropyInput should contain this many high quality random bytes. + * HMAC_DRBG requires entropy input to be security_strength bits long, + * and nonce to be at least 1/2 security_strength bits long. We + * generate them both as a single "extra strong" entropy input. + * See: http://csrc.nist.gov/publications/nistpubs/800-90A/SP800-90A.pdf + */ + public static final int ENTROPY_INPUT_SIZE_BYTES = (SECURITY_STRENGTH / 8) * 3 / 2; + + /** + * The maximum total number of bytes that can be generated from this DRBG. + * + * This is conservative releative to the suggestions in + * http://csrc.nist.gov/publications/nistpubs/800-90A/SP800-90A.pdf section D.2, + * ensuring that reseeding is never triggered (each call to Generate produces at least one byte, + * therefore MAX_BYTES will be reached before RESEED_INTERAL=10000 is exceeded) + * and simplifying the interface (so that the client need not worry about MAX_BYTES_PER_REQUEST, + * below. + */ + public static final int MAX_BYTES_TOTAL = 10000; + + // See: http://csrc.nist.gov/publications/nistpubs/800-90A/SP800-90A.pdf D.2 #2. + private static final int DIGEST_NUM_BYTES = 256 / 8; + + // floor(7500/8); see: http://csrc.nist.gov/publications/nistpubs/800-90A/SP800-90A.pdf D.2 #5. + private static final int MAX_BYTES_PER_REQUEST = 937; + + private static final byte[] BYTE_ARRAY_0 = {0}; + private static final byte[] BYTE_ARRAY_1 = {1}; + + public HmacDrbg(byte[] entropyInput, byte[] personalizationString) { + // HMAC_DRBG Instantiate Process + // See: http://csrc.nist.gov/publications/nistpubs/800-90A/SP800-90A.pdf 10.1.1.2 + + // 1. seed_material = entropy_input + nonce + personalization_string + // Note: We are using the 8.6.7 interpretation, where the entropy_input and + // nonce are acquired at the same time from the same source. + // BEGIN android-changed + // byte[] seedMaterial = Bytes.concat(entropyInput, emptyIfNull(personalizationString)); + byte[] seedMaterial = bytesConcat(entropyInput, emptyIfNull(personalizationString)); + // END android-changed + + // 2. Key = 0x00 00...00 + setKey(new byte[256 / 8]); + + // 3. V = 0x01 01...01 + value = new byte[DIGEST_NUM_BYTES]; + Arrays.fill(value, (byte) 0x01); + + // 4. (Key, V) = HMAC_DRBG_Update(seed_material, Key, V) + hmacDrbgUpdate(seedMaterial); + + bytesGenerated = 0; + } + + /** + * Returns an 0-length byte array if b is null, otherwise returns b. + */ + private static byte[] emptyIfNull(byte[] b) { + return b == null ? new byte[0] : b; + } + + /** + * Set's the "Key" state from the spec. + */ + private void setKey(byte[] key) { + // BEGIN android-changed + // hmac = Hashing.hmacSha256(key); + try { + hmac = Mac.getInstance("HmacSHA256"); + SecretKeySpec secretKey = new SecretKeySpec(key, "HmacSHA256"); + hmac.init(secretKey); + } catch (Exception e) {} + // END android-changed + } + + /** + * Computes hmac("key" from the spec, x). + */ + private byte[] hash(byte[] x) { + // BEGIN android-changed + // return hmac.hashBytes(x).asBytes(); + try { + return hmac.doFinal(x); + } catch (Exception e) { + return null; + } + // END android-changed + } + + /** + * HMAC_DRBG Update Process + * + * See: http://csrc.nist.gov/publications/nistpubs/800-90A/SP800-90A.pdf 10.1.2.2 + */ + private void hmacDrbgUpdate(byte[] providedData) { + + // 1. K = HMAC(K, V || 0x00 || provided_data) + // BEGIN android-changed + // setKey(hash(Bytes.concat(value, BYTE_ARRAY_0, emptyIfNull(providedData)))); + setKey(hash(bytesConcat(value, BYTE_ARRAY_0, emptyIfNull(providedData)))); + // END android-changed + + // 2. V = HMAC(K, V); + value = hash(value); + + // 3. If (provided_data = Null), then return K and V. + if (providedData == null) { + return; + } + + // 4. K = HMAC (K, V || 0x01 || provided_data). + // BEGIN android-changed + // setKey(hash(Bytes.concat(value, BYTE_ARRAY_1, providedData))); + setKey(hash(bytesConcat(value, BYTE_ARRAY_1, providedData))); + // END android-changed + + // 5. V = HMAC (K, V). + value = hash(value); + } + + /** + * HMAC_DRBG Generate Process + * + * See: http://csrc.nist.gov/publications/nistpubs/800-90A/SP800-90A.pdf 10.1.2.5 + * + * We do not support additional_input, assuming it to be always null. + * + * We guarantee that reseeding is never required through the use of MAX_BYTES_TOTAL + * rather than RESEED_INTERVAL. + */ + private void hmacDrbgGenerate(byte[] out, int start, int count) { + // 3. temp = Null. + int bytesWritten = 0; + + // 4. While (len (temp) < requested_number_of_bits) do: + while (bytesWritten < count) { + // 4.1 V = HMAC (Key, V). + value = hash(value); + + // 4.2 temp = temp || V. + // 5. returned_bits = Leftmost requested_number_of_bits of temp + int bytesToWrite = Math.min(count - bytesWritten, DIGEST_NUM_BYTES); + System.arraycopy(value, 0, out, start + bytesWritten, bytesToWrite); + bytesWritten += bytesToWrite; + } + + // 6. (Key, V) = HMAC_DRBG_Update (additional_input, Key, V). + hmacDrbgUpdate(null); + } + + /** + * Generates entropy byte-string suitable for use as the constructor's entropyInput. + * + * Uses SecureRandom to generate entropy. + */ + public static byte[] generateEntropyInput() { + byte result[] = new byte[ENTROPY_INPUT_SIZE_BYTES]; + new SecureRandom().nextBytes(result); + return result; + } + + /** + * Returns the next length pseudo-random bytes. + */ + public byte[] nextBytes(int length) { + byte result[] = new byte[length]; + nextBytes(result); + return result; + } + + /** + * Fills the output vector with pseudo-random bytes. + */ + public void nextBytes(byte[] out) { + nextBytes(out, 0, out.length); + } + + /** + * Fills out[start] through out[start+count-1] (inclusive) with pseudo-random bytes. + */ + public void nextBytes(byte[] out, int start, int count) { + if (count == 0) { + return; + } + if (bytesGenerated + count > MAX_BYTES_TOTAL) { + throw new IllegalStateException("Cannot generate more than a total of " + count + " bytes."); + } + try { + int bytesWritten = 0; + while (bytesWritten < count) { + int bytesToWrite = Math.min(count - bytesWritten, MAX_BYTES_PER_REQUEST); + hmacDrbgGenerate(out, start + bytesWritten, bytesToWrite); + bytesWritten += bytesToWrite; + } + } finally { + bytesGenerated += count; + } + } + + // BEGIN android-changed + private static byte[] bytesConcat(byte[]... arrays) { + int length = 0; + for (byte[] array : arrays) { + length += array.length; + } + byte[] result = new byte[length]; + int pos = 0; + for (byte[] array : arrays) { + System.arraycopy(array, 0, result, pos, array.length); + pos += array.length; + } + return result; + } + // END android-changed +} |