aboutsummaryrefslogtreecommitdiff
path: root/java/com/google/security/wycheproof/testcases/AesGcmTest.java
diff options
context:
space:
mode:
Diffstat (limited to 'java/com/google/security/wycheproof/testcases/AesGcmTest.java')
-rw-r--r--java/com/google/security/wycheproof/testcases/AesGcmTest.java405
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);
+ }
+}