aboutsummaryrefslogtreecommitdiff
path: root/client/java/com/google/rappor/HmacDrbg.java
diff options
context:
space:
mode:
Diffstat (limited to 'client/java/com/google/rappor/HmacDrbg.java')
-rw-r--r--client/java/com/google/rappor/HmacDrbg.java264
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
+}