Skip to content

Commit d9bce21

Browse files
committed
feat: Add PKCS1 to OAEP migration with 4096-bit RSA keys fixed the migration logic for existing user
1 parent 3e24a2b commit d9bce21

File tree

2 files changed

+303
-38
lines changed

2 files changed

+303
-38
lines changed

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

Lines changed: 94 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ 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";
57+
private static final String OLD_PKCS1_RSA_TRANSFORMATION = "RSA/ECB/PKCS1Padding";
5758
// https://developer.android.com/reference/javax/crypto/Cipher.html
5859
@SuppressWarnings("SpellCheckingInspection")
5960
private static final String AES_TRANSFORMATION = "AES/GCM/NOPADDING";
@@ -62,7 +63,7 @@ class CryptoUtil {
6263
private static final String ALGORITHM_RSA = "RSA";
6364
private static final String ALGORITHM_AES = "AES";
6465
private static final int AES_KEY_SIZE = 256;
65-
private static final int RSA_KEY_SIZE = 2048;
66+
private static final int RSA_KEY_SIZE = 4096;
6667

6768
private static final byte FORMAT_MARKER = 0x01;
6869

@@ -372,30 +373,100 @@ byte[] RSAEncrypt(byte[] decryptedInput) throws IncompatibleDeviceException, Cry
372373
@VisibleForTesting
373374
byte[] getAESKey() throws IncompatibleDeviceException, CryptoException {
374375
String encodedEncryptedAES = storage.retrieveString(KEY_ALIAS);
375-
if (TextUtils.isEmpty(encodedEncryptedAES)) {
376-
encodedEncryptedAES = storage.retrieveString(OLD_KEY_ALIAS);
376+
if (!TextUtils.isEmpty(encodedEncryptedAES)) {
377+
byte[] encryptedAESBytes = Base64.decode(encodedEncryptedAES, Base64.DEFAULT);
378+
try {
379+
return RSADecrypt(encryptedAESBytes);
380+
} catch (IncompatibleDeviceException e) {
381+
String fullMessage = e.toString();
382+
Throwable cause = e.getCause();
383+
while (cause != null) {
384+
fullMessage += "\n" + cause.toString();
385+
cause = cause.getCause();
386+
}
387+
388+
if (fullMessage.contains("Incompatible padding mode") ||
389+
fullMessage.contains("INCOMPATIBLE_PADDING_MODE")) {
390+
try {
391+
KeyStore keyStore = KeyStore.getInstance(ANDROID_KEY_STORE);
392+
keyStore.load(null);
393+
394+
// Get the RSA key from KeyStore (could be at KEY_ALIAS or OLD_KEY_ALIAS)
395+
KeyStore.PrivateKeyEntry rsaKey = null;
396+
String keyAliasUsed = null;
397+
398+
if (keyStore.containsAlias(KEY_ALIAS)) {
399+
rsaKey = getKeyEntryCompat(keyStore, KEY_ALIAS);
400+
keyAliasUsed = KEY_ALIAS;
401+
} else if (keyStore.containsAlias(OLD_KEY_ALIAS)) {
402+
rsaKey = getKeyEntryCompat(keyStore, OLD_KEY_ALIAS);
403+
keyAliasUsed = OLD_KEY_ALIAS;
404+
}
405+
406+
if (rsaKey != null && keyAliasUsed != null) {
407+
// Decrypt using OLD PKCS1 padding
408+
Cipher rsaPkcs1Cipher = Cipher.getInstance(OLD_PKCS1_RSA_TRANSFORMATION);
409+
rsaPkcs1Cipher.init(Cipher.DECRYPT_MODE, rsaKey.getPrivateKey());
410+
byte[] decryptedAESKey = rsaPkcs1Cipher.doFinal(encryptedAESBytes);
411+
deleteRSAKeys();
412+
413+
// Re-encrypt AES key with NEW OAEP RSA key (4096-bit)
414+
byte[] encryptedAESWithOAEP = RSAEncrypt(decryptedAESKey);
415+
String newEncodedEncryptedAES = new String(
416+
Base64.encode(encryptedAESWithOAEP, Base64.DEFAULT),
417+
StandardCharsets.UTF_8
418+
);
419+
storage.store(KEY_ALIAS, newEncodedEncryptedAES);
420+
421+
return decryptedAESKey;
422+
}
423+
} catch (CryptoException | KeyStoreException | CertificateException |
424+
IOException | NoSuchAlgorithmException | UnrecoverableEntryException |
425+
NoSuchPaddingException | InvalidKeyException |
426+
IllegalBlockSizeException | BadPaddingException ex) {
427+
Log.e(TAG, "Could not migrate. A new key will be generated.", ex);
428+
deleteRSAKeys();
429+
deleteAESKeys();
430+
}
431+
}
432+
throw e;
433+
} catch (CryptoException e) {
434+
// RSA decryption failed - the encrypted AES key is corrupted or the RSA key is invalid
435+
// Delete keys and regenerate them
436+
Log.w(TAG, "RSA decryption failed with CryptoException. Keys may be corrupted. Will regenerate.", e);
437+
deleteRSAKeys();
438+
deleteAESKeys();
439+
}
377440
}
378-
if (encodedEncryptedAES != null) {
379-
//Return existing key
380-
byte[] encryptedAES = Base64.decode(encodedEncryptedAES, Base64.DEFAULT);
381-
byte[] existingAES = RSADecrypt(encryptedAES);
382-
final int aesExpectedLengthInBytes = AES_KEY_SIZE / 8;
383-
//Prevent returning an 'Empty key' (invalid/corrupted) that was mistakenly saved
384-
if (existingAES != null && existingAES.length == aesExpectedLengthInBytes) {
385-
//Key exists and has the right size
386-
return existingAES;
441+
String encodedOldAES = storage.retrieveString(OLD_KEY_ALIAS);
442+
if (!TextUtils.isEmpty(encodedOldAES)) {
443+
try {
444+
byte[] encryptedOldAESBytes = Base64.decode(encodedOldAES, Base64.DEFAULT);
445+
KeyStore.PrivateKeyEntry rsaKeyEntry = getRSAKeyEntry();
446+
Cipher rsaPkcs1Cipher = Cipher.getInstance(OLD_PKCS1_RSA_TRANSFORMATION);
447+
rsaPkcs1Cipher.init(Cipher.DECRYPT_MODE, rsaKeyEntry.getPrivateKey());
448+
byte[] decryptedAESKey = rsaPkcs1Cipher.doFinal(encryptedOldAESBytes);
449+
450+
byte[] encryptedAESWithOAEP = RSAEncrypt(decryptedAESKey);
451+
String newEncodedEncryptedAES = new String(Base64.encode(encryptedAESWithOAEP, Base64.DEFAULT), StandardCharsets.UTF_8);
452+
storage.store(KEY_ALIAS, newEncodedEncryptedAES);
453+
storage.remove(OLD_KEY_ALIAS);
454+
return decryptedAESKey;
455+
} catch (Exception e) {
456+
Log.e(TAG, "Could not migrate the legacy AES key. A new key will be generated.", e);
457+
deleteAESKeys();
387458
}
388459
}
389-
//Key doesn't exist. Generate new AES
460+
390461
try {
391462
KeyGenerator keyGen = KeyGenerator.getInstance(ALGORITHM_AES);
392463
keyGen.init(AES_KEY_SIZE);
393-
byte[] aes = keyGen.generateKey().getEncoded();
394-
//Save encrypted encoded version
395-
byte[] encryptedAES = RSAEncrypt(aes);
396-
String encodedEncryptedAESText = new String(Base64.encode(encryptedAES, Base64.DEFAULT), StandardCharsets.UTF_8);
397-
storage.store(KEY_ALIAS, encodedEncryptedAESText);
398-
return aes;
464+
byte[] decryptedAESKey = keyGen.generateKey().getEncoded();
465+
466+
byte[] encryptedNewAES = RSAEncrypt(decryptedAESKey);
467+
String encodedEncryptedNewAESText = new String(Base64.encode(encryptedNewAES, Base64.DEFAULT), StandardCharsets.UTF_8);
468+
storage.store(KEY_ALIAS, encodedEncryptedNewAESText);
469+
return decryptedAESKey;
399470
} catch (NoSuchAlgorithmException e) {
400471
/*
401472
* This exceptions are safe to be ignored:
@@ -407,6 +478,9 @@ byte[] getAESKey() throws IncompatibleDeviceException, CryptoException {
407478
*/
408479
Log.e(TAG, "Error while creating the AES key.", e);
409480
throw new IncompatibleDeviceException(e);
481+
} catch (Exception e) {
482+
Log.e(TAG, "Unexpected error while creating the new AES key.", e);
483+
throw new CryptoException("Unexpected error while creating the new AES key.", e);
410484
}
411485
}
412486

0 commit comments

Comments
 (0)