@@ -56,11 +56,17 @@ class CryptoUtil {
5656 private static final String RSA_TRANSFORMATION = "RSA/ECB/OAEPWithSHA-256AndMGF1Padding" ;
5757 /**
5858 * !!! WARNING !!!
59- * "RSA/ECB/PKCS1Padding" is deprecated due to vulnerabilities (see Bleichenbacher attacks, etc),
60- * and should only be used here for *legacy key migration only*. All new data must use OAEP padding.
61- * REMOVE SUPPORT FOR THIS AS SOON AS ALL DATA IS MIGRATED.
59+ * "RSA/ECB/PKCS1Padding" is cryptographically deprecated due to vulnerabilities
60+ * (e.g. Bleichenbacher padding oracle attacks) and MUST NOT be used for encrypting
61+ * new data or for any general-purpose RSA operations.
62+ *
63+ * This transformation exists solely to DECRYPT pre-existing legacy data that was
64+ * originally encrypted with PKCS#1 v1.5 padding, so that it can be re-encrypted
65+ * using the secure OAEP-based {@link #RSA_TRANSFORMATION}. Once all legacy data has
66+ * been migrated, support for this constant and any code paths that use it should be
67+ * removed.
6268 */
63- private static final String OLD_PKCS1_RSA_TRANSFORMATION = "RSA/ECB/PKCS1Padding" ;
69+ private static final String LEGACY_PKCS1_RSA_TRANSFORMATION = "RSA/ECB/PKCS1Padding" ;
6470 // https://developer.android.com/reference/javax/crypto/Cipher.html
6571 @ SuppressWarnings ("SpellCheckingInspection" )
6672 private static final String AES_TRANSFORMATION = "AES/GCM/NOPADDING" ;
@@ -98,6 +104,31 @@ public CryptoUtil(@NonNull Context context, @NonNull Storage storage, @NonNull S
98104 this .storage = storage ;
99105 }
100106
107+ /**
108+ * Decrypts data that was encrypted using legacy RSA/PKCS1 padding.
109+ * <p>
110+ * WARNING: This must only be used for decrypting legacy data during migration.
111+ * New code must always use OAEP padding for RSA encryption/decryption.
112+ *
113+ * @param encryptedData The data encrypted with PKCS1 padding
114+ * @param privateKey The private key for decryption
115+ * @return The decrypted data
116+ * @throws NoSuchPaddingException If PKCS1 padding is not available
117+ * @throws NoSuchAlgorithmException If RSA algorithm is not available
118+ * @throws InvalidKeyException If the private key is invalid
119+ * @throws BadPaddingException If the encrypted data has invalid padding
120+ * @throws IllegalBlockSizeException If the encrypted data size is invalid
121+ */
122+ @ NonNull
123+ private static byte [] RSADecryptLegacyPKCS1 (@ NonNull byte [] encryptedData ,
124+ @ NonNull PrivateKey privateKey )
125+ throws NoSuchPaddingException , NoSuchAlgorithmException , InvalidKeyException ,
126+ BadPaddingException , IllegalBlockSizeException {
127+ Cipher rsaPkcs1Cipher = Cipher .getInstance (LEGACY_PKCS1_RSA_TRANSFORMATION );
128+ rsaPkcs1Cipher .init (Cipher .DECRYPT_MODE , privateKey );
129+ return rsaPkcs1Cipher .doFinal (encryptedData );
130+ }
131+
101132 /**
102133 * Attempts to recover the existing RSA Private Key entry or generates a new one as secure as
103134 * this device and Android version allows it if none is found.
@@ -410,11 +441,12 @@ byte[] getAESKey() throws IncompatibleDeviceException, CryptoException {
410441 }
411442
412443 if (rsaKey != null && keyAliasUsed != null ) {
413- // WARNING: Using PKCS1 padding here is intentional and ONLY for decrypting legacy data
414- // Do NOT use PKCS1 padding for encryption in new code; always use OAEP padding instead.
415- Cipher rsaPkcs1Cipher = Cipher .getInstance (OLD_PKCS1_RSA_TRANSFORMATION );
416- rsaPkcs1Cipher .init (Cipher .DECRYPT_MODE , rsaKey .getPrivateKey ());
417- byte [] decryptedAESKey = rsaPkcs1Cipher .doFinal (encryptedAESBytes );
444+ // WARNING: Using PKCS1 padding here is intentional and ONLY for decrypting legacy data.
445+ // This cipher must NEVER be used for encryption or for any new data; always use OAEP instead.
446+ byte [] decryptedAESKey = RSADecryptLegacyPKCS1 (
447+ encryptedAESBytes ,
448+ rsaKey .getPrivateKey ()
449+ );
418450 deleteRSAKeys ();
419451
420452 // Re-encrypt AES key with NEW OAEP RSA key (4096-bit)
@@ -435,8 +467,9 @@ byte[] getAESKey() throws IncompatibleDeviceException, CryptoException {
435467 deleteRSAKeys ();
436468 deleteAESKeys ();
437469 }
470+ } else {
471+ throw e ;
438472 }
439- throw e ;
440473 } catch (CryptoException e ) {
441474 // RSA decryption failed - the encrypted AES key is corrupted or the RSA key is invalid
442475 // Delete keys and regenerate them
@@ -450,11 +483,12 @@ byte[] getAESKey() throws IncompatibleDeviceException, CryptoException {
450483 try {
451484 byte [] encryptedOldAESBytes = Base64 .decode (encodedOldAES , Base64 .DEFAULT );
452485 KeyStore .PrivateKeyEntry rsaKeyEntry = getRSAKeyEntry ();
453- // WARNING: Using PKCS1 padding here is intentional and ONLY for decrypting legacy data
454- // Do NOT use PKCS1 padding for encryption in new code; always use OAEP padding instead.
455- Cipher rsaPkcs1Cipher = Cipher .getInstance (OLD_PKCS1_RSA_TRANSFORMATION );
456- rsaPkcs1Cipher .init (Cipher .DECRYPT_MODE , rsaKeyEntry .getPrivateKey ());
457- byte [] decryptedAESKey = rsaPkcs1Cipher .doFinal (encryptedOldAESBytes );
486+ // WARNING: Using PKCS1 padding here is intentional and ONLY for decrypting legacy data.
487+ // This cipher must NEVER be used for encryption or for any new data; always use OAEP padding instead.
488+ byte [] decryptedAESKey = RSADecryptLegacyPKCS1 (
489+ encryptedOldAESBytes ,
490+ rsaKeyEntry .getPrivateKey ()
491+ );
458492
459493 byte [] encryptedAESWithOAEP = RSAEncrypt (decryptedAESKey );
460494 String newEncodedEncryptedAES = new String (Base64 .encode (encryptedAESWithOAEP , Base64 .DEFAULT ), StandardCharsets .UTF_8 );
0 commit comments