/** * @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; } }