Skip to content

Commit a0e3f46

Browse files
committed
RSA encryption padding change from PKCS1Padding to OAEPWithSHA-256AndMGF1Padding
1 parent 495d1a9 commit a0e3f46

File tree

1 file changed

+108
-30
lines changed
  • auth0/src/main/java/com/auth0/android/authentication/storage

1 file changed

+108
-30
lines changed

auth0/src/main/java/com/auth0/android/authentication/storage/CryptoUtil.java

Lines changed: 108 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,11 @@ class CryptoUtil {
5353

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

6162
private static final String ANDROID_KEY_STORE = "AndroidKeyStore";
6263
private static final String ALGORITHM_RSA = "RSA";
@@ -124,7 +125,8 @@ KeyStore.PrivateKeyEntry getRSAKeyEntry() throws CryptoException, IncompatibleDe
124125
.setCertificateNotBefore(start.getTime())
125126
.setCertificateNotAfter(end.getTime())
126127
.setKeySize(RSA_KEY_SIZE)
127-
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)
128+
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_OAEP)
129+
.setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA1)
128130
.setBlockModes(KeyProperties.BLOCK_MODE_ECB)
129131
.build();
130132
} else {
@@ -355,6 +357,7 @@ byte[] RSAEncrypt(byte[] decryptedInput) throws IncompatibleDeviceException, Cry
355357

356358
/**
357359
* Attempts to recover the existing AES Key or generates a new one if none is found.
360+
* Handles migration from PKCS1Padding-encrypted AES keys to OAEP-encrypted ones.
358361
*
359362
* @return a valid AES Key bytes
360363
* @throws IncompatibleDeviceException in the event the device can't understand the cryptographic settings required
@@ -366,39 +369,114 @@ byte[] getAESKey() throws IncompatibleDeviceException, CryptoException {
366369
if (TextUtils.isEmpty(encodedEncryptedAES)) {
367370
encodedEncryptedAES = storage.retrieveString(OLD_KEY_ALIAS);
368371
}
372+
byte[] decryptedAESKey = null;
373+
boolean migrationNeeded = false;
369374
if (encodedEncryptedAES != null) {
370375
//Return existing key
371-
byte[] encryptedAES = Base64.decode(encodedEncryptedAES, Base64.DEFAULT);
372-
byte[] existingAES = RSADecrypt(encryptedAES);
373-
final int aesExpectedLengthInBytes = AES_KEY_SIZE / 8;
374-
//Prevent returning an 'Empty key' (invalid/corrupted) that was mistakenly saved
375-
if (existingAES != null && existingAES.length == aesExpectedLengthInBytes) {
376-
//Key exists and has the right size
377-
return existingAES;
376+
byte[] encryptedAESBytes = Base64.decode(encodedEncryptedAES, Base64.DEFAULT);
377+
try {
378+
// Attempt 1: Decrypt with new OAEP configuration
379+
Log.d(TAG, "Attempting to decrypt AES key with OAEP (" + RSA_TRANSFORMATION + ").");
380+
decryptedAESKey = RSADecrypt(encryptedAESBytes); // RSADecrypt uses the new RSA_TRANSFORMATION (OAEP)
381+
Log.d(TAG, "AES key successfully decrypted with OAEP.");
382+
}catch (IncompatibleDeviceException e){
383+
Log.w(TAG, "Failed to decrypt AES key with OAEP due to " +
384+
"IncompatibleDeviceException. Cause: "
385+
+ (e.getCause() != null ? e.getCause().getClass().getSimpleName() : "N/A")
386+
+ ". Attempting PKCS1 fallback for migration.", e);
387+
Throwable cause = e.getCause();
388+
if (cause instanceof InvalidKeyException) { // Specifically if key was not compatible with OAEP
389+
try {
390+
// Attempt 2: Decrypt with old PKCS1Padding configuration
391+
Log.d(TAG, "Attempting to decrypt AES key with PKCS1Padding " +
392+
"("+ OLD_PKCS1_RSA_TRANSFORMATION +") for migration due to " +
393+
"InvalidKeyException with OAEP.");
394+
KeyStore.PrivateKeyEntry rsaKeyEntry = getRSAKeyEntry();
395+
Cipher rsaPkcs1Cipher = Cipher.getInstance(OLD_PKCS1_RSA_TRANSFORMATION);
396+
rsaPkcs1Cipher.init(Cipher.DECRYPT_MODE, rsaKeyEntry.getPrivateKey());
397+
decryptedAESKey = rsaPkcs1Cipher.doFinal(encryptedAESBytes);
398+
Log.i(TAG, "Successfully decrypted legacy AES key using PKCS1Padding. Migration is needed.");
399+
migrationNeeded = true;
400+
} catch (Exception pkcs1Exception) {
401+
Log.e(TAG, "Failed to decrypt AES key with PKCS1Padding fallback.",
402+
pkcs1Exception);
403+
decryptedAESKey = null; // Ensure it's null after failed fallback
404+
}
405+
} else {
406+
// Not an InvalidKeyException cause we're handling for PKCS1 fallback for IncompatibleDeviceException, so rethrow
407+
throw e;
408+
}
409+
}catch (CryptoException e) {
410+
Log.w(TAG, "Failed to decrypt AES key with OAEP. Cause: " +
411+
(e.getCause() != null ? e.getCause().getClass().getSimpleName() : "N/A") +
412+
". Attempting PKCS1 fallback for migration.", e);
413+
Throwable cause = e.getCause();
414+
if (cause instanceof BadPaddingException || cause instanceof
415+
IllegalBlockSizeException || cause instanceof InvalidKeyException) {
416+
try {
417+
// Attempt 2: Decrypt with old PKCS1Padding configuration
418+
Log.d(TAG, "Attempting to decrypt AES key with PKCS1Padding ("+
419+
OLD_PKCS1_RSA_TRANSFORMATION +") for migration.");
420+
KeyStore.PrivateKeyEntry rsaKeyEntry = getRSAKeyEntry();
421+
Cipher rsaPkcs1Cipher = Cipher.getInstance(OLD_PKCS1_RSA_TRANSFORMATION);
422+
rsaPkcs1Cipher.init(Cipher.DECRYPT_MODE, rsaKeyEntry.getPrivateKey());
423+
decryptedAESKey = rsaPkcs1Cipher.doFinal(encryptedAESBytes);
424+
Log.i(TAG, "Successfully decrypted legacy AES key using PKCS1Padding."
425+
+ " Migration is needed.");
426+
migrationNeeded = true;
427+
} catch (Exception pkcs1Exception) {
428+
Log.e(TAG, "Failed to decrypt AES key with PKCS1Padding fallback."
429+
, pkcs1Exception);
430+
decryptedAESKey = null;
431+
}
432+
} else {
433+
// Not a known migration-related exception cause from CryptoException, rethrow.
434+
throw e;
435+
}
436+
}
437+
if (decryptedAESKey != null) {
438+
final int aesExpectedLengthInBytes = AES_KEY_SIZE / 8;
439+
if (decryptedAESKey.length != aesExpectedLengthInBytes) {
440+
Log.w(TAG, "Decrypted AES key has incorrect length (" +
441+
decryptedAESKey.length + " bytes, expected " + aesExpectedLengthInBytes
442+
+ "). Discarding.");
443+
decryptedAESKey = null;
444+
migrationNeeded = false;
445+
}
378446
}
379447
}
380-
//Key doesn't exist. Generate new AES
381-
try {
382-
KeyGenerator keyGen = KeyGenerator.getInstance(ALGORITHM_AES);
383-
keyGen.init(AES_KEY_SIZE);
384-
byte[] aes = keyGen.generateKey().getEncoded();
385-
//Save encrypted encoded version
386-
byte[] encryptedAES = RSAEncrypt(aes);
387-
String encodedEncryptedAESText = new String(Base64.encode(encryptedAES, Base64.DEFAULT), StandardCharsets.UTF_8);
388-
storage.store(KEY_ALIAS, encodedEncryptedAESText);
389-
return aes;
390-
} catch (NoSuchAlgorithmException e) {
391-
/*
392-
* This exceptions are safe to be ignored:
393-
*
394-
* - NoSuchAlgorithmException:
395-
* Thrown if the Algorithm implementation is not available. AES was introduced in API 1
396-
*
397-
* Read more in https://developer.android.com/reference/javax/crypto/KeyGenerator
398-
*/
399-
Log.e(TAG, "Error while creating the AES key.", e);
400-
throw new IncompatibleDeviceException(e);
448+
449+
if (migrationNeeded && decryptedAESKey != null) {
450+
try {
451+
Log.d(TAG, "AES key was from legacy PKCS1. Deleting old RSA key pair to ensure new OAEP-compatible RSA key is used for re-encryption.");
452+
deleteRSAKeys();
453+
454+
byte[] encryptedAESWithOAEP = RSAEncrypt(decryptedAESKey);
455+
String newEncodedEncryptedAES = new String(Base64.encode(encryptedAESWithOAEP, Base64.DEFAULT), StandardCharsets.UTF_8);
456+
storage.store(KEY_ALIAS, newEncodedEncryptedAES);
457+
Log.i(TAG, "AES key successfully migrated and re-encrypted with OAEP.");
458+
} catch (Exception reEncryptEx) {
459+
Log.e(TAG, "Failed to re-encrypt AES key with OAEP during migration. A new AES key will be generated.", reEncryptEx);
460+
decryptedAESKey = null;
461+
}
462+
}
463+
if (decryptedAESKey == null) {
464+
Log.d(TAG, "Generating new AES key.");
465+
try {
466+
KeyGenerator keyGen = KeyGenerator.getInstance(ALGORITHM_AES);
467+
keyGen.init(AES_KEY_SIZE);
468+
decryptedAESKey = keyGen.generateKey().getEncoded();
469+
470+
byte[] encryptedNewAES = RSAEncrypt(decryptedAESKey);
471+
String encodedEncryptedNewAESText = new String(Base64.encode(encryptedNewAES, Base64.DEFAULT), StandardCharsets.UTF_8);
472+
storage.store(KEY_ALIAS, encodedEncryptedNewAESText);
473+
Log.d(TAG, "New AES key generated, encrypted with OAEP, and stored.");
474+
} catch (NoSuchAlgorithmException e) {
475+
Log.e(TAG, "Error while creating the new AES key.", e);
476+
throw new IncompatibleDeviceException(e);
477+
}
401478
}
479+
return decryptedAESKey;
402480
}
403481

404482

0 commit comments

Comments
 (0)