diff options
Diffstat (limited to 'java/com/google/security/wycheproof/testcases/AesGcmTest.java')
-rw-r--r-- | java/com/google/security/wycheproof/testcases/AesGcmTest.java | 405 |
1 files changed, 405 insertions, 0 deletions
diff --git a/java/com/google/security/wycheproof/testcases/AesGcmTest.java b/java/com/google/security/wycheproof/testcases/AesGcmTest.java new file mode 100644 index 0000000..c3c3dad --- /dev/null +++ b/java/com/google/security/wycheproof/testcases/AesGcmTest.java @@ -0,0 +1,405 @@ +/** + * @license + * Copyright 2016 Google Inc. All rights reserved. + * 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 java.nio.ByteBuffer; +import java.security.AlgorithmParameterGenerator; +import java.security.AlgorithmParameters; +import java.security.InvalidAlgorithmParameterException; +import java.security.NoSuchAlgorithmException; +import java.security.Security; +import javax.crypto.Cipher; +import javax.crypto.ShortBufferException; +import javax.crypto.spec.GCMParameterSpec; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import junit.framework.TestCase; + +// TODO(bleichen): +// - For EAX I was able to derive some special cases by inverting OMAC. +// Not sure if that is possible here. +// - Gcm used to skip the tag verification in BouncyCastle when using ByteBuffers. +// test this. +// - Other Bytebuffer tests: Buffers with offset, Readonly buffers +// - The SUNJce provider requires tags that are at least 96 bits long. +// It would make sense to require this for all providers and add a test. +// - conscrypt only allows 12 byte IVs. +/** + * Testing AES-GCM + * + * <p>Other tests using AES-GCM are: CipherInputStreamTest.java CipherOuputStreamTest.java + */ +public class AesGcmTest extends TestCase { + + /** Test vectors */ + public static class GcmTestVector { + final byte[] pt; + final byte[] aad; + final byte[] ct; + final String ptHex; + final String ctHex; + final GCMParameterSpec parameters; + final SecretKeySpec key; + + public GcmTestVector( + String message, + String keyMaterial, + String nonce, + String aad, + String ciphertext, + String tag) { + this.ptHex = message; + this.pt = TestUtil.hexToBytes(message); + this.aad = TestUtil.hexToBytes(aad); + this.ct = TestUtil.hexToBytes(ciphertext + tag); + this.ctHex = ciphertext + tag; + int tagLength = 4 * tag.length(); + this.parameters = new GCMParameterSpec(tagLength, TestUtil.hexToBytes(nonce)); + this.key = new SecretKeySpec(TestUtil.hexToBytes(keyMaterial), "AES"); + } + }; + + // default, used for BouncyCastle + private static final GcmTestVector[] DEFAULT_GCM_TEST_VECTORS = { + new GcmTestVector( + "001d0c231287c1182784554ca3a21908", + "5b9604fe14eadba931b0ccf34843dab9", + "028318abc1824029138141a2", + "", + "26073cc1d851beff176384dc9896d5ff", + "0a3ea7a5487cb5f7d70fb6c58d038554"), + new GcmTestVector( + "001d0c231287c1182784554ca3a21908", + "5b9604fe14eadba931b0ccf34843dab9", + "921d2507fa8007b7bd067d34", + "00112233445566778899aabbccddeeff", + "49d8b9783e911913d87094d1f63cc765", + "1e348ba07cca2cf04c618cb4"), + new GcmTestVector( + "2035af313d1346ab00154fea78322105", + "aa023d0478dcb2b2312498293d9a9129", + "0432bc49ac34412081288127", + "aac39231129872a2", + "eea945f3d0f98cc0fbab472a0cf24e87", + "4bb9b4812519dadf9e1232016d068133"), + new GcmTestVector( + "2035af313d1346ab00154fea78322105", + "aa023d0478dcb2b2312498293d9a9129", + "0432bc49ac344120", + "aac39231129872a2", + "64c36bb3b732034e3a7d04efc5197785", + "b7d0dd70b00d65b97cfd080ff4b819d1"), + }; + + // Conscrypt doesn't support 8-byte nonces + private static final GcmTestVector[] OPENSSL_PROVIDER_GCM_TEST_VECTORS = { + new GcmTestVector( + "001d0c231287c1182784554ca3a21908", + "5b9604fe14eadba931b0ccf34843dab9", + "028318abc1824029138141a2", + "", + "26073cc1d851beff176384dc9896d5ff", + "0a3ea7a5487cb5f7d70fb6c58d038554"), + new GcmTestVector( + "001d0c231287c1182784554ca3a21908", + "5b9604fe14eadba931b0ccf34843dab9", + "921d2507fa8007b7bd067d34", + "00112233445566778899aabbccddeeff", + "49d8b9783e911913d87094d1f63cc765", + "1e348ba07cca2cf0"), + new GcmTestVector( + "2035af313d1346ab00154fea78322105", + "aa023d0478dcb2b2312498293d9a9129", + "0432bc49ac34412081288127", + "aac39231129872a2", + "eea945f3d0f98cc0fbab472a0cf24e87", + "4bb9b4812519dadf9e1232016d068133"), + }; + + // SunJCE doesn't support 8-byte tags + private static final GcmTestVector[] SUNJCE_PROVIDER_GCM_TEST_VECTORS = { + new GcmTestVector( + "001d0c231287c1182784554ca3a21908", + "5b9604fe14eadba931b0ccf34843dab9", + "028318abc1824029138141a2", + "", + "26073cc1d851beff176384dc9896d5ff", + "0a3ea7a5487cb5f7d70fb6c58d038554"), + new GcmTestVector( + "2035af313d1346ab00154fea78322105", + "aa023d0478dcb2b2312498293d9a9129", + "0432bc49ac34412081288127", + "aac39231129872a2", + "eea945f3d0f98cc0fbab472a0cf24e87", + "4bb9b4812519dadf9e1232016d068133"), + new GcmTestVector( + "2035af313d1346ab00154fea78322105", + "aa023d0478dcb2b2312498293d9a9129", + "0432bc49ac344120", + "aac39231129872a2", + "64c36bb3b732034e3a7d04efc5197785", + "b7d0dd70b00d65b97cfd080ff4b819d1"), + }; + + private GcmTestVector[] getTestVectors() { + GcmTestVector[] gcmTestVectors = DEFAULT_GCM_TEST_VECTORS; + if (Security.getProvider("AndroidOpenSSL") != null) { + gcmTestVectors = OPENSSL_PROVIDER_GCM_TEST_VECTORS; + } else if (Security.getProvider("SunJCE") != null) { + gcmTestVectors = SUNJCE_PROVIDER_GCM_TEST_VECTORS; + } + return gcmTestVectors; + } + + public void testVectors() throws Exception { + GcmTestVector[] gcmTestVectors = getTestVectors(); + for (GcmTestVector test : gcmTestVectors) { + Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); + cipher.init(Cipher.ENCRYPT_MODE, test.key, test.parameters); + cipher.updateAAD(test.aad); + byte[] ct = cipher.doFinal(test.pt); + assertEquals(test.ctHex, TestUtil.bytesToHex(ct)); + } + } + + /** + * Typically one should always call updateAAD before any call to update. This test checks what + * happens if the order is reversed. The test expects that a correct implementation either + * computes the tag correctly or throws an exception. + * + * <p>For example, OpenJdk did compute incorrect tags in this case. The bug has been fixed in + * http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/rev/89c06ca1e6cc + * + * <p>For example BouncyCastle computes correct tags if the calls are reversed, SunJCE and OpenJdk + * now throw exceptions. + */ + public void testLateUpdateAAD() throws Exception { + GcmTestVector[] gcmTestVectors = getTestVectors(); + for (GcmTestVector test : gcmTestVectors) { + Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); + cipher.init(Cipher.ENCRYPT_MODE, test.key, test.parameters); + byte[] c0 = cipher.update(test.pt); + try { + cipher.updateAAD(test.aad); + } catch (java.lang.IllegalStateException ex) { + // Throwing an exception is valid behaviour. + continue; + } + byte[] c1 = cipher.doFinal(); + String result = TestUtil.bytesToHex(c0) + TestUtil.bytesToHex(c1); + assertEquals(test.ctHex, result); + } + } + + /** Encryption with ByteBuffers. */ + public void testByteBuffer() throws Exception { + GcmTestVector[] gcmTestVectors = getTestVectors(); + for (GcmTestVector test : gcmTestVectors) { + // Encryption + Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); + ByteBuffer ptBuffer = ByteBuffer.wrap(test.pt); + cipher.init(Cipher.ENCRYPT_MODE, test.key, test.parameters); + int outputSize = cipher.getOutputSize(test.pt.length); + ByteBuffer ctBuffer = ByteBuffer.allocate(outputSize); + cipher.updateAAD(test.aad); + cipher.doFinal(ptBuffer, ctBuffer); + assertEquals(test.ctHex, TestUtil.byteBufferToHex(ctBuffer)); + + // Decryption + ctBuffer.flip(); + cipher.init(Cipher.DECRYPT_MODE, test.key, test.parameters); + outputSize = cipher.getOutputSize(test.ct.length); + ByteBuffer decrypted = ByteBuffer.allocate(outputSize); + cipher.updateAAD(test.aad); + cipher.doFinal(ctBuffer, decrypted); + assertEquals(test.ptHex, TestUtil.byteBufferToHex(decrypted)); + } + } + + /** Encryption with ByteBuffers should be copy-safe. */ + public void testByteBufferAlias() throws Exception { + GcmTestVector[] gcmTestVectors = getTestVectors(); + for (GcmTestVector test : gcmTestVectors) { + // Encryption + Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); + cipher.init(Cipher.ENCRYPT_MODE, test.key, test.parameters); + int outputSize = cipher.getOutputSize(test.pt.length); + byte[] backingArray = new byte[outputSize]; + ByteBuffer ptBuffer = ByteBuffer.wrap(backingArray); + ptBuffer.put(test.pt); + ptBuffer.flip(); + ByteBuffer ctBuffer = ByteBuffer.wrap(backingArray); + cipher.updateAAD(test.aad); + cipher.doFinal(ptBuffer, ctBuffer); + assertEquals(test.ctHex, TestUtil.byteBufferToHex(ctBuffer)); + + // Decryption + ByteBuffer decrypted = ByteBuffer.wrap(backingArray); + ctBuffer.flip(); + cipher.init(Cipher.DECRYPT_MODE, test.key, test.parameters); + cipher.updateAAD(test.aad); + cipher.doFinal(ctBuffer, decrypted); + assertEquals(test.ptHex, TestUtil.byteBufferToHex(decrypted)); + } + } + + public void testReadOnlyByteBuffer() throws Exception { + GcmTestVector[] gcmTestVectors = getTestVectors(); + for (GcmTestVector test : gcmTestVectors) { + // Encryption + Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); + ByteBuffer ptBuffer = ByteBuffer.wrap(test.pt).asReadOnlyBuffer(); + cipher.init(Cipher.ENCRYPT_MODE, test.key, test.parameters); + int outputSize = cipher.getOutputSize(test.pt.length); + ByteBuffer ctBuffer = ByteBuffer.allocate(outputSize); + cipher.updateAAD(test.aad); + cipher.doFinal(ptBuffer, ctBuffer); + assertEquals(test.ctHex, TestUtil.byteBufferToHex(ctBuffer)); + + // Decryption + ctBuffer.flip(); + ctBuffer = ctBuffer.asReadOnlyBuffer(); + cipher.init(Cipher.DECRYPT_MODE, test.key, test.parameters); + outputSize = cipher.getOutputSize(test.ct.length); + ByteBuffer decrypted = ByteBuffer.allocate(outputSize); + cipher.updateAAD(test.aad); + cipher.doFinal(ctBuffer, decrypted); + assertEquals(test.ptHex, TestUtil.byteBufferToHex(decrypted)); + } + } + + /** + * If a ByteBuffer is backed by an array and not readonly, then it is possible to access the data + * through the .array() method. An implementation using this possiblity must ensure that it + * considers the offset. + */ + public void testByteBufferWithOffset() throws Exception { + GcmTestVector[] gcmTestVectors = getTestVectors(); + for (GcmTestVector test : gcmTestVectors) { + // Encryption + Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); + ByteBuffer ptBuffer = ByteBuffer.wrap(new byte[test.pt.length + 50]); + ptBuffer.position(5); + ptBuffer = ptBuffer.slice(); + ptBuffer.put(test.pt); + ptBuffer.flip(); + + ByteBuffer ctBuffer = ByteBuffer.wrap(new byte[test.ct.length + 50]); + ctBuffer.position(8); + ctBuffer = ctBuffer.slice(); + cipher.init(Cipher.ENCRYPT_MODE, test.key, test.parameters); + cipher.updateAAD(test.aad); + cipher.doFinal(ptBuffer, ctBuffer); + assertEquals(test.ctHex, TestUtil.byteBufferToHex(ctBuffer)); + ctBuffer.flip(); + + // Decryption + ByteBuffer decBuffer = ByteBuffer.wrap(new byte[test.pt.length + 50]); + decBuffer.position(6); + decBuffer = decBuffer.slice(); + cipher.init(Cipher.DECRYPT_MODE, test.key, test.parameters); + cipher.updateAAD(test.aad); + cipher.doFinal(ctBuffer, decBuffer); + assertEquals(test.ptHex, TestUtil.byteBufferToHex(decBuffer)); + } + } + + public void testByteBufferTooShort() throws Exception { + GcmTestVector[] gcmTestVectors = getTestVectors(); + for (GcmTestVector test : gcmTestVectors) { + // Encryption + Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); + ByteBuffer ptBuffer = ByteBuffer.wrap(test.pt); + ByteBuffer ctBuffer = ByteBuffer.allocate(test.ct.length - 1); + cipher.init(Cipher.ENCRYPT_MODE, test.key, test.parameters); + cipher.updateAAD(test.aad); + try { + cipher.doFinal(ptBuffer, ctBuffer); + fail("This should not work"); + } catch (ShortBufferException ex) { + // expected + } + + // Decryption + ctBuffer = ByteBuffer.wrap(test.ct); + ByteBuffer decrypted = ByteBuffer.allocate(test.pt.length - 1); + cipher.init(Cipher.DECRYPT_MODE, test.key, test.parameters); + cipher.updateAAD(test.aad); + try { + cipher.doFinal(ctBuffer, decrypted); + fail("This should not work"); + } catch (ShortBufferException ex) { + // expected + } + } + } + + /** + * The default authentication tag size should be 128-bit by default for the following reasons: + * <br> + * (1) Security: Ferguson, N., Authentication Weaknesses in GCM, Natl. Inst. Stand. Technol. [Web + * page], http://www.csrc.nist.gov/groups/ST/toolkit/BCM/documents/comments/ + * CWC-GCM/Ferguson2.pdf, May 20, 2005. This paper points out that a n-bit tag has lower strength + * than expected. <br> + * (2) Compatibility: Assume an implementer tests some code using one provider than switches to + * another provider. Such a switch should ideally not lower the security. <br> + * Conscrypt used to have only 12-byte authentication tag (b/26186727). + */ + public void testDefaultTagSizeIvParameterSpec() throws Exception { + byte[] counter = new byte[12]; + byte[] input = new byte[16]; + byte[] key = new byte[16]; + Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); + try { + cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"), new IvParameterSpec(counter)); + } catch (InvalidAlgorithmParameterException ex) { + // OpenJDK8 does not support IvParameterSpec for GCM. + System.out.println(ex); + return; + } + byte[] output = cipher.doFinal(input); + assertEquals(input.length + 16, output.length); + } + + /** + * The default authentication tag size should be 128-bit by default for the following reasons: + * <br> + * (1) Security: Ferguson, N., Authentication Weaknesses in GCM, Natl. Inst. Stand. Technol. [Web + * page], http://www.csrc.nist.gov/groups/ST/toolkit/BCM/documents/comments/ + * CWC-GCM/Ferguson2.pdf, May 20, 2005. This paper points out that a n-bit tag has lower strength + * than expected. <br> + * (2) Compatibility: Assume an implementer tests some code using one provider than switches to + * another provider. Such a switch should ideally not lower the security. <br> + * BouncyCastle used to have only 12-byte authentication tag (b/26186727). + */ + public void testDefaultTagSizeAlgorithmParameterGenerator() throws Exception { + byte[] input = new byte[10]; + byte[] key = new byte[16]; + Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); + try { + AlgorithmParameterGenerator.getInstance("GCM"); + } catch (NoSuchAlgorithmException ex) { + // Conscrypt does not support AlgorithmParameterGenerator for GCM. + System.out.println(ex); + return; + } + AlgorithmParameters param = AlgorithmParameterGenerator.getInstance("GCM").generateParameters(); + cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"), param); + byte[] output = cipher.doFinal(input); + assertEquals(input.length + 16, output.length); + } +} |