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