Skip to content

Commit

Permalink
Run key derivation for Authenticator Pro importer on background thread
Browse files Browse the repository at this point in the history
  • Loading branch information
alexbakker committed Mar 3, 2023
1 parent 58b8edf commit efd8e2d
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 88 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand All @@ -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 {
Expand Down Expand Up @@ -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) {
Expand All @@ -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 {
Expand All @@ -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);
}

Expand All @@ -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);
Expand Down Expand Up @@ -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<AndOtpImporter.KeyDerivationParams, SecretKey> {
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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -30,18 +34,14 @@
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;
import javax.crypto.Cipher;
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";
Expand Down Expand Up @@ -144,30 +144,42 @@ 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);
}
}

@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 {
Expand Down
Original file line number Diff line number Diff line change
@@ -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<PBKDFTask.Params, SecretKey> {
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;
}
}
}

0 comments on commit efd8e2d

Please sign in to comment.