summaryrefslogtreecommitdiffstats
path: root/luni
diff options
context:
space:
mode:
authorKenny Root <kroot@google.com>2013-05-13 14:47:30 -0700
committerKenny Root <kroot@google.com>2013-05-14 17:33:18 -0700
commitd416195acbc08f2b3bdd5d5532d40438465d99e9 (patch)
tree531d20377c7bb7471798b0f4e622ac882bf0d291 /luni
parente95612e19f51b0fbcc9196e7a07737388010a4ea (diff)
downloadlibcore-d416195acbc08f2b3bdd5d5532d40438465d99e9.zip
libcore-d416195acbc08f2b3bdd5d5532d40438465d99e9.tar.gz
libcore-d416195acbc08f2b3bdd5d5532d40438465d99e9.tar.bz2
Add classes for AEAD encryption
New classes in Java 7 for Authenicated Encryption with Additional Data (AEAD). This allows the use of encryption modes such as Galois/Counter Mode with performs the equivalent of MAC and encryption simultaneously and consequently makes encryption safer to use for implementors. Change-Id: I6302826b096044ade5f62a667dc240e3ab07b351
Diffstat (limited to 'luni')
-rw-r--r--luni/src/main/java/javax/crypto/AEADBadTagException.java43
-rw-r--r--luni/src/main/java/javax/crypto/Cipher.java95
-rw-r--r--luni/src/main/java/javax/crypto/CipherSpi.java63
-rw-r--r--luni/src/main/java/javax/crypto/spec/GCMParameterSpec.java97
-rw-r--r--luni/src/test/java/libcore/javax/crypto/CipherTest.java83
-rw-r--r--luni/src/test/java/org/apache/harmony/crypto/tests/javax/crypto/spec/GCMParameterSpecTest.java102
6 files changed, 482 insertions, 1 deletions
diff --git a/luni/src/main/java/javax/crypto/AEADBadTagException.java b/luni/src/main/java/javax/crypto/AEADBadTagException.java
new file mode 100644
index 0000000..d5d1b11
--- /dev/null
+++ b/luni/src/main/java/javax/crypto/AEADBadTagException.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * 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 javax.crypto;
+
+/**
+ * Thrown by a {@link Cipher} that is using an Authenticated Encryption with
+ * Additional Data (AEAD) mode such as Galois/Counter Mode (GCM) and the tag
+ * failed verification.
+ *
+ * @since 1.7
+ */
+public class AEADBadTagException extends BadPaddingException {
+ private static final long serialVersionUID = -488059093241685509L;
+
+ /**
+ * Constructs an instance of {@code AEADBadTagException}.
+ */
+ public AEADBadTagException() {
+ super();
+ }
+
+ /**
+ * Constructs an instance of {@code AEADBadTagException} with the given
+ * {@code message}.
+ */
+ public AEADBadTagException(String message) {
+ super(message);
+ }
+}
diff --git a/luni/src/main/java/javax/crypto/Cipher.java b/luni/src/main/java/javax/crypto/Cipher.java
index aeb5def..af58a22 100644
--- a/luni/src/main/java/javax/crypto/Cipher.java
+++ b/luni/src/main/java/javax/crypto/Cipher.java
@@ -875,7 +875,7 @@ public class Cipher {
* if this cipher instance is not initialized for encryption or
* decryption.
* @throws IllegalArgumentException
- * if the input is {@code null}, or if {@code inputOffset} and
+ * if {@code input} is {@code null}, or if {@code inputOffset} and
* {@code inputLen} do not specify a valid chunk in the input
* buffer.
*/
@@ -1026,6 +1026,99 @@ public class Cipher {
}
/**
+ * Continues a multi-part transformation (encryption or decryption) with
+ * Authenticated Additional Data (AAD). AAD may only be added after the
+ * {@code Cipher} is initialized and before any data is passed to the
+ * instance.
+ * <p>
+ * This is only usable with cipher modes that support Authenticated
+ * Encryption with Additional Data (AEAD) such as Galois/Counter Mode (GCM).
+ *
+ * @param input bytes of AAD to use with the cipher
+ * @throws IllegalStateException
+ * if this cipher instance is not initialized for encryption or
+ * decryption.
+ * @throws IllegalArgumentException
+ * if {@code input} is {@code null}
+ * @throws UnsupportedOperationException if the cipher does not support AEAD
+ * @since 1.7
+ */
+ public final void updateAAD(byte[] input) {
+ if (input == null) {
+ throw new IllegalArgumentException("input == null");
+ }
+ if (mode != ENCRYPT_MODE && mode != DECRYPT_MODE) {
+ throw new IllegalStateException();
+ }
+ if (input.length == 0) {
+ return;
+ }
+ spiImpl.engineUpdateAAD(input, 0, input.length);
+ }
+
+ /**
+ * Continues a multi-part transformation (encryption or decryption) with
+ * Authenticated Additional Data (AAD). AAD may only be added after the
+ * {@code Cipher} is initialized and before any data is passed to the
+ * instance.
+ * <p>
+ * This is only usable with cipher modes that support Authenticated
+ * Encryption with Additional Data (AEAD) such as Galois/Counter Mode (GCM).
+ *
+ * @param input bytes of AAD to use with the cipher
+ * @param inputOffset offset within bytes of additional data to add to cipher
+ * @param inputLen length of bytes of additional data to add to cipher
+ * @throws IllegalStateException
+ * if this cipher instance is not initialized for encryption or
+ * decryption.
+ * @throws IllegalArgumentException
+ * if {@code input} is {@code null}, or if {@code inputOffset} and
+ * {@code inputLen} do not specify a valid chunk in the input
+ * buffer.
+ * @throws UnsupportedOperationException if the cipher does not support AEAD
+ * @since 1.7
+ */
+ public final void updateAAD(byte[] input, int inputOffset, int inputLen) {
+ if (input == null) {
+ throw new IllegalArgumentException("input == null");
+ }
+ if (mode != ENCRYPT_MODE && mode != DECRYPT_MODE) {
+ throw new IllegalStateException();
+ }
+ checkInputOffsetAndCount(input.length, inputOffset, inputLen);
+ if (input.length == 0) {
+ return;
+ }
+ spiImpl.engineUpdateAAD(input, inputOffset, inputLen);
+ }
+
+ /**
+ * Continues a multi-part transformation (encryption or decryption) with
+ * Authenticated Additional Data (AAD). AAD may only be added after the
+ * {@code Cipher} is initialized and before any data is passed to the
+ * instance.
+ * <p>
+ * This is only usable with cipher modes that support Authenticated
+ * Encryption with Additional Data (AEAD) such as Galois/Counter Mode (GCM).
+ *
+ * @param input buffer of AAD to be used
+ * @throws IllegalStateException
+ * if this cipher instance is not initialized for encryption or
+ * decryption.
+ * @throws UnsupportedOperationException if the cipher does not support AEAD
+ * @since 1.7
+ */
+ public final void updateAAD(ByteBuffer input) {
+ if (mode != ENCRYPT_MODE && mode != DECRYPT_MODE) {
+ throw new IllegalStateException("Cipher is not initialized");
+ }
+ if (input == null) {
+ throw new IllegalArgumentException("input == null");
+ }
+ spiImpl.engineUpdateAAD(input);
+ }
+
+ /**
* Finishes a multi-part transformation (encryption or decryption).
* <p>
* Processes any bytes that may have been buffered in previous {@code
diff --git a/luni/src/main/java/javax/crypto/CipherSpi.java b/luni/src/main/java/javax/crypto/CipherSpi.java
index 1f91ba8..70e06b1 100644
--- a/luni/src/main/java/javax/crypto/CipherSpi.java
+++ b/luni/src/main/java/javax/crypto/CipherSpi.java
@@ -368,6 +368,69 @@ public abstract class CipherSpi {
}
/**
+ * Continues a multi-part transformation (encryption or decryption) with
+ * Authenticated Additional Data (AAD). AAD may only be added after the
+ * {@code Cipher} is initialized and before any data is passed to the
+ * instance.
+ * <p>
+ * This is only usable with cipher modes that support Authenticated
+ * Encryption with Additional Data (AEAD) such as Galois/Counter Mode (GCM).
+ *
+ * @param input bytes of AAD to use with the cipher
+ * @param inputOffset offset within bytes of additional data to add to cipher
+ * @param inputLen length of bytes of additional data to add to cipher
+ * @throws IllegalStateException
+ * if this cipher instance is not initialized for encryption or
+ * decryption.
+ * @throws IllegalArgumentException
+ * if {@code input} is {@code null}, or if {@code inputOffset} and
+ * {@code inputLen} do not specify a valid chunk in the input
+ * buffer.
+ * @throws UnsupportedOperationException if the cipher does not support AEAD
+ * @since 1.7
+ */
+ protected void engineUpdateAAD(byte[] input, int inputOffset, int inputLen) {
+ throw new UnsupportedOperationException(
+ "This cipher does not support Authenticated Encryption with Additional Data");
+ }
+
+ /**
+ * Continues a multi-part transformation (encryption or decryption). The
+ * {@code input.remaining()} bytes starting at {@code input.position()} are
+ * used for the Additional Authenticated Data (AAD). AAD may only be added
+ * after the {@code Cipher} is initialized and before any data is passed to
+ * the instance.
+ * <p>
+ * This is only usable with cipher modes that support Authenticated
+ * Encryption with Additional Data (AEAD) such as Galois/Counter Mode (GCM).
+ *
+ * @param input the input buffer to transform.
+ * @since 1.7
+ */
+ protected void engineUpdateAAD(ByteBuffer input) {
+ if (input == null) {
+ throw new NullPointerException("input == null");
+ }
+ int position = input.position();
+ int limit = input.limit();
+ if ((limit - position) <= 0) {
+ return;
+ }
+ byte[] bInput;
+ if (input.hasArray()) {
+ bInput = input.array();
+ int offset = input.arrayOffset();
+ engineUpdateAAD(bInput, offset + position, limit - position);
+ input.position(limit);
+ } else {
+ int len = limit - position;
+ bInput = new byte[len];
+ input.get(bInput);
+ engineUpdateAAD(bInput, 0, len);
+ }
+ }
+
+ /**
* Finishes a multi-part transformation (encryption or decryption).
* <p>
* Processes the {@code inputLen} bytes in {@code input} buffer at {@code
diff --git a/luni/src/main/java/javax/crypto/spec/GCMParameterSpec.java b/luni/src/main/java/javax/crypto/spec/GCMParameterSpec.java
new file mode 100644
index 0000000..a781697
--- /dev/null
+++ b/luni/src/main/java/javax/crypto/spec/GCMParameterSpec.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * 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 javax.crypto.spec;
+
+import java.security.spec.AlgorithmParameterSpec;
+import java.util.Arrays;
+
+/**
+ * Provides a the parameters for an instance of a {@link javax.crypto.Cipher}
+ * using Galois/Counter Mode (GCM). This is an Authenticated Encryption with
+ * Associated Data (AEAD) mode for a cipher which allows you to use the
+ * {@link javax.crypto.Cipher#updateAAD(byte[])} method to provide data that is
+ * transmitted in the clear but authenticated using a cryptographic Message
+ * Authentication Code (MAC).
+ *
+ * @since 1.7
+ */
+public class GCMParameterSpec implements AlgorithmParameterSpec {
+ private final int tagLen;
+
+ private final byte[] iv;
+
+ /**
+ * Creates a new {@code GCMParameterSpec} instance from the specified
+ * Initial Vector (IV) from buffer {@code iv} and a tag length of
+ * {@code tagLen} in bits.
+ *
+ * @throws IllegalArgumentException if the specified {@code iv} is null or
+ * {@code offset} and {@code byteCount} do not specify a valid
+ * chunk in the specified buffer.
+ */
+ public GCMParameterSpec(int tagLen, byte[] iv) {
+ if (tagLen < 0) {
+ throw new IllegalArgumentException("tag should be a non-negative integer");
+ }
+ if (iv == null) {
+ throw new IllegalArgumentException("iv == null");
+ }
+ this.tagLen = tagLen;
+ this.iv = iv.clone();
+ }
+
+ /**
+ * Creates a new {@code GCMParameterSpec} instance with the Initial Vector
+ * (IV) of {@code byteCount} bytes from the specified buffer {@code iv}
+ * starting at {@code offset} and a tag length of {@code tagLen} in bits.
+ *
+ * @throws IllegalArgumentException if the specified {@code iv} is null or
+ * {@code offset} and {@code byteCount} do not specify a valid
+ * chunk in the specified buffer.
+ * @throws ArrayIndexOutOfBoundsException if {@code offset} or
+ * {@code byteCount} are negative.
+ */
+ public GCMParameterSpec(int tagLen, byte[] iv, int offset, int byteCount) {
+ if (tagLen < 0) {
+ throw new IllegalArgumentException("tag should be a non-negative integer");
+ }
+ if (iv == null) {
+ throw new IllegalArgumentException("iv == null");
+ }
+ try {
+ Arrays.checkOffsetAndCount(iv.length, offset, byteCount);
+ } catch (ArrayIndexOutOfBoundsException e) {
+ throw new IllegalArgumentException(e);
+ }
+ this.tagLen = tagLen;
+ this.iv = Arrays.copyOfRange(iv, offset, offset + byteCount);
+ }
+
+ /**
+ * Returns the size of the tag in bits.
+ */
+ public int getTLen() {
+ return tagLen;
+ }
+
+ /**
+ * Returns the Initial Vector (IV) used by this parameter spec.
+ */
+ public byte[] getIV() {
+ return iv.clone();
+ }
+}
diff --git a/luni/src/test/java/libcore/javax/crypto/CipherTest.java b/luni/src/test/java/libcore/javax/crypto/CipherTest.java
index 9f19eef..d349919 100644
--- a/luni/src/test/java/libcore/javax/crypto/CipherTest.java
+++ b/luni/src/test/java/libcore/javax/crypto/CipherTest.java
@@ -20,6 +20,7 @@ import com.android.org.bouncycastle.asn1.x509.KeyUsage;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.math.BigInteger;
+import java.nio.ByteBuffer;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
@@ -2032,6 +2033,12 @@ public final class CipherTest extends TestCase {
c.init(Cipher.DECRYPT_MODE, key, spec);
+ try {
+ c.updateAAD(new byte[8]);
+ fail("Cipher should not support AAD");
+ } catch (UnsupportedOperationException expected) {
+ }
+
byte[] emptyPlainText = c.doFinal(emptyCipherText);
assertEquals(Arrays.toString(EmptyArray.BYTE), Arrays.toString(emptyPlainText));
@@ -2125,6 +2132,82 @@ public final class CipherTest extends TestCase {
}
}
+ public void testCipher_updateAAD_BeforeInit_Failure() throws Exception {
+ Cipher c = Cipher.getInstance("AES/ECB/NoPadding");
+
+ try {
+ c.updateAAD((byte[]) null);
+ fail("should not be able to call updateAAD before Cipher is initialized");
+ } catch (IllegalArgumentException expected) {
+ }
+
+ try {
+ c.updateAAD((ByteBuffer) null);
+ fail("should not be able to call updateAAD before Cipher is initialized");
+ } catch (IllegalStateException expected) {
+ }
+
+ try {
+ c.updateAAD(new byte[8]);
+ fail("should not be able to call updateAAD before Cipher is initialized");
+ } catch (IllegalStateException expected) {
+ }
+
+ try {
+ c.updateAAD(null, 0, 8);
+ fail("should not be able to call updateAAD before Cipher is initialized");
+ } catch (IllegalStateException expected) {
+ }
+
+ ByteBuffer bb = ByteBuffer.allocate(8);
+ try {
+ c.updateAAD(bb);
+ fail("should not be able to call updateAAD before Cipher is initialized");
+ } catch (IllegalStateException expected) {
+ }
+ }
+
+ public void testCipher_updateAAD_AfterInit_Failure() throws Exception {
+ Cipher c = Cipher.getInstance("AES/ECB/NoPadding");
+ c.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(new byte[128 / 8], "AES"));
+
+ try {
+ c.updateAAD((byte[]) null);
+ fail("should not be able to call updateAAD with null input");
+ } catch (IllegalArgumentException expected) {
+ }
+
+ try {
+ c.updateAAD((ByteBuffer) null);
+ fail("should not be able to call updateAAD with null input");
+ } catch (IllegalArgumentException expected) {
+ }
+
+ try {
+ c.updateAAD(null, 0, 8);
+ fail("should not be able to call updateAAD with null input");
+ } catch (IllegalArgumentException expected) {
+ }
+
+ try {
+ c.updateAAD(new byte[8], -1, 7);
+ fail("should not be able to call updateAAD with invalid offset");
+ } catch (IllegalArgumentException expected) {
+ }
+
+ try {
+ c.updateAAD(new byte[8], 0, -1);
+ fail("should not be able to call updateAAD with negative length");
+ } catch (IllegalArgumentException expected) {
+ }
+
+ try {
+ c.updateAAD(new byte[8], 0, 8 + 1);
+ fail("should not be able to call updateAAD with too large length");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
public void testCipher_ShortBlock_Failure() throws Exception {
for (String provider : AES_PROVIDERS) {
testCipher_ShortBlock_Failure(provider);
diff --git a/luni/src/test/java/org/apache/harmony/crypto/tests/javax/crypto/spec/GCMParameterSpecTest.java b/luni/src/test/java/org/apache/harmony/crypto/tests/javax/crypto/spec/GCMParameterSpecTest.java
new file mode 100644
index 0000000..28b57fd
--- /dev/null
+++ b/luni/src/test/java/org/apache/harmony/crypto/tests/javax/crypto/spec/GCMParameterSpecTest.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * 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 org.apache.harmony.crypto.tests.javax.crypto.spec;
+
+import java.util.Arrays;
+
+import javax.crypto.spec.GCMParameterSpec;
+
+import junit.framework.TestCase;
+
+public class GCMParameterSpecTest extends TestCase {
+ private static final byte[] TEST_IV = new byte[8];
+
+ public void testConstructor_IntByteArray_Success() throws Exception {
+ new GCMParameterSpec(8, TEST_IV);
+ }
+
+ public void testConstructor_IntByteArray_NegativeTLen_Failure() throws Exception {
+ try {
+ new GCMParameterSpec(-1, TEST_IV);
+ fail("Should throw IllegalArgumentException");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ public void testConstructor_IntByteArray_NullIv_Failure() throws Exception {
+ try {
+ new GCMParameterSpec(8, null);
+ fail("Should throw IllegalArgumentException");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ public void testConstructor_IntByteArrayWithOffsets_Success() throws Exception {
+ new GCMParameterSpec(8, TEST_IV, 0, TEST_IV.length);
+ }
+
+ public void testConstructor_IntByteArrayWithOffsets_NullIv_Failure() throws Exception {
+ try {
+ new GCMParameterSpec(8, null, 0, TEST_IV.length);
+ fail("Should throw IllegalArgumentException");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ public void testConstructor_IntByteArrayWithOffsets_NegativeOffset_Failure() throws Exception {
+ try {
+ new GCMParameterSpec(8, TEST_IV, -1, TEST_IV.length);
+ fail("Should throw IllegalArgumentException");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ public void testConstructor_IntByteArrayWithOffsets_TooLongLength_Failure() throws Exception {
+ try {
+ new GCMParameterSpec(8, TEST_IV, 0, TEST_IV.length + 1);
+ fail("Should throw IllegalArgumentException");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ public void testGetIV_Success() throws Exception {
+ GCMParameterSpec spec = new GCMParameterSpec(8, TEST_IV);
+
+ byte[] actual = spec.getIV();
+ assertEquals(Arrays.toString(TEST_IV), Arrays.toString(actual));
+
+ // XOR with 0xFF so we're sure we changed the array
+ for (int i = 0; i < actual.length; i++) {
+ actual[i] ^= 0xFF;
+ }
+
+ assertFalse("Changing the IV returned shouldn't change the parameter spec",
+ Arrays.equals(spec.getIV(), actual));
+ assertEquals(Arrays.toString(TEST_IV), Arrays.toString(spec.getIV()));
+ }
+
+ public void testGetIV_Subarray_Success() throws Exception {
+ GCMParameterSpec spec = new GCMParameterSpec(8, TEST_IV, 2, 4);
+ assertEquals(Arrays.toString(Arrays.copyOfRange(TEST_IV, 2, 6)),
+ Arrays.toString(spec.getIV()));
+ }
+
+ public void testGetTLen_Success() throws Exception {
+ GCMParameterSpec spec = new GCMParameterSpec(8, TEST_IV);
+ assertEquals(8, spec.getTLen());
+ }
+}