aboutsummaryrefslogtreecommitdiff
path: root/java/com/google/security/wycheproof/testcases/RsaEncryptionTest.java
blob: a15d45df8a0a8362e735f757787fba3844c147e0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
/**
 * @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;
  }
}