1/**
2 * @license
3 * Copyright 2016 Google Inc. All rights reserved.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *   http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.google.security.wycheproof;
18
19import com.google.security.wycheproof.WycheproofRunner.ExcludedTest;
20import com.google.security.wycheproof.WycheproofRunner.ProviderType;
21import com.google.security.wycheproof.WycheproofRunner.SlowTest;
22import java.nio.ByteBuffer;
23import java.security.AlgorithmParameterGenerator;
24import java.security.AlgorithmParameters;
25import java.security.InvalidAlgorithmParameterException;
26import java.security.InvalidKeyException;
27import java.security.NoSuchAlgorithmException;
28import java.security.SecureRandom;
29import java.util.ArrayList;
30import java.util.Arrays;
31import javax.crypto.Cipher;
32import javax.crypto.ShortBufferException;
33import javax.crypto.spec.GCMParameterSpec;
34import javax.crypto.spec.IvParameterSpec;
35import javax.crypto.spec.SecretKeySpec;
36import junit.framework.TestCase;
37
38// TODO(bleichen):
39//   - For EAX I was able to derive some special cases by inverting OMAC.
40//     Not sure if that is possible here.
41/**
42 * Testing AES-GCM
43 *
44 * <p>Other tests using AES-GCM are: CipherInputStreamTest.java CipherOuputStreamTest.java
45 */
46public class AesGcmTest extends TestCase {
47
48  /** Test vectors */
49  public static class GcmTestVector {
50    final byte[] pt;
51    final byte[] aad;
52    final byte[] ct;
53    final String ptHex;
54    final String ctHex;
55    final GCMParameterSpec parameters;
56    final SecretKeySpec key;
57    final int nonceLengthInBits;
58    final int tagLengthInBits;
59
60    public GcmTestVector(
61        String message,
62        String keyMaterial,
63        String nonce,
64        String aad,
65        String ciphertext,
66        String tag) {
67      this.ptHex = message;
68      this.pt = TestUtil.hexToBytes(message);
69      this.aad = TestUtil.hexToBytes(aad);
70      this.ct = TestUtil.hexToBytes(ciphertext + tag);
71      this.ctHex = ciphertext + tag;
72      this.tagLengthInBits = 4 * tag.length();
73      this.nonceLengthInBits = 4 * nonce.length();
74      this.parameters = new GCMParameterSpec(tagLengthInBits, TestUtil.hexToBytes(nonce));
75      this.key = new SecretKeySpec(TestUtil.hexToBytes(keyMaterial), "AES");
76    }
77  };
78
79  private static final GcmTestVector[] GCM_TEST_VECTORS = {
80    new GcmTestVector(
81        "001d0c231287c1182784554ca3a21908",
82        "5b9604fe14eadba931b0ccf34843dab9",
83        "028318abc1824029138141a2",
84        "",
85        "26073cc1d851beff176384dc9896d5ff",
86        "0a3ea7a5487cb5f7d70fb6c58d038554"),
87    new GcmTestVector(
88        "001d0c231287c1182784554ca3a21908",
89        "5b9604fe14eadba931b0ccf34843dab9",
90        "921d2507fa8007b7bd067d34",
91        "00112233445566778899aabbccddeeff",
92        "49d8b9783e911913d87094d1f63cc765",
93        "1e348ba07cca2cf04c618cb4"),
94    new GcmTestVector(
95        "2035af313d1346ab00154fea78322105",
96        "aa023d0478dcb2b2312498293d9a9129",
97        "0432bc49ac34412081288127",
98        "aac39231129872a2",
99        "eea945f3d0f98cc0fbab472a0cf24e87",
100        "4bb9b4812519dadf9e1232016d068133"),
101    new GcmTestVector(
102        "2035af313d1346ab00154fea78322105",
103        "aa023d0478dcb2b2312498293d9a9129",
104        "0432bc49ac344120",
105        "aac39231129872a2",
106        "64c36bb3b732034e3a7d04efc5197785",
107        "b7d0dd70b00d65b97cfd080ff4b819d1"),
108    new GcmTestVector(
109        "02efd2e5782312827ed5d230189a2a342b277ce048462193",
110        "2034a82547276c83dd3212a813572bce",
111        "3254202d854734812398127a3d134421",
112        "1a0293d8f90219058902139013908190bc490890d3ff12a3",
113        "64069c2d58690561f27ee199e6b479b6369eec688672bde9",
114        "9b7abadd6e69c1d9ec925786534f5075"),
115  };
116
117  /**
118   * Returns the GCM test vectors supported by the current provider.
119   * This is necessary since not every provider supports all parameters sizes.
120   * For example SUNJCE does not support 8 byte tags and Conscrypt only supports
121   * 12 byte nonces.
122   * Such restrictions are often made because AES-GCM is a relatively weak algorithm and
123   * especially small parameter sizes can lead to easy attacks.
124   * Avoiding such small parameter sizes should not be seen as a bug in the library.
125   *
126   * <p>The only assumption we make here is that all test vectors with 128 bit tags and nonces
127   * with at least 96 bits are supported.
128   */
129  private Iterable<GcmTestVector> getTestVectors() throws Exception {
130    ArrayList<GcmTestVector> supported = new ArrayList<GcmTestVector>();
131    for (GcmTestVector test : GCM_TEST_VECTORS) {
132      if (test.nonceLengthInBits != 96 || test.tagLengthInBits != 128) {
133        try {
134          // Checks whether the parameter size is supported.
135          // It would be nice if there was a way to check this without trying to encrypt.
136          Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
137          cipher.init(Cipher.ENCRYPT_MODE, test.key, test.parameters);
138        } catch (InvalidKeyException | InvalidAlgorithmParameterException ex) {
139          // Not supported
140          continue;
141        }
142      }
143      supported.add(test);
144    }
145    return supported;
146  }
147
148  public void testVectors() throws Exception {
149    for (GcmTestVector test : getTestVectors()) {
150      Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
151      cipher.init(Cipher.ENCRYPT_MODE, test.key, test.parameters);
152      cipher.updateAAD(test.aad);
153      byte[] ct = cipher.doFinal(test.pt);
154      assertEquals(test.ctHex, TestUtil.bytesToHex(ct));
155    }
156  }
157
158  /**
159   * Typically one should always call updateAAD before any call to update. This test checks what
160   * happens if the order is reversed. The test expects that a correct implementation either
161   * computes the tag correctly or throws an exception.
162   *
163   * <p>For example, OpenJdk did compute incorrect tags in this case. The bug has been fixed in
164   * http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/rev/89c06ca1e6cc
165   *
166   * <p>For example BouncyCastle computes correct tags if the calls are reversed, SunJCE and OpenJdk
167   * now throw exceptions.
168   */
169  public void testLateUpdateAAD() throws Exception {
170    for (GcmTestVector test : getTestVectors()) {
171      Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
172      cipher.init(Cipher.ENCRYPT_MODE, test.key, test.parameters);
173      byte[] c0 = cipher.update(test.pt);
174      try {
175        cipher.updateAAD(test.aad);
176      } catch (java.lang.IllegalStateException ex) {
177        // Throwing an exception is valid behaviour.
178        continue;
179      }
180      byte[] c1 = cipher.doFinal();
181      String result = TestUtil.bytesToHex(c0) + TestUtil.bytesToHex(c1);
182      assertEquals(test.ctHex, result);
183    }
184  }
185
186  /**
187   * JCE has a dangerous feature: after a doFinal the cipher is typically reinitialized using the
188   * previous IV. This "feature" can easily break AES-GCM usages, because encrypting twice with
189   * the same key and IV leaks the authentication key. Hence any reasonable implementation of
190   * AES-GCM should not allow this. The expected behaviour of OpenJDK can be derived from the tests
191   * in jdk/test/com/sun/crypto/provider/Cipher/AES/TestGCMKeyAndIvCheck.java.
192   * OpenJDK does not allow two consecutive initializations for encryption with the same key and IV.
193   *
194   * <p>The test here is weaker than the restrictions in OpenJDK. The only requirement here is that
195   * reusing a Cipher without an explicit init() is caught.
196   *
197   * <p>BouncyCastle 1.52 failed this test
198   *
199   * <p>Conscrypt failed this test
200   */
201  public void testIvReuse() throws Exception {
202    for (GcmTestVector test : getTestVectors()) {
203      Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
204      cipher.init(Cipher.ENCRYPT_MODE, test.key, test.parameters);
205      cipher.updateAAD(test.aad);
206      byte[] ct1 = cipher.doFinal(test.pt);
207      try {
208        byte[] ct2 = cipher.doFinal(test.pt);
209        fail(
210            "It should not possible to reuse an IV."
211                + " ct1:"
212                + TestUtil.bytesToHex(ct1)
213                + " ct2:"
214                + TestUtil.bytesToHex(ct2));
215      } catch (java.lang.IllegalStateException ex) {
216        // This is expected.
217      }
218    }
219  }
220
221  /** Encryption with ByteBuffers. */
222  public void testByteBuffer() throws Exception {
223    for (GcmTestVector test : getTestVectors()) {
224      // Encryption
225      Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
226      ByteBuffer ptBuffer = ByteBuffer.wrap(test.pt);
227      cipher.init(Cipher.ENCRYPT_MODE, test.key, test.parameters);
228      int outputSize = cipher.getOutputSize(test.pt.length);
229      ByteBuffer ctBuffer = ByteBuffer.allocate(outputSize);
230      cipher.updateAAD(test.aad);
231      cipher.doFinal(ptBuffer, ctBuffer);
232      assertEquals(test.ctHex, TestUtil.byteBufferToHex(ctBuffer));
233
234      // Decryption
235      ctBuffer.flip();
236      cipher.init(Cipher.DECRYPT_MODE, test.key, test.parameters);
237      outputSize = cipher.getOutputSize(test.ct.length);
238      ByteBuffer decrypted = ByteBuffer.allocate(outputSize);
239      cipher.updateAAD(test.aad);
240      cipher.doFinal(ctBuffer, decrypted);
241      assertEquals(test.ptHex, TestUtil.byteBufferToHex(decrypted));
242    }
243  }
244
245  /** Encryption with ByteBuffers should be copy-safe. */
246  public void testByteBufferAlias() throws Exception {
247    for (GcmTestVector test : getTestVectors()) {
248      // Encryption
249      Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
250      cipher.init(Cipher.ENCRYPT_MODE, test.key, test.parameters);
251      int outputSize = cipher.getOutputSize(test.pt.length);
252      byte[] backingArray = new byte[outputSize];
253      ByteBuffer ptBuffer = ByteBuffer.wrap(backingArray);
254      ptBuffer.put(test.pt);
255      ptBuffer.flip();
256      ByteBuffer ctBuffer = ByteBuffer.wrap(backingArray);
257      cipher.updateAAD(test.aad);
258      cipher.doFinal(ptBuffer, ctBuffer);
259      assertEquals(test.ctHex, TestUtil.byteBufferToHex(ctBuffer));
260
261      // Decryption
262      ByteBuffer decrypted = ByteBuffer.wrap(backingArray);
263      ctBuffer.flip();
264      cipher.init(Cipher.DECRYPT_MODE, test.key, test.parameters);
265      cipher.updateAAD(test.aad);
266      cipher.doFinal(ctBuffer, decrypted);
267      assertEquals(test.ptHex, TestUtil.byteBufferToHex(decrypted));
268    }
269  }
270
271  public void testReadOnlyByteBuffer() throws Exception {
272    for (GcmTestVector test : getTestVectors()) {
273      // Encryption
274      Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
275      ByteBuffer ptBuffer = ByteBuffer.wrap(test.pt).asReadOnlyBuffer();
276      cipher.init(Cipher.ENCRYPT_MODE, test.key, test.parameters);
277      int outputSize = cipher.getOutputSize(test.pt.length);
278      ByteBuffer ctBuffer = ByteBuffer.allocate(outputSize);
279      cipher.updateAAD(test.aad);
280      cipher.doFinal(ptBuffer, ctBuffer);
281      assertEquals(test.ctHex, TestUtil.byteBufferToHex(ctBuffer));
282
283      // Decryption
284      ctBuffer.flip();
285      ctBuffer = ctBuffer.asReadOnlyBuffer();
286      cipher.init(Cipher.DECRYPT_MODE, test.key, test.parameters);
287      outputSize = cipher.getOutputSize(test.ct.length);
288      ByteBuffer decrypted = ByteBuffer.allocate(outputSize);
289      cipher.updateAAD(test.aad);
290      cipher.doFinal(ctBuffer, decrypted);
291      assertEquals(test.ptHex, TestUtil.byteBufferToHex(decrypted));
292    }
293  }
294
295  /**
296   * If a ByteBuffer is backed by an array and not readonly, then it is possible to access the data
297   * through the .array() method. An implementation using this possiblity must ensure that it
298   * considers the offset.
299   */
300  public void testByteBufferWithOffset() throws Exception {
301    for (GcmTestVector test : getTestVectors()) {
302      // Encryption
303      Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
304      ByteBuffer ptBuffer = ByteBuffer.wrap(new byte[test.pt.length + 50]);
305      ptBuffer.position(5);
306      ptBuffer = ptBuffer.slice();
307      ptBuffer.put(test.pt);
308      ptBuffer.flip();
309
310      ByteBuffer ctBuffer = ByteBuffer.wrap(new byte[test.ct.length + 50]);
311      ctBuffer.position(8);
312      ctBuffer = ctBuffer.slice();
313      cipher.init(Cipher.ENCRYPT_MODE, test.key, test.parameters);
314      cipher.updateAAD(test.aad);
315      cipher.doFinal(ptBuffer, ctBuffer);
316      assertEquals(test.ctHex, TestUtil.byteBufferToHex(ctBuffer));
317      ctBuffer.flip();
318
319      // Decryption
320      ByteBuffer decBuffer = ByteBuffer.wrap(new byte[test.pt.length + 50]);
321      decBuffer.position(6);
322      decBuffer = decBuffer.slice();
323      cipher.init(Cipher.DECRYPT_MODE, test.key, test.parameters);
324      cipher.updateAAD(test.aad);
325      cipher.doFinal(ctBuffer, decBuffer);
326      assertEquals(test.ptHex, TestUtil.byteBufferToHex(decBuffer));
327    }
328  }
329
330  public void testByteBufferTooShort() throws Exception {
331    for (GcmTestVector test : getTestVectors()) {
332      // Encryption
333      Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
334      ByteBuffer ptBuffer = ByteBuffer.wrap(test.pt);
335      ByteBuffer ctBuffer = ByteBuffer.allocate(test.ct.length - 1);
336      cipher.init(Cipher.ENCRYPT_MODE, test.key, test.parameters);
337      cipher.updateAAD(test.aad);
338      try {
339        cipher.doFinal(ptBuffer, ctBuffer);
340        fail("This should not work");
341      } catch (ShortBufferException ex) {
342        // expected
343      }
344
345      // Decryption
346      ctBuffer = ByteBuffer.wrap(test.ct);
347      ByteBuffer decrypted = ByteBuffer.allocate(test.pt.length - 1);
348      cipher.init(Cipher.DECRYPT_MODE, test.key, test.parameters);
349      cipher.updateAAD(test.aad);
350      try {
351        cipher.doFinal(ctBuffer, decrypted);
352        fail("This should not work");
353      } catch (ShortBufferException ex) {
354        // expected
355      }
356    }
357  }
358
359  /**
360   * The default authentication tag size should be 128-bit by default for the following reasons:
361   * <br>
362   * (1) Security: Ferguson, N., Authentication Weaknesses in GCM, Natl. Inst. Stand. Technol. [Web
363   * page], http://www.csrc.nist.gov/groups/ST/toolkit/BCM/documents/comments/
364   * CWC-GCM/Ferguson2.pdf, May 20, 2005. This paper points out that a n-bit tag has lower strength
365   * than expected. <br>
366   * (2) Compatibility: Assume an implementer tests some code using one provider than switches to
367   * another provider. Such a switch should ideally not lower the security. <br>
368   * Conscrypt used to have only 12-byte authentication tag (b/26186727).
369   */
370  public void testDefaultTagSizeIvParameterSpec() throws Exception {
371    byte[] counter = new byte[12];
372    byte[] input = new byte[16];
373    byte[] key = new byte[16];
374    Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
375    try {
376      cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"), new IvParameterSpec(counter));
377    } catch (InvalidAlgorithmParameterException ex) {
378      // OpenJDK8 does not support IvParameterSpec for GCM.
379      System.out.println("testDefaultTagSizeIvParameterSpec:" + ex.toString());
380      return;
381    }
382    byte[] output = cipher.doFinal(input);
383    assertEquals(input.length + 16, output.length);
384  }
385
386  /**
387   * The default authentication tag size should be 128-bit by default for the following reasons:
388   * <br>
389   * (1) Security: Ferguson, N., Authentication Weaknesses in GCM, Natl. Inst. Stand. Technol. [Web
390   * page], http://www.csrc.nist.gov/groups/ST/toolkit/BCM/documents/comments/
391   * CWC-GCM/Ferguson2.pdf, May 20, 2005. This paper points out that a n-bit tag has lower strength
392   * than expected. <br>
393   * (2) Compatibility: Assume an implementer tests some code using one provider than switches to
394   * another provider. Such a switch should ideally not lower the security. <br>
395   * BouncyCastle used to have only 12-byte authentication tag (b/26186727).
396   */
397  public void testDefaultTagSizeAlgorithmParameterGenerator() throws Exception {
398    byte[] input = new byte[10];
399    byte[] key = new byte[16];
400    Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
401    try {
402      AlgorithmParameterGenerator.getInstance("GCM");
403    } catch (NoSuchAlgorithmException ex) {
404      // Conscrypt does not support AlgorithmParameterGenerator for GCM.
405      System.out.println("testDefaultTagSizeAlgorithmParameterGenerator:" + ex.toString());
406      return;
407    }
408    AlgorithmParameters param = AlgorithmParameterGenerator.getInstance("GCM").generateParameters();
409    cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"), param);
410    byte[] output = cipher.doFinal(input);
411    assertEquals(input.length + 16, output.length);
412  }
413
414  /**
415   * Test AES-GCM wrapped around counter bug which leaks plaintext and authentication key. Let's
416   * consider 12-byte IV, counter = IV || 0^31 || 1. For each encryption block, the last 4 bytes of
417   * the counter is increased by 1. After 2^32 blocks, the counter will be wrapped around causing
418   * counter collision and hence, leaking plaintext and authentication key as explained below. The
419   * library must make a check to make sure that the plaintext's length never exceeds 2^32 - 2
420   * blocks. Note that this is different from usual IV collisions because it happens even if users
421   * use different IVs. <br>
422   * We have: <br>
423   * J0 = IV || 0^31 || 1 <br>
424   * Plaintext: P[0], P[1], P[2], .... <br>
425   * Ciphertext: <br>
426   * C[0] = Enc(K, (J0 + 1) % 2^32) XOR P[0] <br>
427   * C[1] = Enc(K, (J0 + 2) % 2^32) XOR P[1] <br>
428   * C[2] = Enc(K, (J0 + 3) % 2^32) XOR P[2] <br>
429   * ... <br>
430   * C[2^32 - 1] = Enc(K, J0) XOR P[2^32 - 1] <br>
431   * C[2^32] = Enc(K, (J0 + 1)% 2^32) XOR P[2^32] <br>
432   * It means that after 2^32 blocks, the counter is wrapped around causing counter collisions. In
433   * counter mode, once the counter is collided then it's reasonable to assume that the plaintext is
434   * leaked. As the ciphertext is already known to attacker, Enc(K, J0) is leaked. <br>
435   * Now, as the authentication tag T is computed as GHASH(H, {}, C) XOR E(K, J0), the attacker can
436   * learn GHASH(H, {}, C}. It essentially means that the attacker finds a polynomial where H is the
437   * root (see Joux attack http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/Joux_comments.pdf).
438   * Solving polynomial equation in GF(2^128) is enough to extract the authentication key.
439   *
440   * <p>BouncyCastle used to have this bug (CVE-2015-6644).
441   *
442   * <p>OpenJDK8 used to have this bug (http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/rev/0c3ed12cdaf5)
443   *
444   * <p>The test is slow as we have to encrypt 2^32 blocks.
445   */
446  // TODO(quannguyen): Is there a faster way to test it?
447/*
448  @ExcludedTest(
449    providers = {ProviderType.CONSCRYPT},
450    comment = "Conscrypt doesn't support streaming, would crash")
451  @SlowTest(
452    providers = {ProviderType.BOUNCY_CASTLE, ProviderType.SPONGY_CASTLE, ProviderType.OPENJDK})
453  public void testWrappedAroundCounter() throws Exception {
454    try {
455      byte[] iv = new byte[12];
456      byte[] input = new byte[16];
457      byte[] key = new byte[16];
458      (new SecureRandom()).nextBytes(key);
459      Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
460      cipher.init(
461          Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"), new GCMParameterSpec(16 * 8, iv));
462      byte[] output = cipher.update(input);
463      for (long i = 0; i < 4294967296L + 2; i++) {
464        byte[] output1 = cipher.update(input);
465        assertFalse("GCM Wrapped Around Counter" + i, Arrays.equals(output, output1));
466      }
467      fail("Expected Exception");
468    } catch (Exception expected) {
469      System.out.println("testWrappedAroundcounter:" + expected.toString());
470    }
471  }
472*/
473}
474