• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.google.security.wycheproof;
18 
19 import com.google.security.wycheproof.WycheproofRunner.ExcludedTest;
20 import com.google.security.wycheproof.WycheproofRunner.ProviderType;
21 import com.google.security.wycheproof.WycheproofRunner.SlowTest;
22 import java.nio.ByteBuffer;
23 import java.security.AlgorithmParameterGenerator;
24 import java.security.AlgorithmParameters;
25 import java.security.InvalidAlgorithmParameterException;
26 import java.security.InvalidKeyException;
27 import java.security.NoSuchAlgorithmException;
28 import java.security.SecureRandom;
29 import java.util.ArrayList;
30 import java.util.Arrays;
31 import javax.crypto.Cipher;
32 import javax.crypto.ShortBufferException;
33 import javax.crypto.spec.GCMParameterSpec;
34 import javax.crypto.spec.IvParameterSpec;
35 import javax.crypto.spec.SecretKeySpec;
36 import 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  */
46 public 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 
GcmTestVector( String message, String keyMaterial, String nonce, String aad, String ciphertext, String tag)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    */
getTestVectors()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 
testVectors()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    */
testLateUpdateAAD()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    */
testIvReuse()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. */
testByteBuffer()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. */
testByteBufferAlias()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 
testReadOnlyByteBuffer()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    */
testByteBufferWithOffset()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 
testByteBufferTooShort()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    */
testDefaultTagSizeIvParameterSpec()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    */
testDefaultTagSizeAlgorithmParameterGenerator()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