xref: /aosp_15_r20/external/wycheproof/keystore-cts/java/com/google/security/wycheproof/testcases/MacTest.java (revision 3d322d526be0ef039eebde10aa6ae75db8cc3a3d)
1 /**
2  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
3  * in compliance with the License. You may obtain a copy of the License at
4  *
5  * <p>http://www.apache.org/licenses/LICENSE-2.0
6  *
7  * <p>Unless required by applicable law or agreed to in writing, software distributed under the
8  * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
9  * express or implied. See the License for the specific language governing permissions and
10  * limitations under the License.
11  */
12 package com.google.security.wycheproof;
13 
14 import static java.nio.charset.StandardCharsets.UTF_8;
15 import static org.junit.Assert.assertEquals;
16 import static org.junit.Assert.fail;
17 
18 import java.nio.ByteBuffer;
19 import java.security.GeneralSecurityException;
20 import java.security.Key;
21 import java.security.KeyStore;
22 import java.security.NoSuchAlgorithmException;
23 import java.security.SecureRandom;
24 import javax.crypto.Mac;
25 import javax.crypto.spec.SecretKeySpec;
26 import org.junit.After;
27 import org.junit.Test;
28 import org.junit.Ignore;
29 import android.security.keystore.KeyProtection;
30 import android.security.keystore.KeyProperties;
31 import android.keystore.cts.util.KeyStoreUtil;
32 
33 /**
34  * Tests for MACs.
35  *
36  * <p>TODO(bleichen): The tests are quite incomplete. Some of the missing stuff: More test vectors
37  * with known results are necessary. So far only simple test vectors for long messages are
38  * available.
39  */
40 public class MacTest {
41   private static final String EXPECTED_PROVIDER_NAME = TestUtil.EXPECTED_CRYPTO_OP_PROVIDER_NAME;
42   private static final String KEY_ALIAS_1 = "TestKey";
43 
44   @After
tearDown()45   public void tearDown() throws Exception {
46     KeyStoreUtil.cleanUpKeyStore();
47   }
48 
getKeyStoreSecretKey(byte[] keyMaterial, String algorithm, boolean isStrongBox)49   private static Key getKeyStoreSecretKey(byte[] keyMaterial, String algorithm, boolean isStrongBox)
50           throws Exception {
51       KeyStore keyStore = KeyStoreUtil.saveSecretKeyToKeystore(KEY_ALIAS_1,
52                             new SecretKeySpec(keyMaterial, algorithm),
53                             new KeyProtection.Builder(KeyProperties.PURPOSE_SIGN)
54                                     .setIsStrongBoxBacked(isStrongBox).build());
55     return keyStore.getKey(KEY_ALIAS_1, null);
56   }
57 
58   /**
59    * Computes the maximum of an array with at least one element.
60    *
61    * @param values the values from which the max is computed.
62    * @return the maximum
63    * @throws IllegalArgumentException if values is empty of null.
64    */
max(int[] values)65   private static int max(int[] values) {
66     if (values == null || values.length == 0) {
67       throw new IllegalArgumentException("Expecting an array with at least one element");
68     }
69     int result = Integer.MIN_VALUE;
70     for (int value : values) {
71       result = Math.max(result, value);
72     }
73     return result;
74   }
75 
arrayEquals(byte[] a, byte[] b)76   protected static boolean arrayEquals(byte[] a, byte[] b) {
77     if (a.length != b.length) {
78       return false;
79     }
80     byte res = 0;
81     for (int i = 0; i < a.length; i++) {
82       res |= (byte) (a[i] ^ b[i]);
83     }
84     return res == 0;
85   }
86 
87   /**
88    * Tests computing a MAC by computing it multiple times. The test passes all the results are the
89    * same in all cases.
90    *
91    * @param algorithm the name of the MAC (e.g. "HMACSHA1")
92    * @param key the key of the MAC
93    * @param data input data for the MAC. The size of the data must be at least as long as the sum of
94    *     all chunkSizes.
95    * @param chunkSizes the sizes of the chunks used in the calls of update
96    */
testUpdateWithChunks(String algorithm, Key key, byte[] data, int... chunkSizes)97   private void testUpdateWithChunks(String algorithm, Key key, byte[] data, int... chunkSizes)
98       throws Exception {
99     Mac mac = Mac.getInstance(algorithm, EXPECTED_PROVIDER_NAME);
100 
101     // First evaluation: compute MAC in one piece.
102     int totalLength = 0;
103     for (int chunkSize : chunkSizes) {
104       totalLength += chunkSize;
105     }
106     mac.init(key);
107     mac.update(data, 0, totalLength);
108     byte[] mac1 = mac.doFinal();
109 
110     // Second evaluation: using multiple chunks
111     mac.init(key);
112     int start = 0;
113     for (int chunkSize : chunkSizes) {
114       mac.update(data, start, chunkSize);
115       start += chunkSize;
116     }
117     byte[] mac2 = mac.doFinal();
118     if (!arrayEquals(mac1, mac2)) {
119       fail(
120           "Different MACs for same input:"
121               + " computed as one piece:"
122               + TestUtil.bytesToHex(mac1)
123               + " computed with multiple array segments:"
124               + TestUtil.bytesToHex(mac2));
125     }
126     // Third evaluation: using ByteBuffers
127     mac.init(key);
128     start = 0;
129     for (int chunkSize : chunkSizes) {
130       ByteBuffer chunk = ByteBuffer.wrap(data, start, chunkSize);
131       mac.update(chunk);
132       start += chunkSize;
133     }
134     byte[] mac3 = mac.doFinal();
135     if (!arrayEquals(mac1, mac3)) {
136       fail(
137           "Different MACs for same input:"
138               + " computed as one piece:"
139               + TestUtil.bytesToHex(mac1)
140               + " computed with wrapped chunks:"
141               + TestUtil.bytesToHex(mac3));
142     }
143     // Forth evaluation: using ByteBuffer slices.
144     // The effect of using slice() is that the resulting ByteBuffer has
145     // position 0, but possibly an non-zero value for arrayOffset().
146     mac.init(key);
147     start = 0;
148     for (int chunkSize : chunkSizes) {
149       ByteBuffer chunk = ByteBuffer.wrap(data, start, chunkSize).slice();
150       mac.update(chunk);
151       start += chunkSize;
152     }
153     byte[] mac4 = mac.doFinal();
154     if (!arrayEquals(mac1, mac4)) {
155       fail(
156           "Different MACs for same input:"
157               + " computed as one piece:"
158               + TestUtil.bytesToHex(mac1)
159               + " computed with ByteBuffer slices:"
160               + TestUtil.bytesToHex(mac4));
161     }
162   }
163 
164   /**
165    * The paper "Finding Bugs in Cryptographic Hash Function Implementations" by Mouha, Raunak, Kuhn,
166    * and Kacker, https://eprint.iacr.org/2017/891.pdf contains an analysis of implementations
167    * submitted to the SHA-3 competition. Many of the implementations contain bugs. The authors
168    * propose some tests for cryptographic libraries. The test here implements a check for
169    * incremental updates with the values proposed in Table 3.
170    */
testUpdate(String algorithm, Key key)171   private void testUpdate(String algorithm, Key key) throws Exception {
172     int[] chunkSize1 = {0, 8, 16, 24, 32, 40, 48, 56, 64};
173     int[] chunkSize2 = {0, 8, 16, 24, 32, 40, 48, 56, 64};
174     int[] chunkSize3 = {0, 8, 16, 32, 64, 128, 256, 512, 1024, 2048};
175     int[] chunkSize4 = {
176       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,
177       26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48,
178       49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 127, 128, 129, 255, 256,
179       257, 511, 512, 513
180     };
181     int maxSize = max(chunkSize1) + max(chunkSize2) + max(chunkSize3) + max(chunkSize4);
182     byte[] data = new byte[maxSize];
183     SecureRandom rand = new SecureRandom();
184     rand.nextBytes(data);
185     for (int size1 : chunkSize1) {
186       for (int size2 : chunkSize2) {
187         for (int size3 : chunkSize3) {
188           for (int size4 : chunkSize4) {
189             testUpdateWithChunks(algorithm, key, data, size1, size2, size3, size4);
190           }
191         }
192       }
193     }
194   }
195 
testMac(String algorithm, int keySize)196   public void testMac(String algorithm, int keySize) throws Exception {
197     testMac(algorithm, keySize, false);
198   }
testMac(String algorithm, int keySize, boolean isStrongBox)199   public void testMac(String algorithm, int keySize, boolean isStrongBox) throws Exception {
200     try {
201       Mac.getInstance(algorithm, EXPECTED_PROVIDER_NAME);
202     } catch (NoSuchAlgorithmException ex) {
203       fail("Algorithm " + algorithm + " is not supported.");
204     }
205     byte[] key = new byte[keySize];
206     SecureRandom rand = new SecureRandom();
207     rand.nextBytes(key);
208     testUpdate(algorithm, getKeyStoreSecretKey(key, algorithm, isStrongBox));
209   }
210 
211   @Test
212   // Long-running MAC tests expose inefficiencies on some devices. Ignore the test until
213   // performance requirements can be defined. See http://b/296367623
214   @Ignore
testHmacSha1()215   public void testHmacSha1() throws Exception {
216     testMac("HMACSHA1", 20);
217   }
218 
219   @Test
220   // Long-running MAC tests expose inefficiencies on some devices. Ignore the test until
221   // performance requirements can be defined. See http://b/296367623
222   @Ignore
testHmacSha224()223   public void testHmacSha224() throws Exception {
224     testMac("HMACSHA224", 28);
225   }
226 
227   @Test
228   // Long-running MAC tests expose inefficiencies on some devices. Ignore the test until
229   // performance requirements can be defined. See http://b/296367623
230   @Ignore
testHmacSha256()231   public void testHmacSha256() throws Exception {
232     testMac("HMACSHA256", 32);
233   }
234 
235   @Test
236   @Ignore // StrongBox takes very long time to complete this test and CTS timed out (b/242028608), hence ignoring it.
testHmacSha256_StrongBox()237   public void testHmacSha256_StrongBox() throws Exception {
238     KeyStoreUtil.assumeStrongBox();
239     testMac("HMACSHA256", 32, true);
240   }
241 
242   @Test
243   // Long-running MAC tests expose inefficiencies on some devices. Ignore the test until
244   // performance requirements can be defined. See http://b/296367623
245   @Ignore
testHmacSha384()246   public void testHmacSha384() throws Exception {
247     testMac("HMACSHA384", 48);
248   }
249 
250   @Test
251   // Long-running MAC tests expose inefficiencies on some devices. Ignore the test until
252   // performance requirements can be defined. See http://b/296367623
253   @Ignore
testHmacSha512()254   public void testHmacSha512() throws Exception {
255     testMac("HMACSHA512", 64);
256   }
257 
258   @Test
259   @Ignore // HmacSha3 algorithms are not supported in AndroidKeyStore
testHmacSha3_224()260   public void testHmacSha3_224() throws Exception {
261     testMac("HMACSHA3-224", 28);
262   }
263 
264   @Test
265   @Ignore // HmacSha3 algorithms are not supported in AndroidKeyStore
testHmacSha3_256()266   public void testHmacSha3_256() throws Exception {
267     testMac("HMACSHA3-256", 32);
268   }
269 
270   @Test
271   @Ignore // HmacSha3 algorithms are not supported in AndroidKeyStore
testHmacSha3_384()272   public void testHmacSha3_384() throws Exception {
273     testMac("HMACSHA3-384", 48);
274   }
275 
276   @Test
277   @Ignore // HmacSha3 algorithms are not supported in AndroidKeyStore
testHmacSha3_512()278   public void testHmacSha3_512() throws Exception {
279     testMac("HMACSHA3-512", 64);
280   }
281 
282   /**
283    * Computes the mac of a message repeated multiple times.
284    *
285    * @param algorithm the message digest (e.g. "HMACSHA1")
286    * @param message the bytes to mac
287    * @param repetitions the number of repetitions of the message
288    * @return the digest
289    * @throws GeneralSecurityException if the computation of the mac fails (e.g. because the
290    *     algorithm is unknown).
291    */
macRepeatedMessage(String algorithm, Key key, byte[] message, long repetitions)292   public byte[] macRepeatedMessage(String algorithm, Key key, byte[] message, long repetitions)
293       throws Exception {
294     Mac mac = Mac.getInstance(algorithm, EXPECTED_PROVIDER_NAME);
295     mac.init(key);
296     // If the message is short then it is more efficient to collect multiple copies
297     // of the message in one chunk and call update with the larger chunk.
298     final int maxChunkSize = 1 << 16;
299     if (message.length != 0 && 2 * message.length < maxChunkSize) {
300       int repetitionsPerChunk = maxChunkSize / message.length;
301       byte[] chunk = new byte[message.length * repetitionsPerChunk];
302       for (int i = 0; i < repetitionsPerChunk; i++) {
303         System.arraycopy(message, 0, chunk, i * message.length, message.length);
304       }
305       while (repetitions >= repetitionsPerChunk) {
306         mac.update(chunk);
307         repetitions -= repetitionsPerChunk;
308       }
309     }
310 
311     for (int i = 0; i < repetitions; i++) {
312       mac.update(message);
313     }
314     return mac.doFinal();
315   }
316 
317   /**
318    * A test for hashing long messages.
319    *
320    * <p>Java does not allow strings or arrays of size 2^31 or longer. However, it is still possible
321    * to compute a MAC of a long message by repeatedly calling Mac.update(). To compute correct MACs
322    * the total message length must be known. This length can be bigger than 2^32 bytes.
323    *
324    * <p>Reference: http://www-01.ibm.com/support/docview.wss?uid=swg1PK62549 IBMJCE SHA-1
325    * IMPLEMENTATION RETURNS INCORRECT HASH FOR LARGE SETS OF DATA
326    */
testLongMac( String algorithm, String keyhex, String message, long repetitions, String expected)327   private void testLongMac(
328           String algorithm, String keyhex, String message, long repetitions, String expected)
329           throws Exception {
330     testLongMac(algorithm, keyhex, message, repetitions, expected, false);
331   }
testLongMac( String algorithm, String keyhex, String message, long repetitions, String expected, boolean isStrongBox)332   private void testLongMac(
333       String algorithm, String keyhex, String message, long repetitions, String expected,
334           boolean isStrongBox) throws Exception {
335 
336     Key key = getKeyStoreSecretKey(TestUtil.hexToBytes(keyhex), algorithm, isStrongBox);
337     byte[] bytes = message.getBytes(UTF_8);
338     byte[] mac = null;
339     try {
340       mac = macRepeatedMessage(algorithm, key, bytes, repetitions);
341     } catch (NoSuchAlgorithmException ex) {
342       fail("Algorithm " + algorithm + " is not supported.");
343     }
344     String hexmac = TestUtil.bytesToHex(mac);
345     assertEquals(expected, hexmac);
346   }
347 
348   @Test
349   // Long-running MAC tests expose inefficiencies on some devices. Ignore the test until
350   // performance requirements can be defined. See http://b/288588810#comment27
351   @Ignore
testLongMacSha1()352   public void testLongMacSha1() throws Exception {
353     // b/244609904#comment64
354     KeyStoreUtil.assumeKeyMintV1OrNewer(false);
355 
356     testLongMac(
357         "HMACSHA1",
358         "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
359         "a",
360         2147483647L,
361         "703925f6dceb9c602969ad39bba9b1eb49472071");
362     testLongMac(
363         "HMACSHA1",
364         "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
365         "a",
366         5000000000L,
367         "d7f4c387f2237ea119fcc27cd7520fc5132b6230");
368   }
369 
370   @Test
371   // Long-running MAC tests expose inefficiencies on some devices. Ignore the test until
372   // performance requirements can be defined. See http://b/288588810#comment27
373   @Ignore
testLongMacSha256()374   public void testLongMacSha256() throws Exception {
375     // b/244609904#comment64
376     KeyStoreUtil.assumeKeyMintV1OrNewer(false);
377     testLongMacSha256(false);
378   }
379   @Test
380   @Ignore // StrongBox takes very long time to complete this test and CTS timed out (b/242028608), hence ignoring it.
testLongMacSha256_StrongBox()381   public void testLongMacSha256_StrongBox() throws Exception {
382     KeyStoreUtil.assumeStrongBox();
383     testLongMacSha256(true);
384   }
testLongMacSha256(boolean isStrongBox)385   private void testLongMacSha256(boolean isStrongBox) throws Exception {
386     testLongMac(
387         "HMACSHA256",
388         "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
389         "a",
390         2147483647L,
391         "84f213c9bb5b329d547bc31dabed41939754b1af7482365ec74380c45f6ea0a7",
392         isStrongBox);
393     testLongMac(
394         "HMACSHA256",
395         "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
396         "a",
397         5000000000L,
398         "59a75754df7093fa4339aa618b64b104f153a5b42cc85394fdb8735b13ea684a",
399         isStrongBox);
400   }
401 
402   @Test
403   // Long-running MAC tests expose inefficiencies on some devices. Ignore the test until
404   // performance requirements can be defined. See http://b/288588810#comment27
405   @Ignore
testLongMacSha384()406   public void testLongMacSha384() throws Exception {
407     // b/244609904#comment64
408     KeyStoreUtil.assumeKeyMintV1OrNewer(false);
409 
410     testLongMac(
411         "HMACSHA384",
412         "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"
413             + "202122232425262728292a2b2c2d2e2f",
414         "a",
415         2147483647L,
416         "aea987905f64791691b3fdea06f8e4125f396ebb73f37894e961b1a7522a55da"
417             + "ecd856a70c92c6646e6f8c3fcb935528");
418     testLongMac(
419         "HMACSHA384",
420         "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"
421             + "202122232425262728292a2b2c2d2e2f",
422         "a",
423         5000000000L,
424         "88485c9c5714d43a99dacbc861988c7ea39c02d82104bf93e55ec1b8a24fe15a"
425             + "a477e6a84d159d8b7a3daaa89c4f2372");
426   }
427 
428   @Test
429   // Long-running MAC tests expose inefficiencies on some devices. Ignore the test until
430   // performance requirements can be defined. See http://b/288588810#comment27
431   @Ignore
testLongMacSha512()432   public void testLongMacSha512() throws Exception {
433     // b/244609904#comment64
434     KeyStoreUtil.assumeKeyMintV1OrNewer(false);
435 
436     testLongMac(
437         "HMACSHA512",
438         "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"
439             + "202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f",
440         "a",
441         2147483647L,
442         "fc68fbc294951c691e5bc085c3af026099f39a57230b242aaf1fc5ca691e05da"
443             + "d1a5de7d4f30e1c958c6a2cee6159218dab683187e6d56bab824a3adefde9102");
444     testLongMac(
445         "HMACSHA512",
446         "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"
447             + "202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f",
448         "a",
449         5000000000L,
450         "31b1d721b958203bff7d7ddf50d48b17fc760a80a99a7f23ec966ce3bbefff29"
451             + "0d176eebbb6a440960024be0726c94960bbf75816548a7fd4552c7baba4585ee");
452   }
453 }
454