Run key derivation for Authenticator Pro importer on background thread

This commit is contained in:
Alexander Bakker 2023-03-03 23:04:33 +01:00
parent 58b8edf318
commit efd8e2d9ff
3 changed files with 123 additions and 88 deletions

View file

@ -18,7 +18,7 @@ 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.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.InvalidAlgorithmParameterException;
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.Cipher;
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 @@ public class AndOtpImporter extends DatabaseImporter {
}
}
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 @@ public class AndOtpImporter extends DatabaseImporter {
}
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 @@ public class AndOtpImporter extends 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 @@ public class AndOtpImporter extends DatabaseImporter {
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 @@ public class AndOtpImporter extends DatabaseImporter {
}
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;
}
}
}

View file

@ -4,15 +4,19 @@ import android.content.Context;
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.nio.charset.StandardCharsets;
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.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";
@ -144,16 +144,18 @@ public class AuthenticatorProImporter extends DatabaseImporter {
}
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 class AuthenticatorProImporter extends DatabaseImporter {
@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 {

View file

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