@@ -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