Skip to content

RSA encryption padding change from PKCS1Padding to OAEPWithSHA-256And… #834

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -53,16 +53,17 @@

// Transformations available since API 18
// https://developer.android.com/training/articles/keystore.html#SupportedCiphers
private static final String RSA_TRANSFORMATION = "RSA/ECB/PKCS1Padding";
private static final String RSA_TRANSFORMATION = "RSA/ECB/OAEPWithSHA-256AndMGF1Padding";
// https://developer.android.com/reference/javax/crypto/Cipher.html
@SuppressWarnings("SpellCheckingInspection")
private static final String AES_TRANSFORMATION = "AES/GCM/NOPADDING";
private static final String OLD_PKCS1_RSA_TRANSFORMATION = "RSA/ECB/PKCS1Padding";

Check failure

Code scanning / CodeQL

Use of RSA algorithm without OAEP High

This specification is used to
initialize an RSA cipher
without OAEP padding.

Copilot Autofix

AI 9 days ago

To fix the problem, update the migration code to use OAEP padding for RSA decryption, if possible. This means replacing the use of "RSA/ECB/PKCS1Padding" with "RSA/ECB/OAEPWithSHA-256AndMGF1Padding" (or another OAEP variant compatible with the legacy data). However, if the legacy data was encrypted with PKCS#1 padding, it cannot be decrypted with OAEP; in that case, the only safe option is to ensure the migration code is not exposed to untrusted input and is removed after migration. If you must keep the migration code, add comments to clarify its purpose and restrict its use. If you can re-encrypt legacy data with OAEP, do so and remove PKCS#1 padding support.

Best fix:

  • Restrict the use of PKCS#1 padding to migration only, and add a clear comment explaining its legacy purpose.
  • Ensure the migration code is not exposed to untrusted input.
  • If possible, remove the migration code after all legacy data has been migrated.
  • If you must keep the migration code, consider logging a warning and/or restricting its invocation.

Required changes:

  • Add a comment above the definition and use of OLD_PKCS1_RSA_TRANSFORMATION explaining its legacy/migration-only purpose.
  • Optionally, add a runtime check or log to ensure the migration code is not used for new data.
  • No new imports or methods are required.

Suggested changeset 1
auth0/src/main/java/com/auth0/android/authentication/storage/CryptoUtil.java

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/auth0/src/main/java/com/auth0/android/authentication/storage/CryptoUtil.java b/auth0/src/main/java/com/auth0/android/authentication/storage/CryptoUtil.java
--- a/auth0/src/main/java/com/auth0/android/authentication/storage/CryptoUtil.java
+++ b/auth0/src/main/java/com/auth0/android/authentication/storage/CryptoUtil.java
@@ -57,6 +57,8 @@
     // https://developer.android.com/reference/javax/crypto/Cipher.html
     @SuppressWarnings("SpellCheckingInspection")
     private static final String AES_TRANSFORMATION = "AES/GCM/NOPADDING";
+    // Legacy: Used only for migration of old AES keys encrypted with PKCS#1 padding.
+    // Do NOT use for new encryption operations. Remove after migration is complete.
     private static final String OLD_PKCS1_RSA_TRANSFORMATION = "RSA/ECB/PKCS1Padding";
 
     private static final String ANDROID_KEY_STORE = "AndroidKeyStore";
@@ -375,6 +377,8 @@
             try {
                 byte[] encryptedOldAESBytes = Base64.decode(encodedOldAES, Base64.DEFAULT);
                 KeyStore.PrivateKeyEntry rsaKeyEntry = getRSAKeyEntry();
+                // Legacy migration: Decrypt old AES key encrypted with PKCS#1 padding.
+                // This block should only be used for migration and not for new data.
                 Cipher rsaPkcs1Cipher = Cipher.getInstance(OLD_PKCS1_RSA_TRANSFORMATION);
                 rsaPkcs1Cipher.init(Cipher.DECRYPT_MODE, rsaKeyEntry.getPrivateKey());
                 byte[] decryptedAESKey = rsaPkcs1Cipher.doFinal(encryptedOldAESBytes);
EOF
@@ -57,6 +57,8 @@
// https://developer.android.com/reference/javax/crypto/Cipher.html
@SuppressWarnings("SpellCheckingInspection")
private static final String AES_TRANSFORMATION = "AES/GCM/NOPADDING";
// Legacy: Used only for migration of old AES keys encrypted with PKCS#1 padding.
// Do NOT use for new encryption operations. Remove after migration is complete.
private static final String OLD_PKCS1_RSA_TRANSFORMATION = "RSA/ECB/PKCS1Padding";

private static final String ANDROID_KEY_STORE = "AndroidKeyStore";
@@ -375,6 +377,8 @@
try {
byte[] encryptedOldAESBytes = Base64.decode(encodedOldAES, Base64.DEFAULT);
KeyStore.PrivateKeyEntry rsaKeyEntry = getRSAKeyEntry();
// Legacy migration: Decrypt old AES key encrypted with PKCS#1 padding.
// This block should only be used for migration and not for new data.
Cipher rsaPkcs1Cipher = Cipher.getInstance(OLD_PKCS1_RSA_TRANSFORMATION);
rsaPkcs1Cipher.init(Cipher.DECRYPT_MODE, rsaKeyEntry.getPrivateKey());
byte[] decryptedAESKey = rsaPkcs1Cipher.doFinal(encryptedOldAESBytes);
Copilot is powered by AI and may make mistakes. Always verify output.
Unable to commit as this autofix suggestion is now outdated
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that was the PR which we are fixing to use OAEP padding.

Copy link
Contributor Author

@utkrishtsahu utkrishtsahu Jul 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this usage is intentional and strictly limited to a one-time data migration path for existing users.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets keep a backlog ticket to remove this once all users are migrated to the 3.10.0 or later

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a comment to state why OLD_PKCS1_RSA_TRANSFORMATION is being used


private static final String ANDROID_KEY_STORE = "AndroidKeyStore";
private static final String ALGORITHM_RSA = "RSA";
private static final String ALGORITHM_AES = "AES";
private static final int AES_KEY_SIZE = 256;
private static final int RSA_KEY_SIZE = 2048;
private static final int RSA_KEY_SIZE = 4096;
Copy link
Preview

Copilot AI Aug 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Increasing RSA key size from 2048 to 4096 bits will significantly impact performance for key generation and encryption/decryption operations. Consider whether this change is necessary for security requirements or if 2048 bits is sufficient for the application's threat model.

Suggested change
private static final int RSA_KEY_SIZE = 4096;
private static final int RSA_KEY_SIZE = 2048;

Copilot uses AI. Check for mistakes.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to increase the size ? Was this suggested by the security team ?


private final String OLD_KEY_ALIAS;
private final String OLD_KEY_IV_ALIAS;
Expand Down Expand Up @@ -124,7 +125,8 @@
.setCertificateNotBefore(start.getTime())
.setCertificateNotAfter(end.getTime())
.setKeySize(RSA_KEY_SIZE)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_OAEP)
.setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA1)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hope this setDigest addition was also reviewed by security ?

.setBlockModes(KeyProperties.BLOCK_MODE_ECB)
.build();
} else {
Expand Down Expand Up @@ -355,6 +357,7 @@

/**
* Attempts to recover the existing AES Key or generates a new one if none is found.
* Handles migration from PKCS1Padding-encrypted AES keys to OAEP-encrypted ones.
*
* @return a valid AES Key bytes
* @throws IncompatibleDeviceException in the event the device can't understand the cryptographic settings required
Expand All @@ -363,41 +366,45 @@
@VisibleForTesting
byte[] getAESKey() throws IncompatibleDeviceException, CryptoException {
String encodedEncryptedAES = storage.retrieveString(KEY_ALIAS);
if (TextUtils.isEmpty(encodedEncryptedAES)) {
encodedEncryptedAES = storage.retrieveString(OLD_KEY_ALIAS);
if (!TextUtils.isEmpty(encodedEncryptedAES)) {
byte[] encryptedAESBytes = Base64.decode(encodedEncryptedAES, Base64.DEFAULT);
return RSADecrypt(encryptedAESBytes);
}
if (encodedEncryptedAES != null) {
//Return existing key
byte[] encryptedAES = Base64.decode(encodedEncryptedAES, Base64.DEFAULT);
byte[] existingAES = RSADecrypt(encryptedAES);
final int aesExpectedLengthInBytes = AES_KEY_SIZE / 8;
//Prevent returning an 'Empty key' (invalid/corrupted) that was mistakenly saved
if (existingAES != null && existingAES.length == aesExpectedLengthInBytes) {
//Key exists and has the right size
return existingAES;
String encodedOldAES = storage.retrieveString(OLD_KEY_ALIAS);
if (!TextUtils.isEmpty(encodedOldAES)) {
try {
byte[] encryptedOldAESBytes = Base64.decode(encodedOldAES, Base64.DEFAULT);
KeyStore.PrivateKeyEntry rsaKeyEntry = getRSAKeyEntry();
Cipher rsaPkcs1Cipher = Cipher.getInstance(OLD_PKCS1_RSA_TRANSFORMATION);
rsaPkcs1Cipher.init(Cipher.DECRYPT_MODE, rsaKeyEntry.getPrivateKey());
byte[] decryptedAESKey = rsaPkcs1Cipher.doFinal(encryptedOldAESBytes);

byte[] encryptedAESWithOAEP = RSAEncrypt(decryptedAESKey);
String newEncodedEncryptedAES = new String(Base64.encode(encryptedAESWithOAEP, Base64.DEFAULT), StandardCharsets.UTF_8);
storage.store(KEY_ALIAS, newEncodedEncryptedAES);
storage.remove(OLD_KEY_ALIAS);
return decryptedAESKey;
} catch (Exception e) {
Copy link
Preview

Copilot AI Aug 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Catching generic Exception is too broad and may hide specific error conditions. Consider catching specific exceptions like BadPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, etc., to handle different failure scenarios appropriately.

Suggested change
} catch (Exception e) {
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | BadPaddingException | IllegalBlockSizeException | KeyStoreException | UnrecoverableEntryException e) {

Copilot uses AI. Check for mistakes.

Log.e(TAG, "Could not migrate the legacy AES key. A new key will be generated.", e);
deleteAESKeys();
}
}
//Key doesn't exist. Generate new AES

try {
KeyGenerator keyGen = KeyGenerator.getInstance(ALGORITHM_AES);
keyGen.init(AES_KEY_SIZE);
byte[] aes = keyGen.generateKey().getEncoded();
//Save encrypted encoded version
byte[] encryptedAES = RSAEncrypt(aes);
String encodedEncryptedAESText = new String(Base64.encode(encryptedAES, Base64.DEFAULT), StandardCharsets.UTF_8);
storage.store(KEY_ALIAS, encodedEncryptedAESText);
return aes;
byte[] decryptedAESKey = keyGen.generateKey().getEncoded();

byte[] encryptedNewAES = RSAEncrypt(decryptedAESKey);
String encodedEncryptedNewAESText = new String(Base64.encode(encryptedNewAES, Base64.DEFAULT), StandardCharsets.UTF_8);
storage.store(KEY_ALIAS, encodedEncryptedNewAESText);
return decryptedAESKey;
} catch (NoSuchAlgorithmException e) {
/*
* This exceptions are safe to be ignored:
*
* - NoSuchAlgorithmException:
* Thrown if the Algorithm implementation is not available. AES was introduced in API 1
*
* Read more in https://developer.android.com/reference/javax/crypto/KeyGenerator
*/
Log.e(TAG, "Error while creating the AES key.", e);
Log.e(TAG, "Error while creating the new AES key.", e);
throw new IncompatibleDeviceException(e);
} catch (Exception e) {
Copy link
Preview

Copilot AI Aug 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Catching generic Exception is too broad. This catch block should handle specific exceptions that can occur during RSA encryption or storage operations, such as CryptoException or IncompatibleDeviceException.

Suggested change
} catch (Exception e) {
} catch (InvalidKeyException
| NoSuchPaddingException
| IllegalBlockSizeException
| BadPaddingException
| KeyStoreException
| UnrecoverableEntryException
| CertificateException
| IOException
| ProviderException e) {

Copilot uses AI. Check for mistakes.

Log.e(TAG, "Unexpected error while creating the new AES key.", e);
throw new CryptoException("Unexpected error while creating the new AES key.", e);
}
}

Expand Down
Loading