9130b7625f
* Fix ErrorProne build errors
332 lines
13 KiB
Java
332 lines
13 KiB
Java
/**
|
|
* @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.
|
|
//
|
|
// <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);
|
|
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/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[] 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();
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
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);
|
|
try {
|
|
ecies.doFinal(ciphertext);
|
|
fail("This should not work");
|
|
} catch (java.lang.IllegalArgumentException ex) {
|
|
// This is what BouncyCastle throws when the points are not on the curve.
|
|
// Maybe GeneralSecurityException would be better.
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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());
|
|
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));
|
|
}
|
|
*/
|
|
}
|