/** * @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.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. // //
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("InsecureCipherMode")
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);
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("InsecureCipherMode")
public void testInvalidNames() throws Exception {
String[] invalidNames =
new String[] {
"ECIESWITHAES/CBC/PKCS5PADDING",
"ECIESWITHAES/CBC/PKCS7PADDING",
"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("InsecureCipherMode")
public void testValidNames() throws Exception {
String[] invalidNames =
new String[] {
"ECIESWITHAES/DHAES/NOPADDING",
"ECIES/DHAES/PKCS7PADDING",
"ECIESWITHDESEDE/DHAES/NOPADDING",
"ECIESWITHAES-CBC/NONE/NOPADDING",
};
for (String algorithm : invalidNames) {
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();
}
/**
* Check the length of the ciphertext. TODO(bleichen): This is more an explanation what is going
* on than a test. Maybe remove this later.
*/
@SuppressWarnings("InsecureCipherMode")
public void testCiphertextLength() throws Exception {
String algorithm = "ECIESwithAES-CBC";
final int messageLength = 40;
final int coordinateSize = 32;
byte[] message = new byte[messageLength];
ECGenParameterSpec ecSpec = new ECGenParameterSpec("secp256r1");
KeyPairGenerator kf = KeyPairGenerator.getInstance("EC");
kf.initialize(ecSpec);
KeyPair keyPair = kf.generateKeyPair();
PublicKey pub = keyPair.getPublic();
Cipher ecies = Cipher.getInstance(algorithm);
ecies.init(Cipher.ENCRYPT_MODE, pub);
byte[] ciphertext = ecies.doFinal(message);
assertEquals(
expectedCiphertextLength(algorithm, coordinateSize, messageLength), ciphertext.length);
}
// Tries to decrypt ciphertexts where the symmetric part has been
// randomized. Distinguishable exceptions mean that a padding attack
// may be possible.
@SuppressWarnings("InsecureCipherMode")
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);
HashSet This test tries to verify this.
*/
/* TODO(bleichen): There's no point to run this test as long as not even 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));
}
*/
}