mirror of
https://github.com/beemdevelopment/Aegis.git
synced 2025-04-20 22:09:12 +00:00
Run key derivation for Authenticator Pro importer on background thread
This commit is contained in:
parent
58b8edf318
commit
efd8e2d9ff
3 changed files with 123 additions and 88 deletions
|
@ -18,7 +18,7 @@ import com.beemdevelopment.aegis.otp.OtpInfoException;
|
||||||
import com.beemdevelopment.aegis.otp.SteamInfo;
|
import com.beemdevelopment.aegis.otp.SteamInfo;
|
||||||
import com.beemdevelopment.aegis.otp.TotpInfo;
|
import com.beemdevelopment.aegis.otp.TotpInfo;
|
||||||
import com.beemdevelopment.aegis.ui.dialogs.Dialogs;
|
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.util.IOUtils;
|
||||||
import com.beemdevelopment.aegis.vault.VaultEntry;
|
import com.beemdevelopment.aegis.vault.VaultEntry;
|
||||||
import com.topjohnwu.superuser.io.SuFile;
|
import com.topjohnwu.superuser.io.SuFile;
|
||||||
|
@ -35,8 +35,6 @@ import java.security.InvalidAlgorithmParameterException;
|
||||||
import java.security.InvalidKeyException;
|
import java.security.InvalidKeyException;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.spec.InvalidKeySpecException;
|
|
||||||
import java.security.spec.KeySpec;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
|
@ -45,8 +43,6 @@ import javax.crypto.Cipher;
|
||||||
import javax.crypto.IllegalBlockSizeException;
|
import javax.crypto.IllegalBlockSizeException;
|
||||||
import javax.crypto.NoSuchPaddingException;
|
import javax.crypto.NoSuchPaddingException;
|
||||||
import javax.crypto.SecretKey;
|
import javax.crypto.SecretKey;
|
||||||
import javax.crypto.SecretKeyFactory;
|
|
||||||
import javax.crypto.spec.PBEKeySpec;
|
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
|
||||||
public class AndOtpImporter extends DatabaseImporter {
|
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);
|
byte[] iterBytes = Arrays.copyOfRange(_data, 0, INT_SIZE);
|
||||||
int iterations = ByteBuffer.wrap(iterBytes).getInt();
|
int iterations = ByteBuffer.wrap(iterBytes).getInt();
|
||||||
if (iterations < 1) {
|
if (iterations < 1) {
|
||||||
|
@ -131,7 +127,7 @@ public class AndOtpImporter extends DatabaseImporter {
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] salt = Arrays.copyOfRange(_data, INT_SIZE, INT_SIZE + SALT_SIZE);
|
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 {
|
protected DecryptedState decryptOldFormat(char[] password) throws DatabaseImporterException {
|
||||||
|
@ -155,8 +151,8 @@ public class AndOtpImporter extends DatabaseImporter {
|
||||||
|
|
||||||
protected DecryptedState decryptNewFormat(char[] password)
|
protected DecryptedState decryptNewFormat(char[] password)
|
||||||
throws DatabaseImporterException {
|
throws DatabaseImporterException {
|
||||||
KeyDerivationParams params = getKeyDerivationParams(password);
|
PBKDFTask.Params params = getKeyDerivationParams(password);
|
||||||
SecretKey key = AndOtpKeyDerivationTask.deriveKey(params);
|
SecretKey key = PBKDFTask.deriveKey(params);
|
||||||
return decryptNewFormat(key);
|
return decryptNewFormat(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -165,8 +161,8 @@ public class AndOtpImporter extends DatabaseImporter {
|
||||||
DecryptedState state = decryptOldFormat(password);
|
DecryptedState state = decryptOldFormat(password);
|
||||||
listener.onStateDecrypted(state);
|
listener.onStateDecrypted(state);
|
||||||
} else {
|
} else {
|
||||||
KeyDerivationParams params = getKeyDerivationParams(password);
|
PBKDFTask.Params params = getKeyDerivationParams(password);
|
||||||
AndOtpKeyDerivationTask task = new AndOtpKeyDerivationTask(context, key -> {
|
PBKDFTask task = new PBKDFTask(context, key -> {
|
||||||
try {
|
try {
|
||||||
DecryptedState state = decryptNewFormat(key);
|
DecryptedState state = decryptNewFormat(key);
|
||||||
listener.onStateDecrypted(state);
|
listener.onStateDecrypted(state);
|
||||||
|
@ -269,71 +265,10 @@ public class AndOtpImporter extends DatabaseImporter {
|
||||||
}
|
}
|
||||||
|
|
||||||
return new VaultEntry(info, name, issuer);
|
return new VaultEntry(info, name, issuer);
|
||||||
} catch (DatabaseImporterException | EncodingException | OtpInfoException | JSONException e) {
|
} catch (DatabaseImporterException | EncodingException | OtpInfoException |
|
||||||
|
JSONException e) {
|
||||||
throw new DatabaseImporterEntryException(e, obj.toString());
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,15 +4,19 @@ import android.content.Context;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
|
|
||||||
|
import androidx.lifecycle.Lifecycle;
|
||||||
|
|
||||||
import com.beemdevelopment.aegis.R;
|
import com.beemdevelopment.aegis.R;
|
||||||
import com.beemdevelopment.aegis.encoding.Base32;
|
import com.beemdevelopment.aegis.encoding.Base32;
|
||||||
import com.beemdevelopment.aegis.encoding.EncodingException;
|
import com.beemdevelopment.aegis.encoding.EncodingException;
|
||||||
|
import com.beemdevelopment.aegis.helpers.ContextHelper;
|
||||||
import com.beemdevelopment.aegis.otp.HotpInfo;
|
import com.beemdevelopment.aegis.otp.HotpInfo;
|
||||||
import com.beemdevelopment.aegis.otp.OtpInfo;
|
import com.beemdevelopment.aegis.otp.OtpInfo;
|
||||||
import com.beemdevelopment.aegis.otp.OtpInfoException;
|
import com.beemdevelopment.aegis.otp.OtpInfoException;
|
||||||
import com.beemdevelopment.aegis.otp.SteamInfo;
|
import com.beemdevelopment.aegis.otp.SteamInfo;
|
||||||
import com.beemdevelopment.aegis.otp.TotpInfo;
|
import com.beemdevelopment.aegis.otp.TotpInfo;
|
||||||
import com.beemdevelopment.aegis.ui.dialogs.Dialogs;
|
import com.beemdevelopment.aegis.ui.dialogs.Dialogs;
|
||||||
|
import com.beemdevelopment.aegis.ui.tasks.PBKDFTask;
|
||||||
import com.beemdevelopment.aegis.util.IOUtils;
|
import com.beemdevelopment.aegis.util.IOUtils;
|
||||||
import com.beemdevelopment.aegis.vault.VaultEntry;
|
import com.beemdevelopment.aegis.vault.VaultEntry;
|
||||||
import com.topjohnwu.superuser.io.SuFile;
|
import com.topjohnwu.superuser.io.SuFile;
|
||||||
|
@ -30,8 +34,6 @@ import java.nio.charset.StandardCharsets;
|
||||||
import java.security.InvalidAlgorithmParameterException;
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
import java.security.InvalidKeyException;
|
import java.security.InvalidKeyException;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.spec.InvalidKeySpecException;
|
|
||||||
import java.security.spec.KeySpec;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import javax.crypto.BadPaddingException;
|
import javax.crypto.BadPaddingException;
|
||||||
|
@ -39,9 +41,7 @@ import javax.crypto.Cipher;
|
||||||
import javax.crypto.IllegalBlockSizeException;
|
import javax.crypto.IllegalBlockSizeException;
|
||||||
import javax.crypto.NoSuchPaddingException;
|
import javax.crypto.NoSuchPaddingException;
|
||||||
import javax.crypto.SecretKey;
|
import javax.crypto.SecretKey;
|
||||||
import javax.crypto.SecretKeyFactory;
|
|
||||||
import javax.crypto.spec.IvParameterSpec;
|
import javax.crypto.spec.IvParameterSpec;
|
||||||
import javax.crypto.spec.PBEKeySpec;
|
|
||||||
|
|
||||||
public class AuthenticatorProImporter extends DatabaseImporter {
|
public class AuthenticatorProImporter extends DatabaseImporter {
|
||||||
private static final String HEADER = "AuthenticatorPro";
|
private static final String HEADER = "AuthenticatorPro";
|
||||||
|
@ -144,16 +144,18 @@ public class AuthenticatorProImporter extends DatabaseImporter {
|
||||||
}
|
}
|
||||||
|
|
||||||
public JsonState decrypt(char[] password) throws DatabaseImporterException {
|
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 {
|
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));
|
_cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(_iv));
|
||||||
byte[] decrypted = _cipher.doFinal(_data);
|
byte[] decrypted = _cipher.doFinal(_data);
|
||||||
return new JsonState(new JSONObject(new String(decrypted, StandardCharsets.UTF_8)));
|
return new JsonState(new JSONObject(new String(decrypted, StandardCharsets.UTF_8)));
|
||||||
} catch (InvalidAlgorithmParameterException | IllegalBlockSizeException
|
} catch (InvalidAlgorithmParameterException | IllegalBlockSizeException
|
||||||
| JSONException | InvalidKeyException | BadPaddingException
|
| JSONException | InvalidKeyException | BadPaddingException e) {
|
||||||
| InvalidKeySpecException | NoSuchAlgorithmException e) {
|
|
||||||
throw new DatabaseImporterException(e);
|
throw new DatabaseImporterException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -161,13 +163,23 @@ public class AuthenticatorProImporter extends DatabaseImporter {
|
||||||
@Override
|
@Override
|
||||||
public void decrypt(Context context, DecryptListener listener) throws DatabaseImporterException {
|
public void decrypt(Context context, DecryptListener listener) throws DatabaseImporterException {
|
||||||
Dialogs.showPasswordInputDialog(context, R.string.enter_password_aegis_title, 0, (Dialogs.TextInputListener) password -> {
|
Dialogs.showPasswordInputDialog(context, R.string.enter_password_aegis_title, 0, (Dialogs.TextInputListener) password -> {
|
||||||
try {
|
PBKDFTask.Params params = getKeyDerivationParams(password);
|
||||||
listener.onStateDecrypted(decrypt(password));
|
PBKDFTask task = new PBKDFTask(context, key -> {
|
||||||
} catch (DatabaseImporterException e) {
|
try {
|
||||||
listener.onError(e);
|
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());
|
}, 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 {
|
private static class JsonState extends State {
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue