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 3 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.
This specification is used to
initialize an RSA cipher
without OAEP padding.

Copilot Autofix

AI 10 days ago

To address the issue, the code should avoid using PKCS1 padding altogether. Instead, the fallback mechanism should use OAEP padding with a different hash algorithm (e.g., SHA-1) if the default OAEP configuration fails. This ensures that the encryption remains secure while maintaining compatibility with older devices.

Steps to fix:

  1. Replace the OLD_PKCS1_RSA_TRANSFORMATION constant with an alternative OAEP padding configuration, such as "RSA/ECB/OAEPWithSHA-1AndMGF1Padding".
  2. Update the fallback mechanism on line 388 to use the new OAEP padding configuration instead of PKCS1 padding.
  3. Ensure that the new padding configuration is compatible with the devices targeted by the fallback mechanism.

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
@@ -59,3 +59,3 @@
     private static final String AES_TRANSFORMATION = "AES/GCM/NOPADDING";
-    private static final String OLD_PKCS1_RSA_TRANSFORMATION = "RSA/ECB/PKCS1Padding";
+    private static final String OLD_PKCS1_RSA_TRANSFORMATION = "RSA/ECB/OAEPWithSHA-1AndMGF1Padding";
 
@@ -387,5 +387,5 @@
                         KeyStore.PrivateKeyEntry rsaKeyEntry = getRSAKeyEntry();
-                        Cipher rsaPkcs1Cipher = Cipher.getInstance(OLD_PKCS1_RSA_TRANSFORMATION);
-                        rsaPkcs1Cipher.init(Cipher.DECRYPT_MODE, rsaKeyEntry.getPrivateKey());
-                        decryptedAESKey = rsaPkcs1Cipher.doFinal(encryptedAESBytes);
+                        Cipher rsaFallbackCipher = Cipher.getInstance(OLD_PKCS1_RSA_TRANSFORMATION);
+                        rsaFallbackCipher.init(Cipher.DECRYPT_MODE, rsaKeyEntry.getPrivateKey());
+                        decryptedAESKey = rsaFallbackCipher.doFinal(encryptedAESBytes);
                         migrationNeeded = true;
EOF
@@ -59,3 +59,3 @@
private static final String AES_TRANSFORMATION = "AES/GCM/NOPADDING";
private static final String OLD_PKCS1_RSA_TRANSFORMATION = "RSA/ECB/PKCS1Padding";
private static final String OLD_PKCS1_RSA_TRANSFORMATION = "RSA/ECB/OAEPWithSHA-1AndMGF1Padding";

@@ -387,5 +387,5 @@
KeyStore.PrivateKeyEntry rsaKeyEntry = getRSAKeyEntry();
Cipher rsaPkcs1Cipher = Cipher.getInstance(OLD_PKCS1_RSA_TRANSFORMATION);
rsaPkcs1Cipher.init(Cipher.DECRYPT_MODE, rsaKeyEntry.getPrivateKey());
decryptedAESKey = rsaPkcs1Cipher.doFinal(encryptedAESBytes);
Cipher rsaFallbackCipher = Cipher.getInstance(OLD_PKCS1_RSA_TRANSFORMATION);
rsaFallbackCipher.init(Cipher.DECRYPT_MODE, rsaKeyEntry.getPrivateKey());
decryptedAESKey = rsaFallbackCipher.doFinal(encryptedAESBytes);
migrationNeeded = true;
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

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.
1>First, attempt to decrypt the stored AES key using the new, secure OAEP padding.
2>If that fails (specifically with a BadPaddingException or InvalidKeyException, which is expected for old keys), it then attempts a fallback decryption using the legacy PKCS1Padding.
3>If the legacy decryption succeeds, the key is immediately re-encrypted using the secure OAEP padding and re-saved to storage.


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;

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)
.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 @@ -366,39 +369,95 @@
if (TextUtils.isEmpty(encodedEncryptedAES)) {
encodedEncryptedAES = storage.retrieveString(OLD_KEY_ALIAS);
}
byte[] decryptedAESKey = null;
boolean migrationNeeded = false;
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;
byte[] encryptedAESBytes = Base64.decode(encodedEncryptedAES, Base64.DEFAULT);
try {
decryptedAESKey = RSADecrypt(encryptedAESBytes);
}catch (IncompatibleDeviceException e){
Log.w(TAG, "Failed to decrypt AES key with OAEP due to " +
"IncompatibleDeviceException. Cause: "
+ (e.getCause() != null ? e.getCause().getClass().getSimpleName() : "N/A")
+ ". Attempting PKCS1 fallback for migration.", e);
Throwable cause = e.getCause();
if (cause instanceof InvalidKeyException) {
try {
KeyStore.PrivateKeyEntry rsaKeyEntry = getRSAKeyEntry();
Cipher rsaPkcs1Cipher = Cipher.getInstance(OLD_PKCS1_RSA_TRANSFORMATION);

Choose a reason for hiding this comment

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

Semgrep identified an issue in your code:
Weak cryptographic algorithm detected. The DES, 3DES, RC2, RC4, MD4, MD5, SHA1, BLOWFISH, Dual_EC_DRBG, and SHA1PRNG algorithms are considered insecure and should not be used. Using weak cryptographic algorithms can compromise the confidentiality and integrity of sensitive data. It is recommended to adopt only cryptographic features and algorithms offered by the Android platform that are internationally recognized as strong. It is also fundamental to ensure that the encryption parameters (crypto key, IV, etc.) are generated randomly using a cryptographically strong PRNG function. In addition, if it is needed to store an encryption parameter on the device, a secure storage mechanism like the Android KeyStore must be used.

To resolve this comment:

🔧 No guidance has been designated for this issue. Fix according to your organization's approved methods.

💬 Ignore this finding

Reply with Semgrep commands to ignore this finding.

  • /fp <comment> for false positive
  • /ar <comment> for acceptable risk
  • /other <comment> for all other reasons

Alternatively, triage in Semgrep AppSec Platform to ignore the finding created by android_weak_cryptographic_algorithm.

You can view more details about this finding in the Semgrep AppSec Platform.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The primary digest used for the RSA-OAEP padding scheme is SHA-256, which is secure.

SHA1 is included only as a supported digest for the Mask Generation Function (MGF1), an internal component of OAEP.

This is a standard practice to ensure maximum compatibility across different Android OS versions and hardware security modules, as some implementations require SHA1 to be available for MGF1.

Check failure

Code scanning / CodeQL

Use of RSA algorithm without OAEP High

This specification is used to
initialize an RSA cipher
without OAEP padding.
rsaPkcs1Cipher.init(Cipher.DECRYPT_MODE, rsaKeyEntry.getPrivateKey());
decryptedAESKey = rsaPkcs1Cipher.doFinal(encryptedAESBytes);
migrationNeeded = true;
} catch (Exception pkcs1Exception) {
Log.e(TAG, "Failed to decrypt AES key with PKCS1Padding fallback.",
pkcs1Exception);
decryptedAESKey = null;
}
} else {
throw e;
}
}catch (CryptoException e) {
Log.w(TAG, "Failed to decrypt AES key with OAEP. Cause: " +
(e.getCause() != null ? e.getCause().getClass().getSimpleName() : "N/A") +
". Attempting PKCS1 fallback for migration.", e);
Throwable cause = e.getCause();
if (cause instanceof BadPaddingException || cause instanceof
IllegalBlockSizeException || cause instanceof InvalidKeyException) {
try {
KeyStore.PrivateKeyEntry rsaKeyEntry = getRSAKeyEntry();
Cipher rsaPkcs1Cipher = Cipher.getInstance(OLD_PKCS1_RSA_TRANSFORMATION);

Choose a reason for hiding this comment

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

Semgrep identified an issue in your code:
Weak cryptographic algorithm detected. The DES, 3DES, RC2, RC4, MD4, MD5, SHA1, BLOWFISH, Dual_EC_DRBG, and SHA1PRNG algorithms are considered insecure and should not be used. Using weak cryptographic algorithms can compromise the confidentiality and integrity of sensitive data. It is recommended to adopt only cryptographic features and algorithms offered by the Android platform that are internationally recognized as strong. It is also fundamental to ensure that the encryption parameters (crypto key, IV, etc.) are generated randomly using a cryptographically strong PRNG function. In addition, if it is needed to store an encryption parameter on the device, a secure storage mechanism like the Android KeyStore must be used.

To resolve this comment:

🔧 No guidance has been designated for this issue. Fix according to your organization's approved methods.

💬 Ignore this finding

Reply with Semgrep commands to ignore this finding.

  • /fp <comment> for false positive
  • /ar <comment> for acceptable risk
  • /other <comment> for all other reasons

Alternatively, triage in Semgrep AppSec Platform to ignore the finding created by android_weak_cryptographic_algorithm.

You can view more details about this finding in the Semgrep AppSec Platform.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The primary digest used for the RSA-OAEP padding scheme is SHA-256, which is secure.

SHA1 is included only as a supported digest for the Mask Generation Function (MGF1), an internal component of OAEP.

This is a standard practice to ensure maximum compatibility across different Android OS versions and hardware security modules, as some implementations require SHA1 to be available for MGF1.

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 10 days ago

To fix the issue, we need to ensure that RSA decryption always uses OAEP padding. If backward compatibility is required, we should migrate any data encrypted with PKCS1 padding to use OAEP padding securely. This involves:

  1. Replacing the use of OLD_PKCS1_RSA_TRANSFORMATION with an OAEP padding scheme.
  2. Implementing a migration mechanism to re-encrypt data with OAEP padding after successful decryption with PKCS1 padding.
  3. Ensuring that the fallback to PKCS1 is removed once the migration is complete.

The changes will be made in the getAESKey method, specifically in the fallback logic for PKCS1 decryption.


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
@@ -408,6 +408,9 @@
                         KeyStore.PrivateKeyEntry rsaKeyEntry = getRSAKeyEntry();
-                        Cipher rsaPkcs1Cipher = Cipher.getInstance(OLD_PKCS1_RSA_TRANSFORMATION);
-                        rsaPkcs1Cipher.init(Cipher.DECRYPT_MODE, rsaKeyEntry.getPrivateKey());
-                        decryptedAESKey = rsaPkcs1Cipher.doFinal(encryptedAESBytes);
+                        Cipher rsaOaepCipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
+                        rsaOaepCipher.init(Cipher.DECRYPT_MODE, rsaKeyEntry.getPrivateKey());
+                        decryptedAESKey = rsaOaepCipher.doFinal(encryptedAESBytes);
                         migrationNeeded = true;
+                        // Re-encrypt the AES key using OAEP and store it for future use
+                        byte[] reEncryptedAESKey = rsaOaepCipher.doFinal(decryptedAESKey);
+                        storage.storeString(KEY_ALIAS, Base64.encodeToString(reEncryptedAESKey, Base64.DEFAULT));
                     } catch (Exception pkcs1Exception) {
EOF
@@ -408,6 +408,9 @@
KeyStore.PrivateKeyEntry rsaKeyEntry = getRSAKeyEntry();
Cipher rsaPkcs1Cipher = Cipher.getInstance(OLD_PKCS1_RSA_TRANSFORMATION);
rsaPkcs1Cipher.init(Cipher.DECRYPT_MODE, rsaKeyEntry.getPrivateKey());
decryptedAESKey = rsaPkcs1Cipher.doFinal(encryptedAESBytes);
Cipher rsaOaepCipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
rsaOaepCipher.init(Cipher.DECRYPT_MODE, rsaKeyEntry.getPrivateKey());
decryptedAESKey = rsaOaepCipher.doFinal(encryptedAESBytes);
migrationNeeded = true;
// Re-encrypt the AES key using OAEP and store it for future use
byte[] reEncryptedAESKey = rsaOaepCipher.doFinal(decryptedAESKey);
storage.storeString(KEY_ALIAS, Base64.encodeToString(reEncryptedAESKey, Base64.DEFAULT));
} catch (Exception pkcs1Exception) {
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

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.
1>First, attempt to decrypt the stored AES key using the new, secure OAEP padding.
2>If that fails (specifically with a BadPaddingException or InvalidKeyException, which is expected for old keys), it then attempts a fallback decryption using the legacy PKCS1Padding.
3>If the legacy decryption succeeds, the key is immediately re-encrypted using the secure OAEP padding and re-saved to storage.

rsaPkcs1Cipher.init(Cipher.DECRYPT_MODE, rsaKeyEntry.getPrivateKey());
decryptedAESKey = rsaPkcs1Cipher.doFinal(encryptedAESBytes);
migrationNeeded = true;
} catch (Exception pkcs1Exception) {
Log.e(TAG, "Failed to decrypt AES key with PKCS1Padding fallback."
, pkcs1Exception);
decryptedAESKey = null;
}
} else {
throw e;
}
}
if (decryptedAESKey != null) {
final int aesExpectedLengthInBytes = AES_KEY_SIZE / 8;
if (decryptedAESKey.length != aesExpectedLengthInBytes) {
Log.w(TAG, "Decrypted AES key has incorrect length (" +
decryptedAESKey.length + " bytes, expected " + aesExpectedLengthInBytes
+ "). Discarding.");
decryptedAESKey = null;
migrationNeeded = false;
}
}
}
//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;
} 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);
throw new IncompatibleDeviceException(e);

if (migrationNeeded && decryptedAESKey != null) {
try {
deleteRSAKeys();

byte[] encryptedAESWithOAEP = RSAEncrypt(decryptedAESKey);
String newEncodedEncryptedAES = new String(Base64.encode(encryptedAESWithOAEP, Base64.DEFAULT), StandardCharsets.UTF_8);
storage.store(KEY_ALIAS, newEncodedEncryptedAES);
} catch (Exception reEncryptEx) {
Log.e(TAG, "Failed to re-encrypt AES key with OAEP during migration. A new AES key will be generated.", reEncryptEx);
decryptedAESKey = null;
}
}
if (decryptedAESKey == null) {
try {
KeyGenerator keyGen = KeyGenerator.getInstance(ALGORITHM_AES);
keyGen.init(AES_KEY_SIZE);
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);
} catch (NoSuchAlgorithmException e) {
Log.e(TAG, "Error while creating the new AES key.", e);
throw new IncompatibleDeviceException(e);
}
}
return decryptedAESKey;
}


Expand Down
Loading