• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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