From efd8e2d9ff25f59e3d4bcaab664641b0e45761e4 Mon Sep 17 00:00:00 2001 From: Alexander Bakker Date: Fri, 3 Mar 2023 23:04:33 +0100 Subject: [PATCH] Run key derivation for Authenticator Pro importer on background thread --- .../aegis/importers/AndOtpImporter.java | 83 ++--------------- .../importers/AuthenticatorProImporter.java | 40 ++++++--- .../aegis/ui/tasks/PBKDFTask.java | 88 +++++++++++++++++++ 3 files changed, 123 insertions(+), 88 deletions(-) create mode 100644 app/src/main/java/com/beemdevelopment/aegis/ui/tasks/PBKDFTask.java diff --git a/app/src/main/java/com/beemdevelopment/aegis/importers/AndOtpImporter.java b/app/src/main/java/com/beemdevelopment/aegis/importers/AndOtpImporter.java index 4d6f6b7e91..aad90a55f2 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/importers/AndOtpImporter.java +++ b/app/src/main/java/com/beemdevelopment/aegis/importers/AndOtpImporter.java @@ -18,7 +18,7 @@ import com.beemdevelopment.aegis.otp.SteamInfo; import com.beemdevelopment.aegis.otp.TotpInfo; import com.beemdevelopment.aegis.ui.dialogs.Dialogs; -import com.beemdevelopment.aegis.ui.tasks.ProgressDialogTask; +import com.beemdevelopment.aegis.ui.tasks.PBKDFTask; import com.beemdevelopment.aegis.util.IOUtils; import com.beemdevelopment.aegis.vault.VaultEntry; import com.topjohnwu.superuser.io.SuFile; @@ -35,8 +35,6 @@ import java.security.InvalidKeyException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.KeySpec; import java.util.Arrays; import java.util.Locale; @@ -45,8 +43,6 @@ import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKey; -import javax.crypto.SecretKeyFactory; -import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.SecretKeySpec; public class AndOtpImporter extends DatabaseImporter { @@ -117,7 +113,7 @@ private DecryptedState decryptContent(SecretKey key, int offset) throws Database } } - private KeyDerivationParams getKeyDerivationParams(char[] password) throws DatabaseImporterException { + private PBKDFTask.Params getKeyDerivationParams(char[] password) throws DatabaseImporterException { byte[] iterBytes = Arrays.copyOfRange(_data, 0, INT_SIZE); int iterations = ByteBuffer.wrap(iterBytes).getInt(); if (iterations < 1) { @@ -131,7 +127,7 @@ private KeyDerivationParams getKeyDerivationParams(char[] password) throws Datab } byte[] salt = Arrays.copyOfRange(_data, INT_SIZE, INT_SIZE + SALT_SIZE); - return new KeyDerivationParams(password, salt, iterations); + return new PBKDFTask.Params("PBKDF2WithHmacSHA1", KEY_SIZE, password, salt, iterations); } protected DecryptedState decryptOldFormat(char[] password) throws DatabaseImporterException { @@ -155,8 +151,8 @@ protected DecryptedState decryptNewFormat(SecretKey key) throws DatabaseImporter protected DecryptedState decryptNewFormat(char[] password) throws DatabaseImporterException { - KeyDerivationParams params = getKeyDerivationParams(password); - SecretKey key = AndOtpKeyDerivationTask.deriveKey(params); + PBKDFTask.Params params = getKeyDerivationParams(password); + SecretKey key = PBKDFTask.deriveKey(params); return decryptNewFormat(key); } @@ -165,8 +161,8 @@ private void decrypt(Context context, char[] password, boolean oldFormat, Decryp DecryptedState state = decryptOldFormat(password); listener.onStateDecrypted(state); } else { - KeyDerivationParams params = getKeyDerivationParams(password); - AndOtpKeyDerivationTask task = new AndOtpKeyDerivationTask(context, key -> { + PBKDFTask.Params params = getKeyDerivationParams(password); + PBKDFTask task = new PBKDFTask(context, key -> { try { DecryptedState state = decryptNewFormat(key); listener.onStateDecrypted(state); @@ -269,71 +265,10 @@ private static VaultEntry convertEntry(JSONObject obj) throws DatabaseImporterEn } return new VaultEntry(info, name, issuer); - } catch (DatabaseImporterException | EncodingException | OtpInfoException | JSONException e) { + } catch (DatabaseImporterException | EncodingException | OtpInfoException | + JSONException e) { throw new DatabaseImporterEntryException(e, obj.toString()); } } } - - protected static class AndOtpKeyDerivationTask extends ProgressDialogTask { - private Callback _cb; - - public AndOtpKeyDerivationTask(Context context, Callback cb) { - super(context, context.getString(R.string.unlocking_vault)); - _cb = cb; - } - - @Override - protected SecretKey doInBackground(AndOtpImporter.KeyDerivationParams... args) { - setPriority(); - - AndOtpImporter.KeyDerivationParams params = args[0]; - return deriveKey(params); - } - - protected static SecretKey deriveKey(KeyDerivationParams params) { - try { - SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); - KeySpec spec = new PBEKeySpec(params.getPassword(), params.getSalt(), params.getIterations(), KEY_SIZE); - SecretKey key = factory.generateSecret(spec); - return new SecretKeySpec(key.getEncoded(), "AES"); - } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { - throw new RuntimeException(e); - } - } - - @Override - protected void onPostExecute(SecretKey key) { - super.onPostExecute(key); - _cb.onTaskFinished(key); - } - - public interface Callback { - void onTaskFinished(SecretKey key); - } - } - - protected static class KeyDerivationParams { - private final char[] _password; - private final byte[] _salt; - private final int _iterations; - - public KeyDerivationParams(char[] password, byte[] salt, int iterations) { - _iterations = iterations; - _password = password; - _salt = salt; - } - - public char[] getPassword() { - return _password; - } - - public int getIterations() { - return _iterations; - } - - public byte[] getSalt() { - return _salt; - } - } } diff --git a/app/src/main/java/com/beemdevelopment/aegis/importers/AuthenticatorProImporter.java b/app/src/main/java/com/beemdevelopment/aegis/importers/AuthenticatorProImporter.java index 716c1224c1..354ded2d2f 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/importers/AuthenticatorProImporter.java +++ b/app/src/main/java/com/beemdevelopment/aegis/importers/AuthenticatorProImporter.java @@ -4,15 +4,19 @@ import android.content.pm.PackageManager; import android.database.Cursor; +import androidx.lifecycle.Lifecycle; + import com.beemdevelopment.aegis.R; import com.beemdevelopment.aegis.encoding.Base32; import com.beemdevelopment.aegis.encoding.EncodingException; +import com.beemdevelopment.aegis.helpers.ContextHelper; import com.beemdevelopment.aegis.otp.HotpInfo; import com.beemdevelopment.aegis.otp.OtpInfo; import com.beemdevelopment.aegis.otp.OtpInfoException; import com.beemdevelopment.aegis.otp.SteamInfo; import com.beemdevelopment.aegis.otp.TotpInfo; import com.beemdevelopment.aegis.ui.dialogs.Dialogs; +import com.beemdevelopment.aegis.ui.tasks.PBKDFTask; import com.beemdevelopment.aegis.util.IOUtils; import com.beemdevelopment.aegis.vault.VaultEntry; import com.topjohnwu.superuser.io.SuFile; @@ -30,8 +34,6 @@ import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.KeySpec; import java.util.List; import javax.crypto.BadPaddingException; @@ -39,9 +41,7 @@ import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKey; -import javax.crypto.SecretKeyFactory; import javax.crypto.spec.IvParameterSpec; -import javax.crypto.spec.PBEKeySpec; public class AuthenticatorProImporter extends DatabaseImporter { private static final String HEADER = "AuthenticatorPro"; @@ -144,16 +144,18 @@ public EncryptedState(Cipher cipher, byte[] salt, byte[] iv, byte[] data) { } public JsonState decrypt(char[] password) throws DatabaseImporterException { + PBKDFTask.Params params = getKeyDerivationParams(password); + SecretKey key = PBKDFTask.deriveKey(params); + return decrypt(key); + } + + public JsonState decrypt(SecretKey key) throws DatabaseImporterException { try { - KeySpec spec = new PBEKeySpec(password, _salt, ITERATIONS, KEY_SIZE); - SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); - SecretKey key = keyFactory.generateSecret(spec); _cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(_iv)); byte[] decrypted = _cipher.doFinal(_data); return new JsonState(new JSONObject(new String(decrypted, StandardCharsets.UTF_8))); } catch (InvalidAlgorithmParameterException | IllegalBlockSizeException - | JSONException | InvalidKeyException | BadPaddingException - | InvalidKeySpecException | NoSuchAlgorithmException e) { + | JSONException | InvalidKeyException | BadPaddingException e) { throw new DatabaseImporterException(e); } } @@ -161,13 +163,23 @@ public JsonState decrypt(char[] password) throws DatabaseImporterException { @Override public void decrypt(Context context, DecryptListener listener) throws DatabaseImporterException { Dialogs.showPasswordInputDialog(context, R.string.enter_password_aegis_title, 0, (Dialogs.TextInputListener) password -> { - try { - listener.onStateDecrypted(decrypt(password)); - } catch (DatabaseImporterException e) { - listener.onError(e); - } + PBKDFTask.Params params = getKeyDerivationParams(password); + PBKDFTask task = new PBKDFTask(context, key -> { + try { + AuthenticatorProImporter.JsonState state = decrypt(key); + listener.onStateDecrypted(state); + } catch (DatabaseImporterException e) { + listener.onError(e); + } + }); + Lifecycle lifecycle = ContextHelper.getLifecycle(context); + task.execute(lifecycle, params); }, dialog -> listener.onCanceled()); } + + private PBKDFTask.Params getKeyDerivationParams(char[] password) { + return new PBKDFTask.Params("PBKDF2WithHmacSHA1", KEY_SIZE, password, _salt, ITERATIONS); + } } private static class JsonState extends State { diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/tasks/PBKDFTask.java b/app/src/main/java/com/beemdevelopment/aegis/ui/tasks/PBKDFTask.java new file mode 100644 index 0000000000..da05e58c17 --- /dev/null +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/tasks/PBKDFTask.java @@ -0,0 +1,88 @@ +package com.beemdevelopment.aegis.ui.tasks; + +import android.content.Context; + +import com.beemdevelopment.aegis.R; + +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; + +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.SecretKeySpec; + +public class PBKDFTask extends ProgressDialogTask { + private final Callback _cb; + + public PBKDFTask(Context context, Callback cb) { + super(context, context.getString(R.string.unlocking_vault)); + _cb = cb; + } + + @Override + protected SecretKey doInBackground(Params... args) { + setPriority(); + + Params params = args[0]; + return deriveKey(params); + } + + public static SecretKey deriveKey(Params params) { + try { + SecretKeyFactory factory = SecretKeyFactory.getInstance(params.getAlgorithm()); + KeySpec spec = new PBEKeySpec(params.getPassword(), params.getSalt(), params.getIterations(), params.getKeySize()); + SecretKey key = factory.generateSecret(spec); + return new SecretKeySpec(key.getEncoded(), "AES"); + } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { + throw new RuntimeException(e); + } + } + + @Override + protected void onPostExecute(SecretKey key) { + super.onPostExecute(key); + _cb.onTaskFinished(key); + } + + public interface Callback { + void onTaskFinished(SecretKey key); + } + + public static class Params { + private final String _algorithm; + private final int _keySize; + private final char[] _password; + private final byte[] _salt; + private final int _iterations; + + public Params(String algorithm, int keySize, char[] password, byte[] salt, int iterations) { + _algorithm = algorithm; + _keySize = keySize; + _iterations = iterations; + _password = password; + _salt = salt; + } + + public String getAlgorithm() { + return _algorithm; + } + + public int getKeySize() { + return _keySize; + } + + public char[] getPassword() { + return _password; + } + + public int getIterations() { + return _iterations; + } + + public byte[] getSalt() { + return _salt; + } + } +}