| /** |
| * @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.security.KeyPair; |
| import java.security.KeyPairGenerator; |
| import java.security.NoSuchAlgorithmException; |
| import java.security.SecureRandom; |
| import java.util.Arrays; |
| import java.util.HashSet; |
| import javax.crypto.Cipher; |
| import javax.crypto.NoSuchPaddingException; |
| import junit.framework.TestCase; |
| |
| /** |
| * RSA encryption tests |
| * |
| * @author bleichen@google.com (Daniel Bleichenbacher) |
| */ |
| // TODO(bleichen): test vectors check special cases: |
| // - ciphertext too long |
| // - plaintext too long |
| // - ciphertext 0 |
| // - ciphertext == modulus timing attacks |
| public class RsaEncryptionTest extends TestCase { |
| |
| /** |
| * Providers that implement RSA with PKCS1Padding but not OAEP are outdated and should be avoided |
| * even if RSA is currently not used in a project. Such providers promote using an insecure |
| * cipher. There is a great danger that PKCS1Padding is used as a temporary workaround, but later |
| * stays in the project for much longer than necessary. |
| */ |
| public void testOutdatedProvider() throws Exception { |
| try { |
| Cipher c = Cipher.getInstance("RSA/ECB/PKCS1Padding"); |
| try { |
| Cipher.getInstance("RSA/ECB/OAEPWITHSHA-1ANDMGF1PADDING"); |
| } catch (NoSuchPaddingException | NoSuchAlgorithmException ex) { |
| fail("Provider " + c.getProvider().getName() + " is outdated and should not be used."); |
| } |
| } catch (NoSuchPaddingException | NoSuchAlgorithmException ex) { |
| System.out.println("RSA/ECB/PKCS1Padding is not implemented"); |
| } |
| } |
| |
| /** |
| * Tries decrypting random messages with a given algorithm. Counts the number of distinct error |
| * messages and expects this number to be 1. |
| * |
| * <p><b>References:</b> |
| * |
| * <ul> |
| * <li>Bleichenbacher, "Chosen ciphertext attacks against protocols based on the RSA encryption |
| * standard PKCS# 1" Crypto 98 |
| * <li>Manger, "A chosen ciphertext attack on RSA optimal asymmetric encryption padding (OAEP) |
| * as standardized in PKCS# 1 v2.0", Crypto 2001 This paper shows that OAEP is susceptible |
| * to a chosen ciphertext attack if error messages distinguish between different failure |
| * condidtions. |
| * <li>Bardou, Focardi, Kawamoto, Simionato, Steel, Tsay "Efficient Padding Oracle Attacks on |
| * Cryptographic Hardware", Crypto 2012 The paper shows that small differences on what |
| * information an attacker recieves can make a big difference on the number of chosen |
| * message necessary for an attack. |
| * <li>Smart, "Errors matter: Breaking RSA-based PIN encryption with thirty ciphertext validity |
| * queries" RSA conference, 2010 This paper shows that padding oracle attacks can be |
| * successful with even a small number of queries. |
| * </ul> |
| * |
| * <p><b>Some recent bugs:</b> CVE-2012-5081: Java JSSE provider leaked information through |
| * exceptions and timing. Both the PKCS #1 padding and the OAEP padding were broken: |
| * http://www-brs.ub.ruhr-uni-bochum.de/netahtml/HSS/Diss/MeyerChristopher/diss.pdf |
| * |
| * <p><b>What this test does not (yet) cover:</b> |
| * |
| * <ul> |
| * <li> A previous version of one of the provider leaked the block type. (when was this fixed?) |
| * <li> Some attacks require a large number of ciphertexts to be detected if random ciphertexts |
| * are used. Such problems require specifically crafted ciphertexts to run in a unit test. |
| * E.g. "Attacking RSA-based Sessions in SSL/TLS" by V. Klima, O. Pokorny, and T. Rosa: |
| * https://eprint.iacr.org/2003/052/ |
| * <li> Timing leakages because of differences in parsing the padding (e.g. CVE-2015-7827) Such |
| * differences are too small to be reliably detectable in unit tests. |
| * </ul> |
| */ |
| @SuppressWarnings("InsecureCryptoUsage") |
| public void testExceptions(String algorithm) throws Exception { |
| KeyPairGenerator keygen = KeyPairGenerator.getInstance("RSA"); |
| keygen.initialize(1024); |
| KeyPair keypair = keygen.genKeyPair(); |
| SecureRandom rand = new SecureRandom(); |
| Cipher c = Cipher.getInstance(algorithm); |
| byte[] ciphertext = new byte[1024 / 8]; |
| HashSet<String> exceptions = new HashSet<String>(); |
| final int samples = 1000; |
| for (int i = 0; i < samples; i++) { |
| rand.nextBytes(ciphertext); |
| ciphertext[0] &= (byte) 0x7f; |
| try { |
| c.init(Cipher.DECRYPT_MODE, keypair.getPrivate()); |
| c.doFinal(ciphertext); |
| } catch (Exception ex) { |
| exceptions.add(ex.toString()); |
| } |
| } |
| Cipher enc = Cipher.getInstance("RSA/ECB/NOPADDING"); |
| enc.init(Cipher.ENCRYPT_MODE, keypair.getPublic()); |
| c.init(Cipher.DECRYPT_MODE, keypair.getPrivate()); |
| byte[][] paddedKeys = generatePkcs1Vectors(1024 / 8); |
| for (int i = 0; i < paddedKeys.length; i++) { |
| ciphertext = enc.doFinal(paddedKeys[i]); |
| try { |
| c.doFinal(ciphertext); |
| } catch (Exception ex) { |
| exceptions.add(ex.toString()); |
| } |
| } |
| if (exceptions.size() > 1) { |
| System.out.println("Exceptions for " + algorithm); |
| for (String s : exceptions) { |
| System.out.println(s); |
| } |
| fail("Exceptions leak information about the padding for " + algorithm); |
| } |
| } |
| |
| /** |
| * Tests the exceptions for RSA decryption with PKCS1Padding. PKCS1Padding is susceptible to |
| * chosen message attacks. Nonetheless, to minimize the damage of such an attack an implementation |
| * should minimize the information about the failure in the padding. |
| */ |
| public void testExceptionsPKCS1() throws Exception { |
| testExceptions("RSA/ECB/PKCS1PADDING"); |
| } |
| |
| public void testGetExceptionsOAEP() throws Exception { |
| testExceptions("RSA/ECB/OAEPWITHSHA-1ANDMGF1PADDING"); |
| } |
| |
| /** |
| * Generates PKCS#1 invalid vectors |
| * @param rsaKeyLength |
| * @return |
| */ |
| private byte[][] generatePkcs1Vectors(int rsaKeyLength) { |
| // create plain padded keys |
| byte[][] plainPaddedKeys = new byte[13][]; |
| // no 0x00 byte to deliver a symmetric key |
| plainPaddedKeys[0] = getEK_NoNullByte(rsaKeyLength); |
| // 0x00 too early in the padding |
| plainPaddedKeys[1] = getEK_NullByteInPadding(rsaKeyLength); |
| // 0x00 too early in the PKCS#1 padding |
| plainPaddedKeys[2] = getEK_NullByteInPkcsPadding(rsaKeyLength); |
| // decrypted ciphertext starting with 0x17 0x02 |
| plainPaddedKeys[3] = getEK_WrongFirstByte(rsaKeyLength); |
| // decrypted ciphertext starting with 0x00 0x17 |
| plainPaddedKeys[4] = getEK_WrongSecondByte(rsaKeyLength); |
| // different lengths of the decrypted unpadded key |
| plainPaddedKeys[5] = getPaddedKey(rsaKeyLength, 0); |
| plainPaddedKeys[6] = getPaddedKey(rsaKeyLength, 1); |
| plainPaddedKeys[7] = getPaddedKey(rsaKeyLength, 8); |
| plainPaddedKeys[8] = getPaddedKey(rsaKeyLength, 16); |
| plainPaddedKeys[9] = getPaddedKey(rsaKeyLength, 96); |
| // the decrypted padded plaintext is shorter than RSA key |
| plainPaddedKeys[10] = getPaddedKey(rsaKeyLength - 1, 16); |
| plainPaddedKeys[11] = getPaddedKey(rsaKeyLength - 2, 16); |
| // just 0x00 bytes |
| plainPaddedKeys[12] = new byte[rsaKeyLength]; |
| return plainPaddedKeys; |
| } |
| |
| private byte[] getPaddedKey(int rsaKeyLength, int symmetricKeyLength) { |
| byte[] key = new byte[rsaKeyLength]; |
| // fill all the bytes with non-zero values |
| Arrays.fill(key, (byte) 42); |
| // set the first byte to 0x00 |
| key[0] = 0x00; |
| // set the second byte to 0x02 |
| key[1] = 0x02; |
| // set the separating byte |
| if(symmetricKeyLength != -1) { |
| key[rsaKeyLength - symmetricKeyLength - 1] = 0x00; |
| } |
| return key; |
| } |
| |
| private byte[] getEK_WrongFirstByte(int rsaKeyLength) { |
| byte[] key = getPaddedKey(rsaKeyLength, 16); |
| key[0] = 23; |
| return key; |
| } |
| |
| private byte[] getEK_WrongSecondByte(int rsaKeyLength) { |
| byte[] key = getPaddedKey(rsaKeyLength, 16); |
| key[1] = 23; |
| return key; |
| } |
| |
| private byte[] getEK_NoNullByte(int rsaKeyLength) { |
| byte[] key = getPaddedKey(rsaKeyLength, -1); |
| return key; |
| } |
| |
| private byte[] getEK_NullByteInPkcsPadding(int rsaKeyLength) { |
| byte[] key = getPaddedKey(rsaKeyLength, 16); |
| key[3] = 0x00; |
| return key; |
| } |
| |
| private byte[] getEK_NullByteInPadding(int rsaKeyLength) { |
| byte[] key = getPaddedKey(rsaKeyLength, 16); |
| key[11] = 0x00; |
| return key; |
| } |
| } |