-
Notifications
You must be signed in to change notification settings - Fork 165
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -53,7 +53,8 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 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"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private static final String OLD_PKCS1_RSA_TRANSFORMATION = "RSA/ECB/PKCS1Padding"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // https://developer.android.com/reference/javax/crypto/Cipher.html | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @SuppressWarnings("SpellCheckingInspection") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private static final String AES_TRANSFORMATION = "AES/GCM/NOPADDING"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -62,7 +63,7 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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 static final int RSA_KEY_SIZE = 4096; | |
| private static final int RSA_KEY_SIZE = 2048; |
There was a problem hiding this comment.
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 ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Check failure
Code scanning / CodeQL
Use of RSA algorithm without OAEP High
initialize an RSA cipher
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI about 3 hours ago
The core flag is at:
Cipher rsaPkcs1Cipher = Cipher.getInstance(OLD_PKCS1_RSA_TRANSFORMATION);The context makes this necessary for decryption during migration from old (insecure) data. The code then immediately re-encrypts using RSAEncrypt, which should internally use OAEP padding (this logic is correct).
Required fix:
- Add a warning comment to clarify that the use of PKCS1 here is intentional, only for decryption of legacy data, and should never be copied for encryption.
- Make sure any nearby encryption operations (such as RSAEncrypt) use OAEP and, if not, update them.
- If possible within the snippet, add a comment or refactor variable names to reduce the risk of pattern duplication elsewhere.
Imports, methods, and definitions:
- No new imports required.
- Only comment additions/clarification within the given snippet.
- If the
OLD_PKCS1_RSA_TRANSFORMATIONdefinition is present in the snippet, comment its meaning.
-
Copy modified lines R407-R408
| @@ -404,7 +404,8 @@ | ||
| } | ||
|
|
||
| if (rsaKey != null && keyAliasUsed != null) { | ||
| // Decrypt using OLD PKCS1 padding | ||
| // WARNING: Using PKCS1 padding here is intentional and ONLY for decrypting legacy data | ||
| // Do NOT use PKCS1 padding for encryption in new code; always use OAEP padding instead. | ||
| Cipher rsaPkcs1Cipher = Cipher.getInstance(OLD_PKCS1_RSA_TRANSFORMATION); | ||
| rsaPkcs1Cipher.init(Cipher.DECRYPT_MODE, rsaKey.getPrivateKey()); | ||
| byte[] decryptedAESKey = rsaPkcs1Cipher.doFinal(encryptedAESBytes); |
Fixed
Show fixed
Hide fixed
Check failure
Code scanning / CodeQL
Use of RSA algorithm without OAEP High
initialize an RSA cipher
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI about 3 hours ago
To fix the vulnerability, ensure RSA encryption and decryption only use OAEP padding, not PKCS#1. However, this migration logic exists because some encrypted keys from the past might only be unlocked with PKCS#1. Normally, you'd want to remove this migration code altogether if you can safely assume all users have migrated, or mitigate its risk by restricting who/what can call this code and logging all its use for monitoring. If you must retain migration logic, never allow attacker-controlled input to reach this point, and clearly document the deprecation and intention to remove this code in the near future. If possible, fail closed: don't perform legacy PKCS#1 decryption and show an error.
Best fix without loss of functionality: Remove the PKCS#1 decryption logic (lines 442–455). If a legacy AES key is present, log that migration is no longer supported and generate a new key instead. This disables legacy decryption but maintains safe default behavior (key rotation instead of possibly insecure migration).
Required changes:
- In
getAESKey(relevant snippet lines 441–459), remove the code block that attempts to decrypt using PKCS#1 and migrate. IfOLD_KEY_ALIASis found, treat it as undecryptable: log this scenario, delete the old key, and proceed to generate a new AES key. - No new imports are needed.
- No new methods or variables are required.
-
Copy modified lines R443-R445
| @@ -440,22 +440,9 @@ | ||
| } | ||
| 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) { | ||
| Log.e(TAG, "Could not migrate the legacy AES key. A new key will be generated.", e); | ||
| deleteAESKeys(); | ||
| } | ||
| // Legacy AES key migration via insecure RSA/PKCS1 decryption is no longer supported. | ||
| Log.w(TAG, "Found legacy AES key, but PKCS#1 decryption is disabled. The key will be cleared and a new one generated."); | ||
| deleteAESKeys(); | ||
| } | ||
|
|
||
| try { |
Copilot
AI
Aug 19, 2025
There was a problem hiding this comment.
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.
| } catch (Exception e) { | |
| } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | BadPaddingException | IllegalBlockSizeException | KeyStoreException | UnrecoverableEntryException e) { |
Copilot
AI
Aug 19, 2025
There was a problem hiding this comment.
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.
| } catch (Exception e) { | |
| } catch (InvalidKeyException | |
| | NoSuchPaddingException | |
| | IllegalBlockSizeException | |
| | BadPaddingException | |
| | KeyStoreException | |
| | UnrecoverableEntryException | |
| | CertificateException | |
| | IOException | |
| | ProviderException e) { |
Check failure
Code scanning / CodeQL
Use of RSA algorithm without OAEP High
Copilot Autofix
AI about 3 hours ago
The problem is the use of
"RSA/ECB/PKCS1Padding"in anyCipher.getInstancecall, specifically at lines 408 and 446, in conjunction withOLD_PKCS1_RSA_TRANSFORMATION. The correct approach is to only use"RSA/ECB/OAEPWithSHA-256AndMGF1Padding"(already present asRSA_TRANSFORMATION).However, decryption of data that was encrypted previously using PKCS1 cannot be completed without using PKCS1. Thus, if backward compatibility and migration are critical, we must minimize the PKCS1 window:
The current code already follows this pattern: PKCS1 is only used for decryption in migration code, and the re-encryption uses OAEP immediately after. This is acceptable if legacy support is necessary, and not fixable without breaking backward compatibility. But if possible (e.g., on a new install, or after some period), the migration code and any PKCS1 support should be removed entirely.
Action:
For stricter security, optionally throw/log a warning if PKCS1 is ever used and include a feature flag to disable PKCS1 decrypt migration.
We can also clarify in the code with comments, and (optionally) add a runtime check to refuse decryption with PKCS1 unless in a migration context. If you implement version checks etc., do it here.
In summary: No code changes necessary outside of documentation, deprecation warnings, or removing migration code when migration is complete. Full OAEP-only support is best; PKCS1 use is strictly for necessary, one-time migration.