aboutsummaryrefslogtreecommitdiff
path: root/java/com/google/security/wycheproof/testcases/EciesTest.java
diff options
context:
space:
mode:
Diffstat (limited to 'java/com/google/security/wycheproof/testcases/EciesTest.java')
-rw-r--r--java/com/google/security/wycheproof/testcases/EciesTest.java335
1 files changed, 335 insertions, 0 deletions
diff --git a/java/com/google/security/wycheproof/testcases/EciesTest.java b/java/com/google/security/wycheproof/testcases/EciesTest.java
new file mode 100644
index 0000000..bd29bf8
--- /dev/null
+++ b/java/com/google/security/wycheproof/testcases/EciesTest.java
@@ -0,0 +1,335 @@
+/**
+ * @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.GeneralSecurityException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.interfaces.ECPrivateKey;
+import java.security.interfaces.ECPublicKey;
+import java.security.spec.ECGenParameterSpec;
+import java.util.Arrays;
+import java.util.HashSet;
+import javax.crypto.Cipher;
+import junit.framework.TestCase;
+
+/**
+ * Testing ECIES.
+ *
+ * @author bleichen@google.com (Daniel Bleichenbacher)
+ */
+// Tested providers:
+// BouncyCastle v 1.52: IESCipher is amazingly buggy, both from a crypto
+// viewpoint and from an engineering viewpoint. It uses encryption modes that are completely
+// inapproriate for ECIES or DHIES (i.e. ECB), the CBC implementation distinguishes between
+// padding and MAC failures allowing adaptive chosen-ciphertext attacks. The implementation
+// allows to specify paddings, but ignores them, encryption using ByteBuffers doesn't even work
+// without exceptions, indicating that this hasn't even tested.
+//
+// <p>TODO(bleichen):
+// - compressed points,
+// - maybe again CipherInputStream, CipherOutputStream,
+// - BouncyCastle has a KeyPairGenerator for ECIES. Is this one different from EC?
+public class EciesTest extends TestCase {
+
+ int expectedCiphertextLength(String algorithm, int coordinateSize, int messageLength)
+ throws Exception {
+ switch (algorithm.toUpperCase()) {
+ case "ECIESWITHAES-CBC":
+ // Uses the encoding
+ // 0x04 || coordinate x || coordinate y || PKCS5 padded ciphertext || 20-byte HMAC-digest.
+ return 1 + (2 * coordinateSize) + (messageLength - messageLength % 16 + 16) + 20;
+ default:
+ fail("Not implemented");
+ }
+ return -1;
+ }
+
+ /**
+ * Check that key agreement using ECIES works. This example does not specify an IESParametersSpec.
+ * BouncyCastle v.1.52 uses the following algorithms: KDF2 with SHA1 for the key derivation
+ * AES-CBC with PKCS #5 padding. HMAC-SHA1 with a 20 byte digest. The AES and the HMAC key are
+ * both 128 bits.
+ */
+ @SuppressWarnings("InsecureCryptoUsage")
+ public void testEciesBasic() throws Exception {
+ ECGenParameterSpec ecSpec = new ECGenParameterSpec("secp256r1");
+ KeyPairGenerator kf = KeyPairGenerator.getInstance("EC");
+ kf.initialize(ecSpec);
+ KeyPair keyPair = kf.generateKeyPair();
+ PrivateKey priv = keyPair.getPrivate();
+ PublicKey pub = keyPair.getPublic();
+ byte[] message = "Hello".getBytes("UTF-8");
+ Cipher ecies = Cipher.getInstance("ECIESwithAES-CBC");
+ ecies.init(Cipher.ENCRYPT_MODE, pub);
+ byte[] ciphertext = ecies.doFinal(message);
+ System.out.println("testEciesBasic:" + TestUtil.bytesToHex(ciphertext));
+ ecies.init(Cipher.DECRYPT_MODE, priv, ecies.getParameters());
+ byte[] decrypted = ecies.doFinal(ciphertext);
+ assertEquals(TestUtil.bytesToHex(message), TestUtil.bytesToHex(decrypted));
+ }
+
+ /**
+ * ECIES does not allow encryption modes and paddings. If this test fails then we should add
+ * additional tests covering the new algorithms.
+ */
+ // TODO(bleichen): This test describes BouncyCastles behaviour, but not necessarily what we
+ // expect.
+ @SuppressWarnings("InsecureCryptoUsage")
+ public void testInvalidNames() throws Exception {
+ String[] invalidNames =
+ new String[] {
+ "ECIESWITHAES/CBC/PKCS5PADDING",
+ "ECIESWITHAES/CBC/PKCS7PADDING",
+ "ECIESWITHAES/DHAES/NOPADDING",
+ "ECIESWITHDESEDE/DHAES/NOPADDING",
+ "ECIESWITHAES/ECB/NOPADDING",
+ "ECIESWITHAES/CTR/NOPADDING",
+ };
+ for (String algorithm : invalidNames) {
+ try {
+ Cipher.getInstance(algorithm);
+ fail("unexpected algorithm:" + algorithm);
+ } catch (NoSuchAlgorithmException ex) {
+ // this is expected
+ }
+ }
+ }
+
+ /** Here are a few names that BouncyCastle accepts. */
+ // TODO(bleichen): This test describes BouncyCastles behaviour, but not necessarily what we
+ // expect.
+ @SuppressWarnings("InsecureCryptoUsage")
+ public void testValidNames() throws Exception {
+ String[] validNames =
+ new String[] {
+ "ECIES/DHAES/PKCS7PADDING",
+ "ECIESWITHAES-CBC/NONE/NOPADDING",
+ };
+ for (String algorithm : validNames) {
+ Cipher.getInstance(algorithm);
+ }
+ }
+
+ /**
+ * BouncyCastle has a key generation algorithm "ECIES". This test checks that the result are
+ * ECKeys in both cases.
+ */
+ public void testKeyGeneration() throws Exception {
+ ECGenParameterSpec ecSpec = new ECGenParameterSpec("secp256r1");
+ KeyPairGenerator kf = KeyPairGenerator.getInstance("ECIES");
+ kf.initialize(ecSpec);
+ KeyPair keyPair = kf.generateKeyPair();
+ ECPrivateKey priv = (ECPrivateKey) keyPair.getPrivate();
+ ECPublicKey pub = (ECPublicKey) keyPair.getPublic();
+ }
+
+ /**
+ * Tries to decrypt ciphertexts where the symmetric part has been randomized.
+ * If this randomization leads to distinguishable exceptions then this may indicate that the
+ * implementation is vulnerable to a padding attack.
+ *
+ * Problems detected:
+ * <ul>
+ * <li> CVE-2016-1000345 BouncyCastle before v.1.56 is vulnerable to a padding oracle attack.
+ * </ul>
+ */
+ @SuppressWarnings("InsecureCryptoUsage")
+ public void testExceptions(String algorithm) throws Exception {
+ Cipher ecies;
+ try {
+ ecies = Cipher.getInstance(algorithm);
+ } catch (NoSuchAlgorithmException ex) {
+ // Allowing to skip the algorithm
+ System.out.println("No implementation for:" + algorithm);
+ return;
+ }
+ ECGenParameterSpec ecSpec = new ECGenParameterSpec("secp256r1");
+ final int kemSize = 65;
+ KeyPairGenerator kf = KeyPairGenerator.getInstance("EC");
+ kf.initialize(ecSpec);
+ KeyPair keyPair = kf.generateKeyPair();
+ PrivateKey priv = keyPair.getPrivate();
+ PublicKey pub = keyPair.getPublic();
+ byte[] message = new byte[40];
+ ecies.init(Cipher.ENCRYPT_MODE, pub);
+ byte[] ciphertext = ecies.doFinal(message);
+ System.out.println(TestUtil.bytesToHex(ciphertext));
+ ecies.init(Cipher.DECRYPT_MODE, priv, ecies.getParameters());
+ HashSet<String> exceptions = new HashSet<String>();
+ for (int byteNr = kemSize; byteNr < ciphertext.length; byteNr++) {
+ for (int bit = 0; bit < 8; bit++) {
+ byte[] corrupt = Arrays.copyOf(ciphertext, ciphertext.length);
+ corrupt[byteNr] ^= (byte) (1 << bit);
+ ecies.init(Cipher.DECRYPT_MODE, keyPair.getPrivate());
+ try {
+ ecies.doFinal(corrupt);
+ fail("Decrypted:" + TestUtil.bytesToHex(corrupt));
+ } catch (Exception ex) {
+ String exception = ex.toString();
+ if (exceptions.add(exception)) {
+ System.out.println(algorithm + ":" + exception);
+ }
+ }
+ }
+ }
+ assertEquals(1, exceptions.size());
+ }
+
+ public void testEciesCorruptDefault() throws Exception {
+ testExceptions("ECIES");
+ }
+
+ @SuppressWarnings("InsecureCryptoUsage")
+ public void testModifyPoint() throws Exception {
+ ECGenParameterSpec ecSpec = new ECGenParameterSpec("secp256r1");
+ KeyPairGenerator kf = KeyPairGenerator.getInstance("EC");
+ kf.initialize(ecSpec);
+ KeyPair keyPair = kf.generateKeyPair();
+ PrivateKey priv = keyPair.getPrivate();
+ PublicKey pub = keyPair.getPublic();
+ byte[] message = "This is a long text since we need 32 bytes.".getBytes("UTF-8");
+ Cipher ecies = Cipher.getInstance("ECIESwithAES-CBC");
+ ecies.init(Cipher.ENCRYPT_MODE, pub);
+ byte[] ciphertext = ecies.doFinal(message);
+ ciphertext[2] ^= (byte) 1;
+ ecies.init(Cipher.DECRYPT_MODE, priv, ecies.getParameters());
+ try {
+ ecies.doFinal(ciphertext);
+ fail("This should not work");
+ } catch (GeneralSecurityException ex) {
+ // This is as expected
+ // Bouncy Castle 1.56 throws this exception
+ } catch (Exception ex) {
+ fail("Expected subclass of java.security.GeneralSecurityException, but got: "
+ + ex.getClass().getName());
+ }
+ }
+
+ /**
+ * This test tries to detect ECIES implementations using ECB. This is insecure and also violates
+ * the claims of ECIES, since ECIES is secure agains adaptive chosen-ciphertext attacks.
+ */
+ @SuppressWarnings("InsecureCryptoUsage")
+ public void testNotEcb(String algorithm) throws Exception {
+ Cipher ecies;
+ try {
+ ecies = Cipher.getInstance(algorithm);
+ } catch (NoSuchAlgorithmException ex) {
+ // This test is called with short algorithm names such as just "ECIES".
+ // Requiring full names is typically a good practice. Hence it is OK
+ // to not assigning default algorithms.
+ System.out.println("No implementation for:" + algorithm);
+ return;
+ }
+ ECGenParameterSpec ecSpec = new ECGenParameterSpec("secp256r1");
+ KeyPairGenerator kf = KeyPairGenerator.getInstance("EC");
+ kf.initialize(ecSpec);
+ KeyPair keyPair = kf.generateKeyPair();
+ PublicKey pub = keyPair.getPublic();
+ byte[] message = new byte[512];
+ ecies.init(Cipher.ENCRYPT_MODE, pub);
+ byte[] ciphertext = ecies.doFinal(message);
+ String block1 = TestUtil.bytesToHex(Arrays.copyOfRange(ciphertext, 241, 257));
+ String block2 = TestUtil.bytesToHex(Arrays.copyOfRange(ciphertext, 257, 273));
+ assertTrue("Ciphertext repeats:" + TestUtil.bytesToHex(ciphertext), !block1.equals(block2));
+ }
+
+ public void testDefaultEcies() throws Exception {
+ testNotEcb("ECIES");
+ }
+
+ /**
+ * Tests whether algorithmA is an alias of algorithmB by encrypting with algorithmA and decrypting
+ * with algorithmB.
+ */
+ @SuppressWarnings("InsecureCryptoUsage")
+ public void testIsAlias(String algorithmA, String algorithmB) throws Exception {
+ Cipher eciesA;
+ Cipher eciesB;
+ // Allowing tests to be skipped, because we don't want to encourage abbreviations.
+ try {
+ eciesA = Cipher.getInstance(algorithmA);
+ } catch (NoSuchAlgorithmException ex) {
+ System.out.println("Skipping because of:" + ex.toString());
+ return;
+ }
+ try {
+ eciesB = Cipher.getInstance(algorithmB);
+ } catch (NoSuchAlgorithmException ex) {
+ System.out.println("Skipping because of:" + ex.toString());
+ return;
+ }
+ ECGenParameterSpec ecSpec = new ECGenParameterSpec("secp256r1");
+ KeyPairGenerator kf = KeyPairGenerator.getInstance("EC");
+ kf.initialize(ecSpec);
+ KeyPair keyPair = kf.generateKeyPair();
+ byte[] message = "Hello".getBytes("UTF-8");
+ eciesA.init(Cipher.ENCRYPT_MODE, keyPair.getPublic());
+ byte[] ciphertext = eciesA.doFinal(message);
+ eciesB.init(Cipher.DECRYPT_MODE, keyPair.getPrivate(), eciesB.getParameters());
+ byte[] decrypted = eciesB.doFinal(ciphertext);
+ assertEquals(TestUtil.bytesToHex(message), TestUtil.bytesToHex(decrypted));
+ }
+
+ /** Tests whether two distinct algorithm names implement the same cipher */
+ public void testAlias() throws Exception {
+ testIsAlias("ECIESWITHAES-CBC", "ECIESWithAES-CBC");
+ testIsAlias("ECIESWITHAES", "ECIESWithAES");
+ // BouncyCastle v 1.52 ignores mode and padding and considers the following
+ // names as equivalent:
+ // testIsAlias("ECIES/DHAES/PKCS7PADDING", "ECIES");
+ testIsAlias("ECIESWITHAES-CBC/NONE/PKCS7PADDING", "ECIESWITHAES-CBC/NONE/NOPADDING");
+ }
+
+ /**
+ * Cipher.doFinal(ByteBuffer, ByteBuffer) should be copy-safe according to
+ * https://docs.oracle.com/javase/7/docs/api/javax/crypto/Cipher.html
+ *
+ * <p>This test tries to verify this.
+ */
+ /* TODO(bleichen): There's no point to run this test as long as the previous basic test fails.
+ public void testByteBufferAlias() throws Exception {
+ byte[] message = "Hello".getBytes("UTF-8");
+ String algorithm = "ECIESWithAES-CBC";
+ ECGenParameterSpec ecSpec = new ECGenParameterSpec("secp256r1");
+ KeyPairGenerator kf = KeyPairGenerator.getInstance("EC");
+ kf.initialize(ecSpec);
+ KeyPair keyPair = kf.generateKeyPair();
+ Cipher ecies = Cipher.getInstance(algorithm);
+
+ int ciphertextLength = expectedCiphertextLength(algorithm, 32, message.length);
+ byte[] backingArray = new byte[ciphertextLength];
+ ByteBuffer ptBuffer = ByteBuffer.wrap(backingArray);
+ ptBuffer.put(message);
+ ptBuffer.flip();
+
+ ecies.init(Cipher.ENCRYPT_MODE, keyPair.getPublic());
+ ByteBuffer ctBuffer = ByteBuffer.wrap(backingArray);
+ ecies.doFinal(ptBuffer, ctBuffer);
+ ctBuffer.flip();
+
+ ecies.init(Cipher.DECRYPT_MODE, keyPair.getPrivate());
+ byte[] decrypted = ecies.doFinal(backingArray, 0, ctBuffer.remaining());
+ assertEquals(TestUtil.bytesToHex(message), TestUtil.bytesToHex(decrypted));
+ }
+ */
+}