1 /** 2 * @license 3 * Copyright 2016 Google Inc. All rights reserved. 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.security.wycheproof; 18 19 import java.nio.ByteBuffer; 20 import java.security.GeneralSecurityException; 21 import java.security.KeyPair; 22 import java.security.KeyPairGenerator; 23 import java.security.NoSuchAlgorithmException; 24 import java.security.PrivateKey; 25 import java.security.PublicKey; 26 import java.security.interfaces.ECPrivateKey; 27 import java.security.interfaces.ECPublicKey; 28 import java.security.spec.ECGenParameterSpec; 29 import java.util.Arrays; 30 import java.util.HashSet; 31 import javax.crypto.Cipher; 32 import junit.framework.TestCase; 33 34 /** 35 * Testing ECIES. 36 * 37 * @author bleichen@google.com (Daniel Bleichenbacher) 38 */ 39 // Tested providers: 40 // BouncyCastle v 1.52: IESCipher is amazingly buggy, both from a crypto 41 // viewpoint and from an engineering viewpoint. It uses encryption modes that are completely 42 // inapproriate for ECIES or DHIES (i.e. ECB), the CBC implementation distinguishes between 43 // padding and MAC failures allowing adaptive chosen-ciphertext attacks. The implementation 44 // allows to specify paddings, but ignores them, encryption using ByteBuffers doesn't even work 45 // without exceptions, indicating that this hasn't even tested. 46 // 47 // <p>TODO(bleichen): 48 // - compressed points, 49 // - maybe again CipherInputStream, CipherOutputStream, 50 // - BouncyCastle has a KeyPairGenerator for ECIES. Is this one different from EC? 51 public class EciesTest extends TestCase { 52 expectedCiphertextLength(String algorithm, int coordinateSize, int messageLength)53 int expectedCiphertextLength(String algorithm, int coordinateSize, int messageLength) 54 throws Exception { 55 switch (algorithm.toUpperCase()) { 56 case "ECIESWITHAES-CBC": 57 // Uses the encoding 58 // 0x04 || coordinate x || coordinate y || PKCS5 padded ciphertext || 20-byte HMAC-digest. 59 return 1 + (2 * coordinateSize) + (messageLength - messageLength % 16 + 16) + 20; 60 default: 61 fail("Not implemented"); 62 } 63 return -1; 64 } 65 66 /** 67 * Check that key agreement using ECIES works. This example does not specify an IESParametersSpec. 68 * BouncyCastle v.1.52 uses the following algorithms: KDF2 with SHA1 for the key derivation 69 * AES-CBC with PKCS #5 padding. HMAC-SHA1 with a 20 byte digest. The AES and the HMAC key are 70 * both 128 bits. 71 */ 72 @SuppressWarnings("InsecureCryptoUsage") testEciesBasic()73 public void testEciesBasic() throws Exception { 74 ECGenParameterSpec ecSpec = new ECGenParameterSpec("secp256r1"); 75 KeyPairGenerator kf = KeyPairGenerator.getInstance("EC"); 76 kf.initialize(ecSpec); 77 KeyPair keyPair = kf.generateKeyPair(); 78 PrivateKey priv = keyPair.getPrivate(); 79 PublicKey pub = keyPair.getPublic(); 80 byte[] message = "Hello".getBytes("UTF-8"); 81 Cipher ecies = Cipher.getInstance("ECIESwithAES-CBC"); 82 ecies.init(Cipher.ENCRYPT_MODE, pub); 83 byte[] ciphertext = ecies.doFinal(message); 84 System.out.println("testEciesBasic:" + TestUtil.bytesToHex(ciphertext)); 85 ecies.init(Cipher.DECRYPT_MODE, priv, ecies.getParameters()); 86 byte[] decrypted = ecies.doFinal(ciphertext); 87 assertEquals(TestUtil.bytesToHex(message), TestUtil.bytesToHex(decrypted)); 88 } 89 90 /** 91 * ECIES does not allow encryption modes and paddings. If this test fails then we should add 92 * additional tests covering the new algorithms. 93 */ 94 // TODO(bleichen): This test describes BouncyCastles behaviour, but not necessarily what we 95 // expect. 96 @SuppressWarnings("InsecureCryptoUsage") testInvalidNames()97 public void testInvalidNames() throws Exception { 98 String[] invalidNames = 99 new String[] { 100 "ECIESWITHAES/CBC/PKCS5PADDING", 101 "ECIESWITHAES/CBC/PKCS7PADDING", 102 "ECIESWITHAES/DHAES/NOPADDING", 103 "ECIESWITHDESEDE/DHAES/NOPADDING", 104 "ECIESWITHAES/ECB/NOPADDING", 105 "ECIESWITHAES/CTR/NOPADDING", 106 }; 107 for (String algorithm : invalidNames) { 108 try { 109 Cipher.getInstance(algorithm); 110 fail("unexpected algorithm:" + algorithm); 111 } catch (NoSuchAlgorithmException ex) { 112 // this is expected 113 } 114 } 115 } 116 117 /** Here are a few names that BouncyCastle accepts. */ 118 // TODO(bleichen): This test describes BouncyCastles behaviour, but not necessarily what we 119 // expect. 120 @SuppressWarnings("InsecureCryptoUsage") testValidNames()121 public void testValidNames() throws Exception { 122 String[] validNames = 123 new String[] { 124 "ECIES/DHAES/PKCS7PADDING", 125 "ECIESWITHAES-CBC/NONE/NOPADDING", 126 }; 127 for (String algorithm : validNames) { 128 Cipher.getInstance(algorithm); 129 } 130 } 131 132 /** 133 * BouncyCastle has a key generation algorithm "ECIES". This test checks that the result are 134 * ECKeys in both cases. 135 */ testKeyGeneration()136 public void testKeyGeneration() throws Exception { 137 ECGenParameterSpec ecSpec = new ECGenParameterSpec("secp256r1"); 138 KeyPairGenerator kf = KeyPairGenerator.getInstance("ECIES"); 139 kf.initialize(ecSpec); 140 KeyPair keyPair = kf.generateKeyPair(); 141 ECPrivateKey priv = (ECPrivateKey) keyPair.getPrivate(); 142 ECPublicKey pub = (ECPublicKey) keyPair.getPublic(); 143 } 144 145 /** 146 * Tries to decrypt ciphertexts where the symmetric part has been randomized. 147 * If this randomization leads to distinguishable exceptions then this may indicate that the 148 * implementation is vulnerable to a padding attack. 149 * 150 * Problems detected: 151 * <ul> 152 * <li> CVE-2016-1000345 BouncyCastle before v.1.56 is vulnerable to a padding oracle attack. 153 * </ul> 154 */ 155 @SuppressWarnings("InsecureCryptoUsage") testExceptions(String algorithm)156 public void testExceptions(String algorithm) throws Exception { 157 Cipher ecies; 158 try { 159 ecies = Cipher.getInstance(algorithm); 160 } catch (NoSuchAlgorithmException ex) { 161 // Allowing to skip the algorithm 162 System.out.println("No implementation for:" + algorithm); 163 return; 164 } 165 ECGenParameterSpec ecSpec = new ECGenParameterSpec("secp256r1"); 166 final int kemSize = 65; 167 KeyPairGenerator kf = KeyPairGenerator.getInstance("EC"); 168 kf.initialize(ecSpec); 169 KeyPair keyPair = kf.generateKeyPair(); 170 PrivateKey priv = keyPair.getPrivate(); 171 PublicKey pub = keyPair.getPublic(); 172 byte[] message = new byte[40]; 173 ecies.init(Cipher.ENCRYPT_MODE, pub); 174 byte[] ciphertext = ecies.doFinal(message); 175 System.out.println(TestUtil.bytesToHex(ciphertext)); 176 ecies.init(Cipher.DECRYPT_MODE, priv, ecies.getParameters()); 177 HashSet<String> exceptions = new HashSet<String>(); 178 for (int byteNr = kemSize; byteNr < ciphertext.length; byteNr++) { 179 for (int bit = 0; bit < 8; bit++) { 180 byte[] corrupt = Arrays.copyOf(ciphertext, ciphertext.length); 181 corrupt[byteNr] ^= (byte) (1 << bit); 182 ecies.init(Cipher.DECRYPT_MODE, keyPair.getPrivate()); 183 try { 184 ecies.doFinal(corrupt); 185 fail("Decrypted:" + TestUtil.bytesToHex(corrupt)); 186 } catch (Exception ex) { 187 String exception = ex.toString(); 188 if (exceptions.add(exception)) { 189 System.out.println(algorithm + ":" + exception); 190 } 191 } 192 } 193 } 194 assertEquals(1, exceptions.size()); 195 } 196 testEciesCorruptDefault()197 public void testEciesCorruptDefault() throws Exception { 198 testExceptions("ECIES"); 199 } 200 201 @SuppressWarnings("InsecureCryptoUsage") testModifyPoint()202 public void testModifyPoint() throws Exception { 203 ECGenParameterSpec ecSpec = new ECGenParameterSpec("secp256r1"); 204 KeyPairGenerator kf = KeyPairGenerator.getInstance("EC"); 205 kf.initialize(ecSpec); 206 KeyPair keyPair = kf.generateKeyPair(); 207 PrivateKey priv = keyPair.getPrivate(); 208 PublicKey pub = keyPair.getPublic(); 209 byte[] message = "This is a long text since we need 32 bytes.".getBytes("UTF-8"); 210 Cipher ecies = Cipher.getInstance("ECIESwithAES-CBC"); 211 ecies.init(Cipher.ENCRYPT_MODE, pub); 212 byte[] ciphertext = ecies.doFinal(message); 213 ciphertext[2] ^= (byte) 1; 214 ecies.init(Cipher.DECRYPT_MODE, priv, ecies.getParameters()); 215 try { 216 ecies.doFinal(ciphertext); 217 fail("This should not work"); 218 } catch (GeneralSecurityException ex) { 219 // This is as expected 220 // Bouncy Castle 1.56 throws this exception 221 } catch (Exception ex) { 222 fail("Expected subclass of java.security.GeneralSecurityException, but got: " 223 + ex.getClass().getName()); 224 } 225 } 226 227 /** 228 * This test tries to detect ECIES implementations using ECB. This is insecure and also violates 229 * the claims of ECIES, since ECIES is secure agains adaptive chosen-ciphertext attacks. 230 */ 231 @SuppressWarnings("InsecureCryptoUsage") testNotEcb(String algorithm)232 public void testNotEcb(String algorithm) throws Exception { 233 Cipher ecies; 234 try { 235 ecies = Cipher.getInstance(algorithm); 236 } catch (NoSuchAlgorithmException ex) { 237 // This test is called with short algorithm names such as just "ECIES". 238 // Requiring full names is typically a good practice. Hence it is OK 239 // to not assigning default algorithms. 240 System.out.println("No implementation for:" + algorithm); 241 return; 242 } 243 ECGenParameterSpec ecSpec = new ECGenParameterSpec("secp256r1"); 244 KeyPairGenerator kf = KeyPairGenerator.getInstance("EC"); 245 kf.initialize(ecSpec); 246 KeyPair keyPair = kf.generateKeyPair(); 247 PublicKey pub = keyPair.getPublic(); 248 byte[] message = new byte[512]; 249 ecies.init(Cipher.ENCRYPT_MODE, pub); 250 byte[] ciphertext = ecies.doFinal(message); 251 String block1 = TestUtil.bytesToHex(Arrays.copyOfRange(ciphertext, 241, 257)); 252 String block2 = TestUtil.bytesToHex(Arrays.copyOfRange(ciphertext, 257, 273)); 253 assertTrue("Ciphertext repeats:" + TestUtil.bytesToHex(ciphertext), !block1.equals(block2)); 254 } 255 testDefaultEcies()256 public void testDefaultEcies() throws Exception { 257 testNotEcb("ECIES"); 258 } 259 260 /** 261 * Tests whether algorithmA is an alias of algorithmB by encrypting with algorithmA and decrypting 262 * with algorithmB. 263 */ 264 @SuppressWarnings("InsecureCryptoUsage") testIsAlias(String algorithmA, String algorithmB)265 public void testIsAlias(String algorithmA, String algorithmB) throws Exception { 266 Cipher eciesA; 267 Cipher eciesB; 268 // Allowing tests to be skipped, because we don't want to encourage abbreviations. 269 try { 270 eciesA = Cipher.getInstance(algorithmA); 271 } catch (NoSuchAlgorithmException ex) { 272 System.out.println("Skipping because of:" + ex.toString()); 273 return; 274 } 275 try { 276 eciesB = Cipher.getInstance(algorithmB); 277 } catch (NoSuchAlgorithmException ex) { 278 System.out.println("Skipping because of:" + ex.toString()); 279 return; 280 } 281 ECGenParameterSpec ecSpec = new ECGenParameterSpec("secp256r1"); 282 KeyPairGenerator kf = KeyPairGenerator.getInstance("EC"); 283 kf.initialize(ecSpec); 284 KeyPair keyPair = kf.generateKeyPair(); 285 byte[] message = "Hello".getBytes("UTF-8"); 286 eciesA.init(Cipher.ENCRYPT_MODE, keyPair.getPublic()); 287 byte[] ciphertext = eciesA.doFinal(message); 288 eciesB.init(Cipher.DECRYPT_MODE, keyPair.getPrivate(), eciesB.getParameters()); 289 byte[] decrypted = eciesB.doFinal(ciphertext); 290 assertEquals(TestUtil.bytesToHex(message), TestUtil.bytesToHex(decrypted)); 291 } 292 293 /** Tests whether two distinct algorithm names implement the same cipher */ testAlias()294 public void testAlias() throws Exception { 295 testIsAlias("ECIESWITHAES-CBC", "ECIESWithAES-CBC"); 296 testIsAlias("ECIESWITHAES", "ECIESWithAES"); 297 // BouncyCastle v 1.52 ignores mode and padding and considers the following 298 // names as equivalent: 299 // testIsAlias("ECIES/DHAES/PKCS7PADDING", "ECIES"); 300 testIsAlias("ECIESWITHAES-CBC/NONE/PKCS7PADDING", "ECIESWITHAES-CBC/NONE/NOPADDING"); 301 } 302 303 /** 304 * Cipher.doFinal(ByteBuffer, ByteBuffer) should be copy-safe according to 305 * https://docs.oracle.com/javase/7/docs/api/javax/crypto/Cipher.html 306 * 307 * <p>This test tries to verify this. 308 */ 309 /* TODO(bleichen): There's no point to run this test as long as the previous basic test fails. 310 public void testByteBufferAlias() throws Exception { 311 byte[] message = "Hello".getBytes("UTF-8"); 312 String algorithm = "ECIESWithAES-CBC"; 313 ECGenParameterSpec ecSpec = new ECGenParameterSpec("secp256r1"); 314 KeyPairGenerator kf = KeyPairGenerator.getInstance("EC"); 315 kf.initialize(ecSpec); 316 KeyPair keyPair = kf.generateKeyPair(); 317 Cipher ecies = Cipher.getInstance(algorithm); 318 319 int ciphertextLength = expectedCiphertextLength(algorithm, 32, message.length); 320 byte[] backingArray = new byte[ciphertextLength]; 321 ByteBuffer ptBuffer = ByteBuffer.wrap(backingArray); 322 ptBuffer.put(message); 323 ptBuffer.flip(); 324 325 ecies.init(Cipher.ENCRYPT_MODE, keyPair.getPublic()); 326 ByteBuffer ctBuffer = ByteBuffer.wrap(backingArray); 327 ecies.doFinal(ptBuffer, ctBuffer); 328 ctBuffer.flip(); 329 330 ecies.init(Cipher.DECRYPT_MODE, keyPair.getPrivate()); 331 byte[] decrypted = ecies.doFinal(backingArray, 0, ctBuffer.remaining()); 332 assertEquals(TestUtil.bytesToHex(message), TestUtil.bytesToHex(decrypted)); 333 } 334 */ 335 } 336