aboutsummaryrefslogtreecommitdiff
path: root/keystore-cts/java/com/google/security/wycheproof/testcases/MacTest.java
diff options
context:
space:
mode:
Diffstat (limited to 'keystore-cts/java/com/google/security/wycheproof/testcases/MacTest.java')
-rw-r--r--keystore-cts/java/com/google/security/wycheproof/testcases/MacTest.java398
1 files changed, 398 insertions, 0 deletions
diff --git a/keystore-cts/java/com/google/security/wycheproof/testcases/MacTest.java b/keystore-cts/java/com/google/security/wycheproof/testcases/MacTest.java
new file mode 100644
index 0000000..34b6115
--- /dev/null
+++ b/keystore-cts/java/com/google/security/wycheproof/testcases/MacTest.java
@@ -0,0 +1,398 @@
+/**
+ * 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
+ *
+ * <p>http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * <p>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 java.nio.charset.StandardCharsets.UTF_8;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import com.google.security.wycheproof.WycheproofRunner.ProviderType;
+import com.google.security.wycheproof.WycheproofRunner.SlowTest;
+import java.nio.ByteBuffer;
+import java.security.GeneralSecurityException;
+import java.security.Key;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for MACs.
+ *
+ * <p>TODO(bleichen): The tests are quite incomplete. Some of the missing stuff: More test vectors
+ * with known results are necessary. So far only simple test vectors for long messages are
+ * available.
+ */
+@RunWith(JUnit4.class)
+public class MacTest {
+
+ /**
+ * Computes the maximum of an array with at least one element.
+ *
+ * @param values the values from which the max is computed.
+ * @return the maximum
+ * @throws IllegalArgumentException if values is empty of null.
+ */
+ private static int max(int[] values) {
+ if (values == null || values.length == 0) {
+ throw new IllegalArgumentException("Expecting an array with at least one element");
+ }
+ int result = Integer.MIN_VALUE;
+ for (int value : values) {
+ result = Math.max(result, value);
+ }
+ return result;
+ }
+
+ protected static boolean arrayEquals(byte[] a, byte[] b) {
+ if (a.length != b.length) {
+ return false;
+ }
+ byte res = 0;
+ for (int i = 0; i < a.length; i++) {
+ res |= (byte) (a[i] ^ b[i]);
+ }
+ return res == 0;
+ }
+
+ /**
+ * Tests computing a MAC by computing it multiple times. The test passes all the results are the
+ * same in all cases.
+ *
+ * @param algorithm the name of the MAC (e.g. "HMACSHA1")
+ * @param key the key of the MAC
+ * @param data input data for the MAC. The size of the data must be at least as long as the sum of
+ * all chunkSizes.
+ * @param chunkSizes the sizes of the chunks used in the calls of update
+ */
+ private void testUpdateWithChunks(String algorithm, Key key, byte[] data, int... chunkSizes)
+ throws Exception {
+ Mac mac = Mac.getInstance(algorithm);
+
+ // First evaluation: compute MAC in one piece.
+ int totalLength = 0;
+ for (int chunkSize : chunkSizes) {
+ totalLength += chunkSize;
+ }
+ mac.init(key);
+ mac.update(data, 0, totalLength);
+ byte[] mac1 = mac.doFinal();
+
+ // Second evaluation: using multiple chunks
+ mac.init(key);
+ int start = 0;
+ for (int chunkSize : chunkSizes) {
+ mac.update(data, start, chunkSize);
+ start += chunkSize;
+ }
+ byte[] mac2 = mac.doFinal();
+ if (!arrayEquals(mac1, mac2)) {
+ fail(
+ "Different MACs for same input:"
+ + " computed as one piece:"
+ + TestUtil.bytesToHex(mac1)
+ + " computed with multiple array segments:"
+ + TestUtil.bytesToHex(mac2));
+ }
+ // Third evaluation: using ByteBuffers
+ mac.init(key);
+ start = 0;
+ for (int chunkSize : chunkSizes) {
+ ByteBuffer chunk = ByteBuffer.wrap(data, start, chunkSize);
+ mac.update(chunk);
+ start += chunkSize;
+ }
+ byte[] mac3 = mac.doFinal();
+ if (!arrayEquals(mac1, mac3)) {
+ fail(
+ "Different MACs for same input:"
+ + " computed as one piece:"
+ + TestUtil.bytesToHex(mac1)
+ + " computed with wrapped chunks:"
+ + TestUtil.bytesToHex(mac3));
+ }
+ // Forth evaluation: using ByteBuffer slices.
+ // The effect of using slice() is that the resulting ByteBuffer has
+ // position 0, but possibly an non-zero value for arrayOffset().
+ mac.init(key);
+ start = 0;
+ for (int chunkSize : chunkSizes) {
+ ByteBuffer chunk = ByteBuffer.wrap(data, start, chunkSize).slice();
+ mac.update(chunk);
+ start += chunkSize;
+ }
+ byte[] mac4 = mac.doFinal();
+ if (!arrayEquals(mac1, mac4)) {
+ fail(
+ "Different MACs for same input:"
+ + " computed as one piece:"
+ + TestUtil.bytesToHex(mac1)
+ + " computed with ByteBuffer slices:"
+ + TestUtil.bytesToHex(mac4));
+ }
+ }
+
+ /**
+ * The paper "Finding Bugs in Cryptographic Hash Function Implementations" by Mouha, Raunak, Kuhn,
+ * and Kacker, https://eprint.iacr.org/2017/891.pdf contains an analysis of implementations
+ * submitted to the SHA-3 competition. Many of the implementations contain bugs. The authors
+ * propose some tests for cryptographic libraries. The test here implements a check for
+ * incremental updates with the values proposed in Table 3.
+ */
+ private void testUpdate(String algorithm, Key key) throws Exception {
+ int[] chunkSize1 = {0, 8, 16, 24, 32, 40, 48, 56, 64};
+ int[] chunkSize2 = {0, 8, 16, 24, 32, 40, 48, 56, 64};
+ int[] chunkSize3 = {0, 8, 16, 32, 64, 128, 256, 512, 1024, 2048};
+ int[] chunkSize4 = {
+ 0, 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, 127, 128, 129, 255, 256,
+ 257, 511, 512, 513
+ };
+ int maxSize = max(chunkSize1) + max(chunkSize2) + max(chunkSize3) + max(chunkSize4);
+ byte[] data = new byte[maxSize];
+ SecureRandom rand = new SecureRandom();
+ rand.nextBytes(data);
+ for (int size1 : chunkSize1) {
+ for (int size2 : chunkSize2) {
+ for (int size3 : chunkSize3) {
+ for (int size4 : chunkSize4) {
+ testUpdateWithChunks(algorithm, key, data, size1, size2, size3, size4);
+ }
+ }
+ }
+ }
+ }
+
+ public void testMac(String algorithm, int keySize) throws Exception {
+ try {
+ Mac.getInstance(algorithm);
+ } catch (NoSuchAlgorithmException ex) {
+ System.out.println("Algorithm " + algorithm + " is not supported. Skipping test.");
+ return;
+ }
+ byte[] key = new byte[keySize];
+ SecureRandom rand = new SecureRandom();
+ rand.nextBytes(key);
+ testUpdate(algorithm, new SecretKeySpec(key, algorithm));
+ }
+
+ @Test
+ public void testHmacSha1() throws Exception {
+ testMac("HMACSHA1", 20);
+ }
+
+ @Test
+ public void testHmacSha224() throws Exception {
+ testMac("HMACSHA224", 28);
+ }
+
+ @Test
+ public void testHmacSha256() throws Exception {
+ testMac("HMACSHA256", 32);
+ }
+
+ @Test
+ public void testHmacSha384() throws Exception {
+ testMac("HMACSHA384", 48);
+ }
+
+ @Test
+ public void testHmacSha512() throws Exception {
+ testMac("HMACSHA512", 64);
+ }
+
+ @Test
+ public void testHmacSha3_224() throws Exception {
+ testMac("HMACSHA3-224", 28);
+ }
+
+ @Test
+ public void testHmacSha3_256() throws Exception {
+ testMac("HMACSHA3-256", 32);
+ }
+
+ @Test
+ public void testHmacSha3_384() throws Exception {
+ testMac("HMACSHA3-384", 48);
+ }
+
+ @Test
+ public void testHmacSha3_512() throws Exception {
+ testMac("HMACSHA3-512", 64);
+ }
+
+ /**
+ * Computes the mac of a message repeated multiple times.
+ *
+ * @param algorithm the message digest (e.g. "HMACSHA1")
+ * @param message the bytes to mac
+ * @param repetitions the number of repetitions of the message
+ * @return the digest
+ * @throws GeneralSecurityException if the computation of the mac fails (e.g. because the
+ * algorithm is unknown).
+ */
+ public byte[] macRepeatedMessage(String algorithm, Key key, byte[] message, long repetitions)
+ throws Exception {
+ Mac mac = Mac.getInstance(algorithm);
+ mac.init(key);
+ // If the message is short then it is more efficient to collect multiple copies
+ // of the message in one chunk and call update with the larger chunk.
+ final int maxChunkSize = 1 << 16;
+ if (message.length != 0 && 2 * message.length < maxChunkSize) {
+ int repetitionsPerChunk = maxChunkSize / message.length;
+ byte[] chunk = new byte[message.length * repetitionsPerChunk];
+ for (int i = 0; i < repetitionsPerChunk; i++) {
+ System.arraycopy(message, 0, chunk, i * message.length, message.length);
+ }
+ while (repetitions >= repetitionsPerChunk) {
+ mac.update(chunk);
+ repetitions -= repetitionsPerChunk;
+ }
+ }
+
+ for (int i = 0; i < repetitions; i++) {
+ mac.update(message);
+ }
+ return mac.doFinal();
+ }
+
+ /**
+ * A test for hashing long messages.
+ *
+ * <p>Java does not allow strings or arrays of size 2^31 or longer. However, it is still possible
+ * to compute a MAC of a long message by repeatedly calling Mac.update(). To compute correct MACs
+ * the total message length must be known. This length can be bigger than 2^32 bytes.
+ *
+ * <p>Reference: http://www-01.ibm.com/support/docview.wss?uid=swg1PK62549 IBMJCE SHA-1
+ * IMPLEMENTATION RETURNS INCORRECT HASH FOR LARGE SETS OF DATA
+ */
+ private void testLongMac(
+ String algorithm, String keyhex, String message, long repetitions, String expected)
+ throws Exception {
+
+ Key key = new SecretKeySpec(TestUtil.hexToBytes(keyhex), algorithm);
+ byte[] bytes = message.getBytes(UTF_8);
+ byte[] mac = null;
+ try {
+ mac = macRepeatedMessage(algorithm, key, bytes, repetitions);
+ } catch (NoSuchAlgorithmException ex) {
+ System.out.println("Algorithm " + algorithm + " is not supported. Skipping test.");
+ return;
+ }
+ String hexmac = TestUtil.bytesToHex(mac);
+ assertEquals(expected, hexmac);
+ }
+
+ @SlowTest(
+ providers = {
+ ProviderType.OPENJDK,
+ ProviderType.BOUNCY_CASTLE,
+ ProviderType.SPONGY_CASTLE,
+ ProviderType.CONSCRYPT
+ })
+ @Test
+ public void testLongMacSha1() throws Exception {
+ testLongMac(
+ "HMACSHA1",
+ "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+ "a",
+ 2147483647L,
+ "703925f6dceb9c602969ad39bba9b1eb49472071");
+ testLongMac(
+ "HMACSHA1",
+ "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+ "a",
+ 5000000000L,
+ "d7f4c387f2237ea119fcc27cd7520fc5132b6230");
+ }
+
+ @SlowTest(
+ providers = {
+ ProviderType.OPENJDK,
+ ProviderType.BOUNCY_CASTLE,
+ ProviderType.SPONGY_CASTLE,
+ ProviderType.CONSCRYPT
+ })
+ @Test
+ public void testLongMacSha256() throws Exception {
+ testLongMac(
+ "HMACSHA256",
+ "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+ "a",
+ 2147483647L,
+ "84f213c9bb5b329d547bc31dabed41939754b1af7482365ec74380c45f6ea0a7");
+ testLongMac(
+ "HMACSHA256",
+ "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+ "a",
+ 5000000000L,
+ "59a75754df7093fa4339aa618b64b104f153a5b42cc85394fdb8735b13ea684a");
+ }
+
+ @SlowTest(
+ providers = {
+ ProviderType.OPENJDK,
+ ProviderType.BOUNCY_CASTLE,
+ ProviderType.SPONGY_CASTLE,
+ ProviderType.CONSCRYPT
+ })
+ @Test
+ public void testLongMacSha384() throws Exception {
+ testLongMac(
+ "HMACSHA384",
+ "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"
+ + "202122232425262728292a2b2c2d2e2f",
+ "a",
+ 2147483647L,
+ "aea987905f64791691b3fdea06f8e4125f396ebb73f37894e961b1a7522a55da"
+ + "ecd856a70c92c6646e6f8c3fcb935528");
+ testLongMac(
+ "HMACSHA384",
+ "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"
+ + "202122232425262728292a2b2c2d2e2f",
+ "a",
+ 5000000000L,
+ "88485c9c5714d43a99dacbc861988c7ea39c02d82104bf93e55ec1b8a24fe15a"
+ + "a477e6a84d159d8b7a3daaa89c4f2372");
+ }
+
+ @SlowTest(
+ providers = {
+ ProviderType.OPENJDK,
+ ProviderType.BOUNCY_CASTLE,
+ ProviderType.SPONGY_CASTLE,
+ ProviderType.CONSCRYPT
+ })
+ @Test
+ public void testLongMacSha512() throws Exception {
+ testLongMac(
+ "HMACSHA512",
+ "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"
+ + "202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f",
+ "a",
+ 2147483647L,
+ "fc68fbc294951c691e5bc085c3af026099f39a57230b242aaf1fc5ca691e05da"
+ + "d1a5de7d4f30e1c958c6a2cee6159218dab683187e6d56bab824a3adefde9102");
+ testLongMac(
+ "HMACSHA512",
+ "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"
+ + "202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f",
+ "a",
+ 5000000000L,
+ "31b1d721b958203bff7d7ddf50d48b17fc760a80a99a7f23ec966ce3bbefff29"
+ + "0d176eebbb6a440960024be0726c94960bbf75816548a7fd4552c7baba4585ee");
+ }
+}