Merge pull request #647 from alexbakker/more-tests

Add some more tests
This commit is contained in:
Alexander Bakker 2021-01-16 15:39:02 +01:00 committed by GitHub
commit a5ec7666ec
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
40 changed files with 974 additions and 321 deletions

View file

@ -42,13 +42,22 @@ android {
testOptions {
execution 'ANDROIDX_TEST_ORCHESTRATOR'
unitTests.all {
useJUnitPlatform()
unitTests {
all {
maxHeapSize "3g"
ignoreFailures false
testLogging {
events "passed", "skipped", "failed", "standardOut", "standardError"
ignoreFailures false
testLogging {
events "passed", "skipped", "failed", "standardOut", "standardError"
showExceptions true
exceptionFormat "full"
showCauses true
showStackTraces true
}
}
includeAndroidResources true
}
}
@ -111,10 +120,11 @@ protobuf {
}
dependencies {
def androidTestVersion = '1.3.0'
def cameraxVersion = '1.0.0-rc01'
def glideVersion = '4.11.0'
def guavaVersion = '30.1'
def junitVersion = '5.7.0'
def junitVersion = '4.13.1'
def libsuVersion = '3.0.2'
annotationProcessor 'androidx.annotation:annotation:1.1.0'
@ -157,19 +167,21 @@ dependencies {
implementation 'info.guardianproject.trustedintents:trustedintents:0.2'
implementation 'org.bouncycastle:bcprov-jdk15on:1.68'
androidTestImplementation 'androidx.test:core:1.3.0'
androidTestImplementation 'androidx.test:runner:1.3.0'
androidTestImplementation 'androidx.test:rules:1.3.0'
androidTestImplementation "androidx.test:core:${androidTestVersion}"
androidTestImplementation "androidx.test:runner:${androidTestVersion}"
androidTestImplementation "androidx.test:rules:${androidTestVersion}"
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.3.0'
androidTestImplementation 'androidx.test.espresso:espresso-intents:3.3.0'
androidTestImplementation 'junit:junit:4.13.1'
androidTestImplementation "junit:junit:${junitVersion}"
androidTestUtil 'androidx.test:orchestrator:1.3.0'
testImplementation "androidx.test:core:${androidTestVersion}"
testImplementation "com.google.guava:guava:${guavaVersion}-jre"
testImplementation "org.junit.jupiter:junit-jupiter-api:${junitVersion}"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:${junitVersion}"
testImplementation "junit:junit:${junitVersion}"
testImplementation "org.json:json:20200518"
testImplementation 'org.robolectric:robolectric:4.4'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.1'
}

View file

@ -39,6 +39,19 @@ public abstract class AegisTest {
}
protected VaultManager initVault() {
VaultFileCredentials creds = generateCredentials();
VaultManager vault = getApp().initVaultManager(new Vault(), creds);
try {
vault.save(false);
} catch (VaultManagerException e) {
throw new RuntimeException(e);
}
getApp().getPreferences().setIntroDone(true);
return vault;
}
protected VaultFileCredentials generateCredentials() {
PasswordSlot slot = new PasswordSlot();
byte[] salt = CryptoUtils.generateSalt();
SCryptParameters scryptParams = new SCryptParameters(
@ -59,17 +72,9 @@ public abstract class AegisTest {
| SlotException e) {
throw new RuntimeException(e);
}
creds.getSlots().add(slot);
VaultManager vault = getApp().initVaultManager(new Vault(), creds);
try {
vault.save(false);
} catch (VaultManagerException e) {
throw new RuntimeException(e);
}
getApp().getPreferences().setIntroDone(true);
return vault;
return creds;
}
protected static <T extends OtpInfo> VaultEntry generateEntry(Class<T> type, String name, String issuer) {

View file

@ -0,0 +1,39 @@
package com.beemdevelopment.aegis.vault;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.beemdevelopment.aegis.AegisTest;
import com.beemdevelopment.aegis.vault.slots.PasswordSlot;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class VaultManagerTest extends AegisTest {
@Before
public void before() {
initVault();
}
@Test
public void testToggleEncryption() throws VaultManagerException {
getVault().disableEncryption();
assertFalse(getVault().isEncryptionEnabled());
assertNull(getVault().getCredentials());
VaultFileCredentials creds = generateCredentials();
getVault().enableEncryption(creds);
assertTrue(getVault().isEncryptionEnabled());
assertNotNull(getVault().getCredentials());
assertEquals(getVault().getCredentials().getSlots().findAll(PasswordSlot.class).size(), 1);
}
}

View file

@ -4,11 +4,13 @@ import android.content.Context;
import com.beemdevelopment.aegis.encoding.EncodingException;
import com.beemdevelopment.aegis.otp.OtpInfoException;
import com.beemdevelopment.aegis.ui.tasks.PasswordSlotDecryptTask;
import com.beemdevelopment.aegis.util.IOUtils;
import com.beemdevelopment.aegis.vault.VaultEntry;
import com.beemdevelopment.aegis.vault.VaultFile;
import com.beemdevelopment.aegis.vault.VaultFileCredentials;
import com.beemdevelopment.aegis.vault.VaultFileException;
import com.beemdevelopment.aegis.vault.slots.PasswordSlot;
import com.beemdevelopment.aegis.vault.slots.SlotList;
import com.topjohnwu.superuser.io.SuFile;
@ -18,6 +20,7 @@ import org.json.JSONObject;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class AegisImporter extends DatabaseImporter {
@ -56,11 +59,24 @@ public class AegisImporter extends DatabaseImporter {
return _file.getHeader().getSlots();
}
public State decrypt(VaultFileCredentials creds) throws VaultFileException {
JSONObject obj = _file.getContent(creds);
public State decrypt(VaultFileCredentials creds) throws DatabaseImporterException {
JSONObject obj;
try {
obj = _file.getContent(creds);
} catch (VaultFileException e) {
throw new DatabaseImporterException(e);
}
return new DecryptedState(obj);
}
public State decrypt(char[] password) throws DatabaseImporterException {
List<PasswordSlot> slots = getSlots().findAll(PasswordSlot.class);
PasswordSlotDecryptTask.Result result = PasswordSlotDecryptTask.decrypt(slots, password);
VaultFileCredentials creds = new VaultFileCredentials(result.getKey(), getSlots());
return decrypt(creds);
}
@Override
public void decrypt(Context context, DecryptListener listener) {

View file

@ -95,7 +95,7 @@ public class AndOtpImporter extends DatabaseImporter {
_data = data;
}
private DecryptedState decryptData(SecretKey key, int offset) throws DatabaseImporterException {
private DecryptedState decryptContent(SecretKey key, int offset) throws DatabaseImporterException {
byte[] nonce = Arrays.copyOfRange(_data, offset, offset + NONCE_SIZE);
byte[] tag = Arrays.copyOfRange(_data, _data.length - TAG_SIZE, _data.length);
CryptParameters params = new CryptParameters(nonce, tag);
@ -116,35 +116,52 @@ public class AndOtpImporter extends DatabaseImporter {
}
}
private KeyDerivationParams getKeyDerivationParams(char[] password) throws DatabaseImporterException {
byte[] iterBytes = Arrays.copyOfRange(_data, 0, INT_SIZE);
int iterations = ByteBuffer.wrap(iterBytes).getInt();
if (iterations < 1) {
throw new DatabaseImporterException(String.format("Invalid number of iterations for PBKDF: %d", iterations));
}
byte[] salt = Arrays.copyOfRange(_data, INT_SIZE, INT_SIZE + SALT_SIZE);
return new KeyDerivationParams(password, salt, iterations);
}
protected DecryptedState decryptOldFormat(char[] password) throws DatabaseImporterException {
// WARNING: DON'T DO THIS IN YOUR OWN CODE
// this exists solely to support the old andOTP backup format
// it is not a secure way to derive a key from a password
MessageDigest hash;
try {
hash = MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
byte[] keyBytes = hash.digest(CryptoUtils.toBytes(password));
SecretKey key = new SecretKeySpec(keyBytes, "AES");
return decryptContent(key, 0);
}
protected DecryptedState decryptNewFormat(SecretKey key) throws DatabaseImporterException {
return decryptContent(key, INT_SIZE + SALT_SIZE);
}
protected DecryptedState decryptNewFormat(char[] password)
throws DatabaseImporterException {
KeyDerivationParams params = getKeyDerivationParams(password);
SecretKey key = AndOtpKeyDerivationTask.deriveKey(params);
return decryptNewFormat(key);
}
private void decrypt(Context context, char[] password, boolean oldFormat, DecryptListener listener) throws DatabaseImporterException {
if (oldFormat) {
// WARNING: DON'T DO THIS IN YOUR OWN CODE
// this exists solely to support the old andOTP backup format
// it is not a secure way to derive a key from a password
MessageDigest hash;
try {
hash = MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
byte[] keyBytes = hash.digest(CryptoUtils.toBytes(password));
SecretKey key = new SecretKeySpec(keyBytes, "AES");
DecryptedState state = decryptData(key, 0);
DecryptedState state = decryptOldFormat(password);
listener.onStateDecrypted(state);
} else {
int offset = INT_SIZE + SALT_SIZE;
byte[] iterBytes = Arrays.copyOfRange(_data, 0, INT_SIZE);
int iterations = ByteBuffer.wrap(iterBytes).getInt();
if (iterations < 1) {
throw new DatabaseImporterException(String.format("Invalid number of iterations for PBKDF: %d", iterations));
}
byte[] salt = Arrays.copyOfRange(_data, INT_SIZE, offset);
AndOtpKeyDerivationTask.Params params = new AndOtpKeyDerivationTask.Params(password, salt, iterations);
AndOtpKeyDerivationTask task = new AndOtpKeyDerivationTask(context, key1 -> {
KeyDerivationParams params = getKeyDerivationParams(password);
AndOtpKeyDerivationTask task = new AndOtpKeyDerivationTask(context, key -> {
try {
DecryptedState state = decryptData(key1, offset);
DecryptedState state = decryptNewFormat(key);
listener.onStateDecrypted(state);
} catch (DatabaseImporterException e) {
listener.onError(e);
@ -251,7 +268,7 @@ public class AndOtpImporter extends DatabaseImporter {
}
}
private static class AndOtpKeyDerivationTask extends ProgressDialogTask<AndOtpKeyDerivationTask.Params, SecretKey> {
protected static class AndOtpKeyDerivationTask extends ProgressDialogTask<AndOtpImporter.KeyDerivationParams, SecretKey> {
private Callback _cb;
public AndOtpKeyDerivationTask(Context context, Callback cb) {
@ -260,20 +277,22 @@ public class AndOtpImporter extends DatabaseImporter {
}
@Override
protected SecretKey doInBackground(AndOtpKeyDerivationTask.Params... args) {
protected SecretKey doInBackground(AndOtpImporter.KeyDerivationParams... args) {
setPriority();
AndOtpKeyDerivationTask.Params params = args[0];
SecretKey key;
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);
key = factory.generateSecret(spec);
SecretKey key = factory.generateSecret(spec);
return new SecretKeySpec(key.getEncoded(), "AES");
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
throw new RuntimeException(e);
}
return key;
}
@Override
@ -282,32 +301,32 @@ public class AndOtpImporter extends DatabaseImporter {
_cb.onTaskFinished(key);
}
public static class Params {
private char[] _password;
private byte[] _salt;
private int _iterations;
public Params(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;
}
}
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

@ -37,31 +37,38 @@ public class AuthenticatorPlusImporter extends DatabaseImporter {
}
public static class EncryptedState extends DatabaseImporter.State {
private byte[] _data;
private final byte[] _data;
private EncryptedState(byte[] data) {
super(true);
_data = data;
}
protected State decrypt(char[] password) throws DatabaseImporterException {
try (ByteArrayInputStream inStream = new ByteArrayInputStream(_data);
ZipInputStream zipStream = new ZipInputStream(inStream, password)) {
LocalFileHeader header;
while ((header = zipStream.getNextEntry()) != null) {
File file = new File(header.getFileName());
if (file.getName().equals(FILENAME)) {
GoogleAuthUriImporter importer = new GoogleAuthUriImporter(null);
return importer.read(zipStream);
}
}
throw new FileNotFoundException(FILENAME);
} catch (IOException e) {
throw new DatabaseImporterException(e);
}
}
@Override
public void decrypt(Context context, DecryptListener listener) {
Dialogs.showPasswordInputDialog(context, password -> {
try (ByteArrayInputStream inStream = new ByteArrayInputStream(_data);
ZipInputStream zipStream = new ZipInputStream(inStream, password)) {
LocalFileHeader header;
while ((header = zipStream.getNextEntry()) != null) {
File file = new File(header.getFileName());
if (file.getName().equals(FILENAME)) {
GoogleAuthUriImporter importer = new GoogleAuthUriImporter(context);
DatabaseImporter.State state = importer.read(zipStream);
listener.onStateDecrypted(state);
return;
}
}
throw new FileNotFoundException(FILENAME);
} catch (IOException | DatabaseImporterException e) {
try {
DatabaseImporter.State state = decrypt(password);
listener.onStateDecrypted(state);
} catch (DatabaseImporterException e) {
listener.onError(e);
}
});

View file

@ -42,6 +42,7 @@ import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
public class AuthyImporter extends DatabaseImporter {
private static final String _subPath = "shared_prefs";
@ -154,44 +155,53 @@ public class AuthyImporter extends DatabaseImporter {
_array = array;
}
protected DecryptedState decrypt(char[] password) throws DatabaseImporterException {
try {
for (int i = 0; i < _array.length(); i++) {
JSONObject obj = _array.getJSONObject(i);
String secretString = JsonUtils.optString(obj, "encryptedSecret");
if (secretString == null) {
continue;
}
byte[] encryptedSecret = Base64.decode(secretString);
byte[] salt = obj.getString("salt").getBytes(StandardCharsets.UTF_8);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
KeySpec spec = new PBEKeySpec(password, salt, ITERATIONS, KEY_SIZE);
SecretKey key = factory.generateSecret(spec);
key = new SecretKeySpec(key.getEncoded(), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec ivSpec = new IvParameterSpec(IV);
cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
byte[] secret = cipher.doFinal(encryptedSecret);
obj.remove("encryptedSecret");
obj.remove("salt");
obj.put("decryptedSecret", new String(secret, StandardCharsets.UTF_8));
}
return new DecryptedState(_array);
} catch (JSONException
| EncodingException
| NoSuchAlgorithmException
| InvalidKeySpecException
| InvalidAlgorithmParameterException
| InvalidKeyException
| NoSuchPaddingException
| BadPaddingException
| IllegalBlockSizeException e) {
throw new DatabaseImporterException(e);
}
}
@Override
public void decrypt(Context context, DecryptListener listener) {
Dialogs.showPasswordInputDialog(context, R.string.enter_password_authy_message, password -> {
try {
for (int i = 0; i < _array.length(); i++) {
JSONObject obj = _array.getJSONObject(i);
String secretString = JsonUtils.optString(obj, "encryptedSecret");
if (secretString == null) {
continue;
}
byte[] encryptedSecret = Base64.decode(secretString);
byte[] salt = obj.getString("salt").getBytes(StandardCharsets.UTF_8);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
KeySpec spec = new PBEKeySpec(password, salt, ITERATIONS, KEY_SIZE);
SecretKey key = factory.generateSecret(spec);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
IvParameterSpec ivSpec = new IvParameterSpec(IV);
cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
byte[] secret = cipher.doFinal(encryptedSecret);
obj.remove("encryptedSecret");
obj.remove("salt");
obj.put("decryptedSecret", new String(secret, StandardCharsets.UTF_8));
}
DecryptedState state = new DecryptedState(_array);
DecryptedState state = decrypt(password);
listener.onStateDecrypted(state);
} catch (JSONException
| EncodingException
| NoSuchAlgorithmException
| InvalidKeySpecException
| InvalidAlgorithmParameterException
| InvalidKeyException
| NoSuchPaddingException
| BadPaddingException
| IllegalBlockSizeException e) {
} catch (DatabaseImporterException e) {
listener.onError(e);
}
});
@ -274,6 +284,10 @@ public class AuthyImporter extends DatabaseImporter {
info.Issuer = info.Name;
info.Name = "";
}
if (info.Name.startsWith(": ")) {
info.Name = info.Name.substring(2);
}
}
}

View file

@ -28,9 +28,9 @@ public abstract class DatabaseImporter {
// note: keep these lists sorted alphabetically
_importers = new ArrayList<>();
_importers.add(new Definition("Aegis", AegisImporter.class, R.string.importer_help_aegis, false));
_importers.add(new Definition("andOTP", AndOtpImporter.class, R.string.importer_help_andotp, false));
_importers.add(new Definition("Authenticator Plus", AuthenticatorPlusImporter.class, R.string.importer_help_authenticator_plus, false));
_importers.add(new Definition("Authy", AuthyImporter.class, R.string.importer_help_authy, true));
_importers.add(new Definition("andOTP", AndOtpImporter.class, R.string.importer_help_andotp, false));
_importers.add(new Definition("FreeOTP", FreeOtpImporter.class, R.string.importer_help_freeotp, true));
_importers.add(new Definition("FreeOTP+", FreeOtpPlusImporter.class, R.string.importer_help_freeotp_plus, true));
_importers.add(new Definition("Google Authenticator", GoogleAuthImporter.class, R.string.importer_help_google_authenticator, true));

View file

@ -115,30 +115,6 @@ public class FreeOtpImporter extends DatabaseImporter {
}
}
private static List<JSONObject> parseXml(XmlPullParser parser)
throws IOException, XmlPullParserException, JSONException {
List<JSONObject> entries = new ArrayList<>();
parser.require(XmlPullParser.START_TAG, null, "map");
while (parser.next() != XmlPullParser.END_TAG) {
if (parser.getEventType() != XmlPullParser.START_TAG) {
continue;
}
if (!parser.getName().equals("string")) {
skip(parser);
continue;
}
JSONObject entry = parseXmlEntry(parser);
if (entry != null) {
entries.add(entry);
}
}
return entries;
}
private static byte[] toBytes(JSONArray array) throws JSONException {
byte[] bytes = new byte[array.length()];
for (int i = 0; i < array.length(); i++) {
@ -146,46 +122,4 @@ public class FreeOtpImporter extends DatabaseImporter {
}
return bytes;
}
private static JSONObject parseXmlEntry(XmlPullParser parser)
throws IOException, XmlPullParserException, JSONException {
parser.require(XmlPullParser.START_TAG, null, "string");
String name = parser.getAttributeValue(null, "name");
String value = parseXmlText(parser);
parser.require(XmlPullParser.END_TAG, null, "string");
if (name.equals("tokenOrder")) {
return null;
}
return new JSONObject(value);
}
private static String parseXmlText(XmlPullParser parser) throws IOException, XmlPullParserException {
String text = "";
if (parser.next() == XmlPullParser.TEXT) {
text = parser.getText();
parser.nextTag();
}
return text;
}
private static void skip(XmlPullParser parser) throws IOException, XmlPullParserException {
// source: https://developer.android.com/training/basics/network-ops/xml.html
if (parser.getEventType() != XmlPullParser.START_TAG) {
throw new IllegalStateException();
}
int depth = 1;
while (depth != 0) {
switch (parser.next()) {
case XmlPullParser.END_TAG:
depth--;
break;
case XmlPullParser.START_TAG:
depth++;
break;
}
}
}
}

View file

@ -104,8 +104,8 @@ public class TotpAuthenticatorImporter extends DatabaseImporter {
List<JSONObject> entries = new ArrayList<>();
for (int i = 0; i < array.length(); ++i) {
String s = array.getString(i);
entries.add(new JSONObject(s));
JSONObject obj = array.getJSONObject(i);
entries.add(obj);
}
return entries;
@ -119,7 +119,7 @@ public class TotpAuthenticatorImporter extends DatabaseImporter {
_data = data;
}
private DecryptedState decrypt(char[] password) throws DatabaseImporterException {
protected DecryptedState decrypt(char[] password) throws DatabaseImporterException {
try {
// WARNING: DON'T DO THIS IN YOUR OWN CODE
// this is not a secure way to derive a key from a password
@ -127,7 +127,7 @@ public class TotpAuthenticatorImporter extends DatabaseImporter {
byte[] keyBytes = hash.digest(CryptoUtils.toBytes(password));
SecretKey key = new SecretKeySpec(keyBytes, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec spec = new IvParameterSpec(IV);
cipher.init(Cipher.DECRYPT_MODE, key, spec);

View file

@ -34,6 +34,15 @@ public class TotpInfo extends OtpInfo {
}
}
public String getOtp(long time) {
try {
OTP otp = TOTP.generateOTP(getSecret(), getAlgorithm(true), getDigits(), getPeriod(), time);
return otp.toString();
} catch (InvalidKeyException | NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
@Override
public String getType() {
return ID;

View file

@ -619,7 +619,7 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
DatabaseImporter.State state;
try {
state = ((AegisImporter.EncryptedState) _importerState).decrypt(creds);
} catch (VaultFileException e) {
} catch (DatabaseImporterException e) {
e.printStackTrace();
Dialogs.showErrorDialog(getContext(), R.string.decryption_error, e);
return;

View file

@ -28,9 +28,13 @@ public class PasswordSlotDecryptTask extends ProgressDialogTask<PasswordSlotDecr
setPriority();
Params params = args[0];
for (PasswordSlot slot : params.getSlots()) {
return decrypt(params.getSlots(), params.getPassword());
}
public static Result decrypt(List<PasswordSlot> slots, char[] password) {
for (PasswordSlot slot : slots) {
try {
return decryptPasswordSlot(slot, params.getPassword());
return decryptPasswordSlot(slot, password);
} catch (SlotException e) {
throw new RuntimeException(e);
} catch (SlotIntegrityException ignored) {
@ -41,7 +45,7 @@ public class PasswordSlotDecryptTask extends ProgressDialogTask<PasswordSlotDecr
return null;
}
private Result decryptPasswordSlot(PasswordSlot slot, char[] password)
public static Result decryptPasswordSlot(PasswordSlot slot, char[] password)
throws SlotIntegrityException, SlotException {
MasterKey masterKey;
SecretKey key = slot.deriveKey(password);
@ -56,8 +60,6 @@ public class PasswordSlotDecryptTask extends ProgressDialogTask<PasswordSlotDecr
throw e;
}
publishProgress(getDialog().getContext().getString(R.string.unlocking_vault_repair));
// try to decrypt the password slot with the old key
SecretKey oldKey = slot.deriveKey(oldPasswordBytes);
masterKey = decryptPasswordSlot(slot, oldKey);
@ -75,7 +77,7 @@ public class PasswordSlotDecryptTask extends ProgressDialogTask<PasswordSlotDecr
return new Result(masterKey, slot, repaired);
}
private MasterKey decryptPasswordSlot(PasswordSlot slot, SecretKey key)
public static MasterKey decryptPasswordSlot(PasswordSlot slot, SecretKey key)
throws SlotException, SlotIntegrityException {
Cipher cipher = slot.createDecryptCipher(key);
return slot.getKey(cipher);

View file

@ -70,6 +70,10 @@ public class PasswordSlot extends RawSlot {
return _repaired;
}
public SCryptParameters getSCryptParameters() {
return _params;
}
@Override
public byte getType() {
return TYPE_DERIVED;

View file

@ -51,7 +51,7 @@ public abstract class Slot extends UUIDMap.Value {
public MasterKey getKey(Cipher cipher) throws SlotException, SlotIntegrityException {
try {
CryptResult res = CryptoUtils.decrypt(_encryptedMasterKey, cipher, _encryptedMasterKeyParams);
SecretKey key = new SecretKeySpec(res.getData(), CryptoUtils.CRYPTO_AEAD);
SecretKey key = new SecretKeySpec(res.getData(), "AES");
return new MasterKey(key);
} catch (BadPaddingException e) {
throw new SlotIntegrityException(e);
@ -97,6 +97,10 @@ public abstract class Slot extends UUIDMap.Value {
}
}
protected byte[] getEncryptedMasterKey() {
return _encryptedMasterKey;
}
public JSONObject toJson() {
try {
JSONObject obj = new JSONObject();
@ -130,10 +134,10 @@ public abstract class Slot extends UUIDMap.Value {
break;
case Slot.TYPE_DERIVED:
SCryptParameters scryptParams = new SCryptParameters(
obj.getInt("n"),
obj.getInt("r"),
obj.getInt("p"),
Hex.decode(obj.getString("salt"))
obj.getInt("n"),
obj.getInt("r"),
obj.getInt("p"),
Hex.decode(obj.getString("salt"))
);
boolean repaired = obj.optBoolean("repaired", false);
slot = new PasswordSlot(uuid, key, keyParams, scryptParams, repaired);

View file

@ -1,43 +0,0 @@
package com.beemdevelopment.aegis;
import com.beemdevelopment.aegis.crypto.CryptoUtils;
import com.beemdevelopment.aegis.crypto.SCryptParameters;
import com.beemdevelopment.aegis.encoding.EncodingException;
import com.beemdevelopment.aegis.encoding.Hex;
import org.junit.jupiter.api.Test;
import java.util.Arrays;
import javax.crypto.SecretKey;
import static org.junit.jupiter.api.Assertions.*;
public class SCryptTest {
@Test
public void testTrailingNullCollision() throws EncodingException {
byte[] salt = new byte[0];
SCryptParameters params = new SCryptParameters(
CryptoUtils.CRYPTO_SCRYPT_N,
CryptoUtils.CRYPTO_SCRYPT_p,
CryptoUtils.CRYPTO_SCRYPT_r,
salt
);
byte[] head = new byte[]{'t', 'e', 's', 't'};
byte[] expectedKey = Hex.decode("41cd8110d0c66ede16f97ce84fd8e2bd2269c9318532a01437789dfbadd1392e");
for (int i = 0; i < 128; i += 4) {
byte[] input = new byte[head.length + i];
System.arraycopy(head, 0, input, 0, head.length);
// once the length of the input is over 64 bytes, trailing nulls do not cause a collision anymore
SecretKey key = CryptoUtils.deriveKey(input, params);
if (input.length <= 64) {
assertArrayEquals(expectedKey, key.getEncoded());
} else {
assertFalse(Arrays.equals(expectedKey, key.getEncoded()));
}
}
}
}

View file

@ -0,0 +1,88 @@
package com.beemdevelopment.aegis.crypto;
import com.beemdevelopment.aegis.encoding.EncodingException;
import com.beemdevelopment.aegis.encoding.Hex;
import org.bouncycastle.crypto.generators.SCrypt;
import org.junit.Test;
import java.util.Arrays;
import javax.crypto.SecretKey;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertFalse;
public class SCryptTest {
private static class Vector {
private final byte[] _key;
private final char[] _password;
private final byte[] _salt;
private final int _n;
private final int _r;
private final int _p;
private final int _len;
public Vector(String key, String password, String salt, int n, int r, int p, int len) throws EncodingException {
_key = Hex.decode(key);
_password = password.toCharArray();
_salt = CryptoUtils.toBytes(salt.toCharArray());
_n = n;
_r = r;
_p = p;
_len = len;
}
public void validate() {
SCryptParameters params = new SCryptParameters(_n, _r, _p, _salt);
byte[] key = SCrypt.generate(CryptoUtils.toBytes(_password), params.getSalt(), params.getN(), params.getR(), params.getP(), _len);
assertArrayEquals(_key, key);
}
}
@Test
public void vectorsMatch() throws EncodingException {
// https://tools.ietf.org/html/rfc7914.html#section-12
final Vector[] vectors = new Vector[]{
new Vector("77d6576238657b203b19ca42c18a0497f16b4844e3074ae8dfdffa3fede21442fcd0069ded0948f8326a753a0fc81f17e8d3e0fb2e0d3628cf35e20c38d18906",
"", "", 1 << 4, 1, 1, 64),
new Vector("fdbabe1c9d3472007856e7190d01e9fe7c6ad7cbc8237830e77376634b3731622eaf30d92e22a3886ff109279d9830dac727afb94a83ee6d8360cbdfa2cc0640",
"password", "NaCl", 1 << 10, 8, 16, 64),
new Vector("7023bdcb3afd7348461c06cd81fd38ebfda8fbba904f8e3ea9b543f6545da1f2d5432955613f0fcf62d49705242a9af9e61e85dc0d651e40dfcf017b45575887",
"pleaseletmein", "SodiumChloride", 1 << 14, 8, 1, 64),
new Vector("2101cb9b6a511aaeaddbbe09cf70f881ec568d574a2ffd4dabe5ee9820adaa478e56fd8f4ba5d09ffa1c6d927c40f4c337304049e8a952fbcbf45c6fa77a41a4",
"pleaseletmein", "SodiumChloride", 1 << 20, 8, 1, 64)
};
for (Vector vector : vectors) {
vector.validate();
}
}
@Test
public void testTrailingNullCollision() throws EncodingException {
byte[] salt = new byte[0];
SCryptParameters params = new SCryptParameters(
CryptoUtils.CRYPTO_SCRYPT_N,
CryptoUtils.CRYPTO_SCRYPT_p,
CryptoUtils.CRYPTO_SCRYPT_r,
salt
);
byte[] head = new byte[]{'t', 'e', 's', 't'};
byte[] expectedKey = Hex.decode("41cd8110d0c66ede16f97ce84fd8e2bd2269c9318532a01437789dfbadd1392e");
for (int i = 0; i < 128; i += 4) {
byte[] input = new byte[head.length + i];
System.arraycopy(head, 0, input, 0, head.length);
// once the length of the input is over 64 bytes, trailing nulls do not cause a collision anymore
SecretKey key = CryptoUtils.deriveKey(input, params);
if (input.length <= 64) {
assertArrayEquals(expectedKey, key.getEncoded());
} else {
assertFalse(Arrays.equals(expectedKey, key.getEncoded()));
}
}
}
}

View file

@ -1,18 +1,15 @@
package com.beemdevelopment.aegis;
package com.beemdevelopment.aegis.crypto.otp;
import com.beemdevelopment.aegis.crypto.otp.HOTP;
import com.beemdevelopment.aegis.crypto.otp.OTP;
import org.junit.jupiter.api.Test;
import org.junit.Test;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.Assert.assertEquals;
public class HOTPTest {
// https://tools.ietf.org/html/rfc4226#page-32
private final String[] _vectors = {
public static final String[] VECTORS = {
"755224", "287082",
"359152", "969429",
"338314", "254676",
@ -20,16 +17,16 @@ public class HOTPTest {
"399871", "520489"
};
private final byte[] _secret = new byte[]{
public static final byte[] SECRET = new byte[]{
0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30,
0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30
};
@Test
public void vectorsMatch() throws InvalidKeyException, NoSuchAlgorithmException {
for (int i = 0; i < _vectors.length; i++) {
OTP otp = HOTP.generateOTP(_secret, "HmacSHA1", 6, i);
assertEquals(_vectors[i], otp.toString());
for (int i = 0; i < VECTORS.length; i++) {
OTP otp = HOTP.generateOTP(SECRET, "HmacSHA1", 6, i);
assertEquals(VECTORS[i], otp.toString());
}
}
}

View file

@ -1,18 +1,14 @@
package com.beemdevelopment.aegis;
package com.beemdevelopment.aegis.crypto.otp;
import com.beemdevelopment.aegis.crypto.otp.OTP;
import com.beemdevelopment.aegis.crypto.otp.TOTP;
import org.junit.jupiter.api.Test;
import org.junit.Test;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.Assert.assertEquals;
public class TOTPTest {
private static class Vector {
public static class Vector {
public long Time;
public String OTP;
public String Algo;
@ -25,7 +21,7 @@ public class TOTPTest {
}
// https://tools.ietf.org/html/rfc6238#appendix-B
private final Vector[] _vectors = {
public static final Vector[] VECTORS = {
new Vector(59, "94287082", "HmacSHA1"),
new Vector(59, "46119246", "HmacSHA256"),
new Vector(59, "90693936", "HmacSHA512"),
@ -46,19 +42,19 @@ public class TOTPTest {
new Vector(20000000000L, "47863826", "HmacSHA512")
};
private final byte[] _seed = new byte[]{
private static final byte[] SEED = new byte[]{
0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30,
0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30
};
private final byte[] _seed32 = new byte[]{
private static final byte[] SEED32 = new byte[]{
0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,
0x39, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36,
0x37, 0x38, 0x39, 0x30, 0x31, 0x32, 0x33, 0x34,
0x35, 0x36, 0x37, 0x38, 0x39, 0x30, 0x31, 0x32
};
private final byte[] _seed64 = new byte[]{
private static final byte[] SEED64 = new byte[]{
0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36,
0x37, 0x38, 0x39, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30, 0x31, 0x32,
0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,
@ -67,26 +63,23 @@ public class TOTPTest {
@Test
public void vectorsMatch() throws NoSuchAlgorithmException, InvalidKeyException {
for (Vector vector : _vectors) {
byte[] seed;
switch (vector.Algo) {
case "HmacSHA1":
seed = _seed;
break;
case "HmacSHA256":
seed = _seed32;
break;
case "HmacSHA512":
seed = _seed64;
break;
default:
fail("unsupported mode");
return;
}
for (Vector vector : VECTORS) {
byte[] seed = getSeed(vector.Algo);
OTP otp = TOTP.generateOTP(seed, vector.Algo, 8, 30, vector.Time);
assertEquals(vector.OTP, otp.toString());
}
}
public static byte[] getSeed(String algorithm) {
switch (algorithm) {
case "HmacSHA1":
return SEED;
case "HmacSHA256":
return SEED32;
case "HmacSHA512":
return SEED64;
default:
throw new RuntimeException(String.format("Unsupported algorithm: %s", algorithm));
}
}
}

View file

@ -0,0 +1,331 @@
package com.beemdevelopment.aegis.importers;
import android.content.Context;
import android.os.Build;
import androidx.test.core.app.ApplicationProvider;
import com.beemdevelopment.aegis.encoding.Base32;
import com.beemdevelopment.aegis.encoding.EncodingException;
import com.beemdevelopment.aegis.otp.HotpInfo;
import com.beemdevelopment.aegis.otp.OtpInfoException;
import com.beemdevelopment.aegis.otp.SteamInfo;
import com.beemdevelopment.aegis.otp.TotpInfo;
import com.beemdevelopment.aegis.util.UUIDMap;
import com.beemdevelopment.aegis.vault.VaultEntry;
import com.google.common.collect.Lists;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@Config(sdk = { Build.VERSION_CODES.P })
@RunWith(RobolectricTestRunner.class)
public class DatabaseImporterTest {
private List<VaultEntry> _vectors;
/**
* The procedure for adding new importer tests is as follows:
* 1. Generate QR codes for each test vector:
* -> while read line; do (qrencode "$line" -o - | feh -); done < ./app/src/test/resources/com/beemdevelopment/aegis/importers/plain
* 2. Scan the QR codes with the app we want to test our import functionality of
* 3. Create an export and add the file to the importers resource directory.
* 4. Add a new test for it here.
*/
@Before
public void initVectors() throws EncodingException, OtpInfoException {
_vectors = Lists.newArrayList(
new VaultEntry(new TotpInfo(Base32.decode("4SJHB4GSD43FZBAI7C2HLRJGPQ")), "Mason", "Deno"),
new VaultEntry(new TotpInfo(Base32.decode("5OM4WOOGPLQEF6UGN3CPEOOLWU"), "SHA256", 7, 20), "James", "SPDX"),
new VaultEntry(new TotpInfo(Base32.decode("7ELGJSGXNCCTV3O6LKJWYFV2RA"), "SHA512", 8, 50), "Elijah", "Airbnb"),
new VaultEntry(new HotpInfo(Base32.decode("YOOMIXWS5GN6RTBPUFFWKTW5M4"), "SHA1", 6, 1), "James", "Issuu"),
new VaultEntry(new HotpInfo(Base32.decode("KUVJJOM753IHTNDSZVCNKL7GII"), "SHA256", 7, 50), "Benjamin", "Air Canada"),
new VaultEntry(new HotpInfo(Base32.decode("5VAML3X35THCEBVRLV24CGBKOY"), "SHA512", 8, 10300), "Mason", "WWE"),
new VaultEntry(new SteamInfo(Base32.decode("JRZCL47CMXVOQMNPZR2F7J4RGI"), "SHA1", 5, 30), "Sophia", "Boeing")
);
}
@Test
public void testImportPlainText() throws IOException, DatabaseImporterException {
List<VaultEntry> entries = importPlain(GoogleAuthUriImporter.class, "plain.txt");
checkImportedEntries(entries);
}
@Test
public void testImportAegisPlain() throws IOException, DatabaseImporterException {
List<VaultEntry> entries = importPlain(AegisImporter.class, "aegis_plain.json");
checkImportedEntries(entries);
}
@Test
public void testImportAegisEncrypted() throws IOException, DatabaseImporterException {
List<VaultEntry> entries = importEncrypted(AegisImporter.class, "aegis_encrypted.json", encryptedState -> {
final char[] password = "test".toCharArray();
return ((AegisImporter.EncryptedState) encryptedState).decrypt(password);
});
checkImportedEntries(entries);
}
@Test
public void testImportWinAuth() throws IOException, DatabaseImporterException {
List<VaultEntry> entries = importPlain(WinAuthImporter.class, "plain.txt");
for (VaultEntry entry : entries) {
VaultEntry entryVector = getEntryVectorBySecret(entry.getInfo().getSecret());
entryVector.setIssuer(entryVector.getName());
entryVector.setName("WinAuth");
checkImportedEntry(entryVector, entry);
}
}
@Test
public void testImportAndOTP() throws IOException, DatabaseImporterException {
List<VaultEntry> entries = importPlain(AndOtpImporter.class, "andotp_plain.json");
checkImportedEntries(entries);
}
@Test
public void testImportAndOTPEncrypted() throws IOException, DatabaseImporterException {
List<VaultEntry> entries = importEncrypted(AndOtpImporter.class, "andotp_encrypted.bin", encryptedState -> {
final char[] password = "test".toCharArray();
return ((AndOtpImporter.EncryptedState) encryptedState).decryptNewFormat(password);
});
checkImportedEntries(entries);
}
@Test
public void testImportAndOTPEncryptedOld() throws IOException, DatabaseImporterException {
List<VaultEntry> entries = importEncrypted(AndOtpImporter.class, "andotp_encrypted_old.bin", encryptedState -> {
final char[] password = "test".toCharArray();
return ((AndOtpImporter.EncryptedState) encryptedState).decryptOldFormat(password);
});
for (VaultEntry entry : entries) {
// old versions of andOTP have a bug where the issuer/name is not parsed correctly, so account for that here
VaultEntry entryVector = getEntryVectorBySecret(entry.getInfo().getSecret());
entryVector.setName(String.format("%s:%s", entryVector.getIssuer(), entryVector.getName()));
checkImportedEntry(entryVector, entry);
}
}
@Test
public void testImportTotpAuthenticator() throws IOException, DatabaseImporterException, OtpInfoException {
List<VaultEntry> entries = importEncrypted(TotpAuthenticatorImporter.class, "totp_authenticator.bin", encryptedState -> {
final char[] password = "Testtest1".toCharArray();
return ((TotpAuthenticatorImporter.EncryptedState) encryptedState).decrypt(password);
});
checkImportedTotpAuthenticatorEntries(entries);
}
@Test
public void testImportTotpAuthenticatorInternal() throws IOException, DatabaseImporterException, OtpInfoException {
List<VaultEntry> entries = importPlain(TotpAuthenticatorImporter.class, "totp_authenticator_internal.xml", true);
checkImportedTotpAuthenticatorEntries(entries);
}
@Test
public void testImportAuthy() throws IOException, DatabaseImporterException, OtpInfoException {
List<VaultEntry> entries = importPlain(AuthyImporter.class, "authy_plain.xml");
checkImportedAuthyEntries(entries);
}
@Test
public void testImportAuthyEncrypted() throws IOException, DatabaseImporterException, OtpInfoException {
List<VaultEntry> entries = importEncrypted(AuthyImporter.class, "authy_encrypted.xml", encryptedState -> {
final char[] password = "testtest".toCharArray();
return ((AuthyImporter.EncryptedState) encryptedState).decrypt(password);
});
checkImportedAuthyEntries(entries);
}
@Test
public void testImportFreeOtp() throws IOException, DatabaseImporterException, OtpInfoException {
List<VaultEntry> entries = importPlain(FreeOtpImporter.class, "freeotp.xml");
checkImportedFreeOtpEntries(entries);
}
@Test
public void testImportFreeOtpPlus() throws IOException, DatabaseImporterException, OtpInfoException {
List<VaultEntry> entries = importPlain(FreeOtpPlusImporter.class, "freeotp_plus.json");
checkImportedFreeOtpEntries(entries);
}
@Test
public void testImportFreeOtpPlusInternal() throws IOException, DatabaseImporterException, OtpInfoException {
List<VaultEntry> entries = importPlain(FreeOtpPlusImporter.class, "freeotp_plus_internal.xml", true);
checkImportedFreeOtpEntries(entries);
}
@Test
public void testImportGoogleAuthenticator() throws IOException, DatabaseImporterException, OtpInfoException {
List<VaultEntry> entries = importPlain(GoogleAuthImporter.class, "google_authenticator.sqlite");
for (VaultEntry entry : entries) {
// Google Authenticator doesn't support different hash algorithms, periods or digits, so fix those up here
VaultEntry entryVector = getEntryVectorBySecret(entry.getInfo().getSecret());
entryVector.getInfo().setDigits(6);
if (entryVector.getInfo() instanceof TotpInfo) {
((TotpInfo) entryVector.getInfo()).setPeriod(30);
}
entryVector.getInfo().setAlgorithm("SHA1");
checkImportedEntry(entryVector, entry);
}
}
@Test
public void testImportMicrosoftAuthenticator() throws IOException, DatabaseImporterException, OtpInfoException {
List<VaultEntry> entries = importPlain(MicrosoftAuthImporter.class, "microsoft_authenticator.sqlite");
for (VaultEntry entry : entries) {
// Microsoft Authenticator doesn't support HOTP, different hash algorithms, periods or digits, so fix those up here
VaultEntry entryVector = getEntryVectorBySecret(entry.getInfo().getSecret());
entryVector.setInfo(new TotpInfo(entryVector.getInfo().getSecret()));
checkImportedEntry(entryVector, entry);
}
}
@Test
public void testImportSteam() throws IOException, DatabaseImporterException {
List<VaultEntry> entries = importPlain(SteamImporter.class, "steam.json");
for (VaultEntry entry : entries) {
VaultEntry entryVector = getEntryVectorBySecret(entry.getInfo().getSecret());
entryVector.setIssuer("Steam");
checkImportedEntry(entryVector, entry);
}
}
@Test
public void testImportAuthenticatorPlus() throws IOException, DatabaseImporterException, OtpInfoException {
List<VaultEntry> entries = importEncrypted(AuthenticatorPlusImporter.class, "authenticator_plus.zip", encryptedState -> {
final char[] password = "testtesttest".toCharArray();
return ((AuthenticatorPlusImporter.EncryptedState) encryptedState).decrypt(password);
});
checkImportedEntries(entries);
}
private List<VaultEntry> importPlain(Class<? extends DatabaseImporter> type, String resName)
throws IOException, DatabaseImporterException {
return importPlain(type, resName, false);
}
private List<VaultEntry> importPlain(Class<? extends DatabaseImporter> type, String resName, boolean isInternal)
throws IOException, DatabaseImporterException {
Context context = ApplicationProvider.getApplicationContext();
DatabaseImporter importer = DatabaseImporter.create(context, type);
try (InputStream stream = openResource(resName)) {
DatabaseImporter.State state = importer.read(stream, isInternal);
assertFalse(state.isEncrypted());
DatabaseImporter.Result result = state.convert();
return Lists.newArrayList(getEntries(result));
}
}
private List<VaultEntry> importEncrypted(Class<? extends DatabaseImporter> type, String resName, Decryptor decryptor)
throws IOException, DatabaseImporterException {
return importEncrypted(type, resName, false, decryptor);
}
private List<VaultEntry> importEncrypted(Class<? extends DatabaseImporter> type, String resName, boolean isInternal, Decryptor decryptor)
throws IOException, DatabaseImporterException {
Context context = ApplicationProvider.getApplicationContext();
DatabaseImporter importer = DatabaseImporter.create(context, type);
try (InputStream stream = openResource(resName)) {
DatabaseImporter.State state = importer.read(stream, isInternal);
assertTrue(state.isEncrypted());
DatabaseImporter.Result result = decryptor.decrypt(state).convert();
return Lists.newArrayList(getEntries(result));
}
}
private static UUIDMap<VaultEntry> getEntries(DatabaseImporter.Result result) {
for (DatabaseImporterEntryException e : result.getErrors()) {
fail(e.toString());
}
return result.getEntries();
}
private void checkImportedAuthyEntries(List<VaultEntry> entries) throws OtpInfoException {
for (VaultEntry entry : entries) {
// Authy doesn't support different hash algorithms or periods, so fix those up here
VaultEntry entryVector = getEntryVectorBySecret(entry.getInfo().getSecret());
entryVector.getInfo().setAlgorithm("SHA1");
((TotpInfo) entry.getInfo()).setPeriod(((TotpInfo) entryVector.getInfo()).getPeriod());
checkImportedEntry(entryVector, entry);
}
}
private void checkImportedTotpAuthenticatorEntries(List<VaultEntry> entries) throws OtpInfoException {
for (VaultEntry entry : entries) {
// TOTP Authenticator doesn't support different hash algorithms, periods or digits, so fix those up here
VaultEntry entryVector = getEntryVectorBySecret(entry.getInfo().getSecret());
entryVector.getInfo().setDigits(6);
((TotpInfo) entryVector.getInfo()).setPeriod(30);
entryVector.getInfo().setAlgorithm("SHA1");
entryVector.setName(entryVector.getName().toLowerCase());
checkImportedEntry(entryVector, entry);
}
}
private void checkImportedFreeOtpEntries(List<VaultEntry> entries) throws OtpInfoException {
for (VaultEntry entry : entries) {
// for some reason, FreeOTP adds -1 to the counter
VaultEntry entryVector = getEntryVectorBySecret(entry.getInfo().getSecret());
if (entryVector.getInfo() instanceof HotpInfo) {
((HotpInfo) entryVector.getInfo()).setCounter(((HotpInfo) entryVector.getInfo()).getCounter() - 1);
}
checkImportedEntry(entryVector, entry);
}
}
private void checkImportedEntries(List<VaultEntry> entries) {
for (VaultEntry entry : entries) {
checkImportedEntry(entry);
}
}
private void checkImportedEntry(VaultEntry entry) {
VaultEntry entryVector = getEntryVectorBySecret(entry.getInfo().getSecret());
checkImportedEntry(entryVector, entry);
}
private void checkImportedEntry(VaultEntry entryVector, VaultEntry entry) {
String message = String.format("Entries are not equivalent: (%s) (%s)", entryVector.toJson().toString(), entry.toJson().toString());
assertTrue(message, entryVector.equivalates(entry));
assertEquals(message, entryVector.getInfo().getOtp(), entry.getInfo().getOtp());
}
private VaultEntry getEntryVectorBySecret(byte[] secret) {
for (VaultEntry entry : _vectors) {
if (Arrays.equals(entry.getInfo().getSecret(), secret)) {
return entry;
}
}
fail(String.format("No entry found for secret: %s", Base32.encode(secret)));
return null;
}
private InputStream openResource(String name) {
return getClass().getResourceAsStream(name);
}
private interface Decryptor {
DatabaseImporter.State decrypt(DatabaseImporter.State encryptedState) throws DatabaseImporterException;
}
}

View file

@ -0,0 +1,27 @@
package com.beemdevelopment.aegis.otp;
import com.beemdevelopment.aegis.crypto.otp.HOTPTest;
import com.beemdevelopment.aegis.crypto.otp.TOTPTest;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class OtpTest {
@Test
public void testHotpInfoOtp() throws OtpInfoException {
for (int i = 0; i < HOTPTest.VECTORS.length; i++) {
HotpInfo info = new HotpInfo(HOTPTest.SECRET, "SHA1", 6, i);
assertEquals(HOTPTest.VECTORS[i], info.getOtp());
}
}
@Test
public void testTotpInfoOtp() throws OtpInfoException {
for (TOTPTest.Vector vector : TOTPTest.VECTORS) {
byte[] seed = TOTPTest.getSeed(vector.Algo);
TotpInfo info = new TotpInfo(seed, vector.Algo, 8, 30);
assertEquals(vector.OTP, info.getOtp(vector.Time));
}
}
}

View file

@ -1,22 +1,25 @@
package com.beemdevelopment.aegis;
package com.beemdevelopment.aegis.util;
import com.beemdevelopment.aegis.util.Cloner;
import com.beemdevelopment.aegis.util.UUIDMap;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.BeforeEach;
import org.junit.Before;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
public class UUIDMapTest {
private UUIDMap<Value> _map;
@BeforeEach
@Before
public void init() {
_map = new UUIDMap<>();
}
@ -87,7 +90,7 @@ public class UUIDMapTest {
// swap the values and see if the lists are equal now
_map.swap(value1, value4);
_map.swap(value2, value3);
assertIterableEquals(values, ref);
assertArrayEquals(values.toArray(), ref.toArray());
}
private Value addNewValue() {

View file

@ -1,15 +1,11 @@
package com.beemdevelopment.aegis;
package com.beemdevelopment.aegis.vault.slots;
import com.beemdevelopment.aegis.crypto.CryptoUtils;
import com.beemdevelopment.aegis.crypto.MasterKey;
import com.beemdevelopment.aegis.crypto.SCryptParameters;
import com.beemdevelopment.aegis.vault.slots.PasswordSlot;
import com.beemdevelopment.aegis.vault.slots.RawSlot;
import com.beemdevelopment.aegis.vault.slots.SlotException;
import com.beemdevelopment.aegis.vault.slots.SlotIntegrityException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.Before;
import org.junit.Test;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
@ -20,13 +16,13 @@ import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertThrows;
public class SlotTest {
private MasterKey _masterKey;
@BeforeEach
@Before
public void init() {
_masterKey = MasterKey.generate();
}
@ -75,18 +71,26 @@ public class SlotTest {
public void testSlotIntegrity() throws
InvalidAlgorithmParameterException, NoSuchAlgorithmException,
InvalidKeyException, NoSuchPaddingException,
SlotException {
SlotException, SlotIntegrityException {
RawSlot slot = new RawSlot();
SecretKey rawKey = CryptoUtils.generateKey();
Cipher cipher = CryptoUtils.createEncryptCipher(rawKey);
slot.setKey(_masterKey, cipher);
// garble the first byte of the key
byte[] rawKeyBytes = rawKey.getEncoded();
rawKeyBytes[0] = (byte) ~rawKeyBytes[0];
rawKey = new SecretKeySpec(rawKeyBytes, "AES");
// try to decrypt with good key/ciphertext first
final Cipher decryptCipher = slot.createDecryptCipher(rawKey);
slot.getKey(decryptCipher);
Cipher decryptCipher = slot.createDecryptCipher(rawKey);
// garble the first byte of the key and try to decrypt
byte[] garbledKeyBytes = rawKey.getEncoded();
garbledKeyBytes[0] = (byte) ~garbledKeyBytes[0];
SecretKey garbledKey = new SecretKeySpec(garbledKeyBytes, "AES");
final Cipher garbledDecryptCipher = slot.createDecryptCipher(garbledKey);
assertThrows(SlotIntegrityException.class, () -> slot.getKey(garbledDecryptCipher));
// garble the first byte of the ciphertext and try to decrypt
byte[] garbledCiphertext = slot.getEncryptedMasterKey();
garbledCiphertext[0] = (byte) ~garbledCiphertext[0];
assertThrows(SlotIntegrityException.class, () -> slot.getKey(decryptCipher));
}
}

View file

@ -0,0 +1,26 @@
{
"version": 1,
"header": {
"slots": [
{
"type": 1,
"uuid": "a8325752-c1be-458a-9b3e-5e0a8154d9ec",
"key": "491d44550430ba248986b904b8cffd3a6c5755d176ac877bd11b82c934225017",
"key_params": {
"nonce": "e9705513ba4951fa7a0608d2",
"tag": "931237af257b83c693ddb8f9a7eddaf0"
},
"n": 32768,
"r": 8,
"p": 1,
"salt": "27ea9ae53fa2f08a8dcd201615a8229422647b3058f9f36b08f9457e62888be1",
"repaired": true
}
],
"params": {
"nonce": "095fd13dee336fa56b4634ff",
"tag": "5db2470edf2d12f82a89ae7f48ccd50c"
}
},
"db": "RtGfUrZ01nzRnvHjPJGyWjfa6shQ7NYwa491CgAWNBM8OeGZVIHhnDAVlVWNlSoq2V097p5Yq5m+SFl5g9nBBBQBNePQnj6CCvu1NfNtoA6R3hyp77gd+e+O2MRnOGH1Z1laV2Tl6p3q8IUHWgAJ36LbUxiCXmfh7bWm198uA4bgLwrEmo04MrqeYXggLuXrJrp6dUJQFD72dgoPbHijlSycY5GLel3ZbAXRsUHszd+xdywpj7\/TYa4OYFel0M0QcCpsKA1LRQz365X9OXPJdTsmVyR4dJ6x5RIVeh39lAYKUf7T4w7BLC8taST5m4J\/VXDueKbvg8R13bNWF0aRHUgeuI9BNzMZINJlzKFKNRknTaJ\/1kEUU0sLkgcaVkX\/DVTGG+pWi5MHijicrK0i4LHN3CUwV2\/\/ZNJCGXM5ErsKMOnJfma52gMdifPiXU317Klvc5oOZFYGnhbhJ2WtPIuqjdvnfuLat2JxA7Xx3LqquRWGL2113yjzVzGBDCVY6iIdedBEgH8CGD826\/3R3m6dR5sfSggQ2SbtQA\/DZNhLSNSU+bfNScVQvUWfR2Lf7Q\/4FR\/xATAQJ9IIBeL+w2ErLUPjURocFXup5YOBHxFdDjZ2FqhbAq4h3Zn\/BJ57xUcYEA+YtP5uOP2lQwUh\/0vFWizDVotzraO8tZiBZBsODyb69eJrXNwFbIjeUczY6wrJs1+676IilbCsmtoYvWEpUZF4hIi7TYAD+nyXX\/olrkog9omWZk8R7hJ9KRDfckXEc\/XSzWhk3Kmfa7pRNh9wYZsaR7VPZGZebQMuUKfRRci2qMsZOJvQsDBJvVze0xW9SqiySDgGyRX\/DwzuaZEGZZriaLf6ox7LwY2Qi6QpYOYbAaEaXAesCR1DPxFfGKsUHVjF8hKA6ZBXDXdqM3Y+14naIOH9S7UzYn32botoVLOykSjnW6z6M0ZPkz3dwowMJiVQcyD7p+9p4J6f1S81pFS7DP+jF+PTyC3c3q\/dwFhNdoG6iV9eQEAxjUi6MpzvFRsk9RsLcQqYgzJGmRjYeXlKH8k8tTu1A4puo6w3Daz8hZz9NafMgMsuqY0oKVLgdNqFz8yVMsxYfBW\/oW56SuQyyVWyxXjXmbk1vpYCTL5kXvIZWoTmBRRDb0ay5S\/dlD6z\/WR45\/C4AwcCE9m4Yf3zisRNa7AqWLVgkmJxFdfJxjiuPtUIK79s+lIJkyRENEqkvm809qIxDhkQzY8zcCt4oXCEbJUfSG4awBs1VvilJIwe6qi0bNtqXtAb5TctgxTh29A9oGlsRG4o8sHqA1mtjp5QiLWp5Hh6rOH95W6+fnBiOW+Iw0evBTduroWvx37HBTktJz79zGe0l3c0Y6VmiFvB7knmT2CrgP7woRkxGbXxdE9zMPQJM9ursD538MVDdD\/0tdkxHxilt47f1DPo2CKUWU8Q1KMm1zLXfVO8BbGUWIv4YeDKHfMUL\/HcStv5VJY+LbnOEjzGT4e1\/avSQmqBL4G9XNkYmyMhC8tlLQcmMMH4bNfPOO3vi5Pb5E7XveSgxlOHs4F0+nqxnFOAu8494MEtx6u5+B7d8LI\/DhEO5zTDwE+THiKej6vCsFxTZ519rm67HycOwRR4LKrwfDeUEK3X1PzryOD5zcv3PMcSBgZ8EWvTfZ9ygKP8BmRQRpydTbSt8Hj5fTUuajADCP0Ggw+6G7n+5FhExJNd+o9D8d4KgLPOe08M8InW7pLB389TWtSo4v3VNjcmmJNQ26wlPkhO\/xBU1URFR0fXU3eCO+w++IMt\/fOSqSpNF9bWElfWHIQ23ntxVke\/hR9j\/GG3tHGxYS5pL42sJF\/Re\/UlUJTGSQP6up2xVYs6gncQ0zACDOPjLQmQzYhz\/hr8S6EjYfK++yLZmRTjEI7xT9u\/B5YLyOQCYVTaF\/pDEegjsehXM3qJBfsA+XY7F9TRsmM\/MSVaPDkdIJ7zvL9xtaF6bXdZoZ6po3ml8uu41pSkNmMKgyEy5E0UQUTWMPLC8drUoQ\/KWQnVIN6HUXGBjYy6aax\/LYZaBcbZi97FHK0h+wsx3WN\/uQozNkQjwGYE8fwYxRYh1RaFi5PkiCM505ib7e82Yuts0l+cBb6nG1IruDplg9BD\/G9w4vVDePEikhcPyY\/p7AZ4i7u\/bL2YKlbE3HyJa+7dkbWJgGidtRZgu+Fdl2T\/rrRJ4+lVaKPVKGKT7ItZdIeitIYUdRxCzrOf1ItZCC8BWa4PElDAjj2yDNmMYRpXJBe3gQHWs\/H5SZgFuwsfCu23uzNRQYib8SuwIJQDvPiXo7m4oIySO8VyvemcExlbXSlbZbvwVxYavTVfcUpAXI6qlsg2jjk+JZahfKrWNC5COZPdVjdAXCoiKU+HBPmEFCwQv\/7zlSBEiI2piyqd+MPwnP63RdGO+oXYid6hn4Nm8kcOhtRyvYm95p66jzGlEugsfxJCED7MTh3XShqa2tt4lFG25icllzTvIJboRkz5oIB4dZVS9+q2TgGUoX7UCpobD8WkHo\/y0cpTuZr8vzXqx2fObxzPNoVgxJmp9E06G2bhMVHPpT17xbfq\/KhJJn7k1S0sfXPG+SmYlX4U7zNSe1M7JXtLf3uVOLz7Ccjp3yvcdq8nRmVym3Zwsz+vv57FA2A0dy3Db97ypJa9HGaxnnYIZHHzep0gJCeeIKE9L32zGCoUg+cPu9B2lPEgIr64iGiuvKSRwNQpOBktM6qqjQntE0Me6mh426irFQ\/3tcfH9a4lZEwwuU1X+lUBUWQp3n5Ej4BSJEs8E6H0EjBvyk69q3qjy5yi7ROVRis6y6S1v4er77RHQUf3phK5354VJHrp9pR926t5qngH5RVF4eljwtXDs3MkejADJ6stBHa\/w7FcbUClO8U+S4Bidxb3mZCiZkUVTpbzvBfYAiQvAfdkMa49o3a5DXKsbXyUPrmr6fWRfM1fS0Ehp0lUv6BDj0yR13CLMpKDU4GfDrl8UEvwh7gwtBRkuaBFzyMtd3NeE7kIGf9vFs6MEl2dmMDFSDid7MdVSDVTlhaAtp+zsRejKW3OQr5n051FzkUsIFGty9AWOkwjZCbstHYCOtyJnsnXP1i9lRDFBgPpFgmDD+bzzg0g9AOAxzqTiLF7bb1jejfe5qVr5V9+7zLpwRLiYaLkNOmpsqvNMuYVwdqTp6nyoougdgBlvve3EG0k09sFKi2Ep9lq+QkS7zGre2jJDrqgdC08+V4PXHYkP3V3Zjgn1x6RfQ2PE+2zvk1GGEgzcNww3byoYw0Ra5qS5yftMy\/2WahbA8fjUYvtmksFH8VjN3yasZt3sdQLWtv8qXxZscy+pCyjTdyxW+ddFnrWuqMIV3jbGMvngq6dL\/n5+DumjbA1gmBJVOpmyEsc1iwHDS36cNnyi1htGFO\/6\/Va4YPYK7dG6LY387UoBUU9Q9ijrBrSGpzPWYmXBLZ8e1MMPfHIN1WsaTgYO9leg3MAJTjQFTFrQ5dguYpWhlm2sWJT45jrda4uWqduB+aQLzYRWhEDBFzPV3ZgIe0SB+7h04Vm0Pu\/LDRvqaolpZ86CEm+zgjBOKeEGFwzTXxH\/5pBoca1bZ6wvsbVZxJNBeH8\/w=="
}

View file

@ -0,0 +1,103 @@
{
"version": 1,
"header": {
"slots": null,
"params": null
},
"db": {
"version": 1,
"entries": [
{
"type": "totp",
"uuid": "3ae6f1ad-2e65-4ed2-a953-1ec0dff2386d",
"name": "Mason",
"issuer": "Deno",
"icon": null,
"info": {
"secret": "4SJHB4GSD43FZBAI7C2HLRJGPQ",
"algo": "SHA1",
"digits": 6,
"period": 30
}
},
{
"type": "totp",
"uuid": "84b55971-a3d2-4173-a5bb-0aea113dbc17",
"name": "James",
"issuer": "SPDX",
"icon": null,
"info": {
"secret": "5OM4WOOGPLQEF6UGN3CPEOOLWU",
"algo": "SHA256",
"digits": 7,
"period": 20
}
},
{
"type": "totp",
"uuid": "3deaff2e-f181-4837-80e1-fdf0c54e9363",
"name": "Elijah",
"issuer": "Airbnb",
"icon": null,
"info": {
"secret": "7ELGJSGXNCCTV3O6LKJWYFV2RA",
"algo": "SHA512",
"digits": 8,
"period": 50
}
},
{
"type": "hotp",
"uuid": "0a8c0571-ff6f-4b02-aa4b-50553b4fb4fe",
"name": "James",
"issuer": "Issuu",
"icon": null,
"info": {
"secret": "YOOMIXWS5GN6RTBPUFFWKTW5M4",
"algo": "SHA1",
"digits": 6,
"counter": 1
}
},
{
"type": "hotp",
"uuid": "03e572f2-8ebd-44b0-a57e-e958af74815d",
"name": "Benjamin",
"issuer": "Air Canada",
"icon": null,
"info": {
"secret": "KUVJJOM753IHTNDSZVCNKL7GII",
"algo": "SHA256",
"digits": 7,
"counter": 50
}
},
{
"type": "hotp",
"uuid": "b25f8815-007f-40f7-a700-ce058ac05435",
"name": "Mason",
"issuer": "WWE",
"icon": null,
"info": {
"secret": "5VAML3X35THCEBVRLV24CGBKOY",
"algo": "SHA512",
"digits": 8,
"counter": 10300
}
},
{
"type": "steam",
"uuid": "5b11ae3b-6fc3-4d46-8ca7-cf0aea7de920",
"name": "Sophia",
"issuer": "Boeing",
"icon": null,
"info": {
"secret": "JRZCL47CMXVOQMNPZR2F7J4RGI",
"algo": "SHA1",
"digits": 5,
"period": 30
}
}
]
}
}

View file

@ -0,0 +1 @@
[{"secret":"4SJHB4GSD43FZBAI7C2HLRJGPQ======","issuer":"Deno","label":"Mason","digits":6,"type":"TOTP","algorithm":"SHA1","thumbnail":"Default","last_used":1608146844162,"used_frequency":0,"period":30,"tags":[]},{"secret":"5OM4WOOGPLQEF6UGN3CPEOOLWU======","issuer":"SPDX","label":"James","digits":7,"type":"TOTP","algorithm":"SHA256","thumbnail":"Default","last_used":1608146848740,"used_frequency":0,"period":20,"tags":[]},{"secret":"7ELGJSGXNCCTV3O6LKJWYFV2RA======","issuer":"Airbnb","label":"Elijah","digits":8,"type":"TOTP","algorithm":"SHA512","thumbnail":"AirBNB","last_used":1608146856099,"used_frequency":0,"period":50,"tags":[]},{"secret":"YOOMIXWS5GN6RTBPUFFWKTW5M4======","issuer":"Issuu","label":"James","digits":6,"type":"HOTP","algorithm":"SHA1","thumbnail":"Default","last_used":1608146859856,"used_frequency":0,"counter":1,"tags":[]},{"secret":"KUVJJOM753IHTNDSZVCNKL7GII======","issuer":"Air Canada","label":"Benjamin","digits":7,"type":"HOTP","algorithm":"SHA256","thumbnail":"Default","last_used":1608146866733,"used_frequency":0,"counter":50,"tags":[]},{"secret":"5VAML3X35THCEBVRLV24CGBKOY======","issuer":"WWE","label":"Mason","digits":8,"type":"HOTP","algorithm":"SHA512","thumbnail":"Default","last_used":1608146871644,"used_frequency":0,"counter":10300,"tags":[]},{"secret":"JRZCL47CMXVOQMNPZR2F7J4RGI======","issuer":"Boeing","label":"Sophia","digits":5,"type":"STEAM","algorithm":"SHA1","thumbnail":"Default","last_used":1608146877325,"used_frequency":0,"tags":[]}]

View file

@ -0,0 +1,5 @@
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
<int name="key_version" value="121" />
<string name="com.authy.storage.tokens.authenticator.key">[{&quot;accountType&quot;:&quot;authenticator&quot;,&quot;digits&quot;:6,&quot;encryptedSecret&quot;:&quot;94mk0erEKRYU7UKn4o6WAPWR/gQkyrNIxPWIZcXHeWc\u003d&quot;,&quot;logo&quot;:&quot;Deno&quot;,&quot;originalIssuer&quot;:&quot;Deno&quot;,&quot;originalName&quot;:&quot;Deno:Mason&quot;,&quot;timestamp&quot;:1608377908,&quot;salt&quot;:&quot;z01fjH2vyzYks49nxaOTp8M39eMOjxT4&quot;,&quot;upload_state&quot;:&quot;uploaded&quot;,&quot;hidden&quot;:false,&quot;id&quot;:&quot;1608374963&quot;,&quot;isNew&quot;:false,&quot;name&quot;:&quot;Deno: Mason&quot;},{&quot;accountType&quot;:&quot;authenticator&quot;,&quot;digits&quot;:7,&quot;encryptedSecret&quot;:&quot;0fdhb8g0FBqjZL9mSi13d0+gQ7myUsxllKIHwzxJB08\u003d&quot;,&quot;logo&quot;:&quot;SPDX&quot;,&quot;originalIssuer&quot;:&quot;SPDX&quot;,&quot;originalName&quot;:&quot;SPDX:James&quot;,&quot;timestamp&quot;:1608377908,&quot;salt&quot;:&quot;AVqSXXFR6WGdRyXutRhS2qqtJKMxUmfN&quot;,&quot;upload_state&quot;:&quot;uploaded&quot;,&quot;hidden&quot;:false,&quot;id&quot;:&quot;1608374991&quot;,&quot;isNew&quot;:false,&quot;name&quot;:&quot;SPDX: James&quot;},{&quot;accountType&quot;:&quot;authenticator&quot;,&quot;digits&quot;:8,&quot;encryptedSecret&quot;:&quot;SRtA3xEj1iD9Bh1aHITCtPW6wcbURAsWwWs00oH4SZ8\u003d&quot;,&quot;logo&quot;:&quot;Airbnb&quot;,&quot;originalIssuer&quot;:&quot;Airbnb&quot;,&quot;originalName&quot;:&quot;Airbnb:Elijah&quot;,&quot;timestamp&quot;:1608377908,&quot;salt&quot;:&quot;oXag8rjuljcU5XWhT2FQXgM9alkIvguT&quot;,&quot;upload_state&quot;:&quot;uploaded&quot;,&quot;hidden&quot;:false,&quot;id&quot;:&quot;1608375004&quot;,&quot;isNew&quot;:false,&quot;name&quot;:&quot;Airbnb: Elijah&quot;}]</string>
</map>

View file

@ -0,0 +1,5 @@
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
<int name="key_version" value="121" />
<string name="com.authy.storage.tokens.authenticator.key">[{&quot;accountType&quot;:&quot;authenticator&quot;,&quot;decryptedSecret&quot;:&quot;4SJHB4GSD43FZBAI7C2HLRJGPQ&quot;,&quot;digits&quot;:6,&quot;logo&quot;:&quot;Deno&quot;,&quot;originalIssuer&quot;:&quot;Deno&quot;,&quot;originalName&quot;:&quot;Deno:Mason&quot;,&quot;timestamp&quot;:1608374963,&quot;upload_state&quot;:&quot;notUploaded&quot;,&quot;hidden&quot;:false,&quot;id&quot;:&quot;1608374963&quot;,&quot;isNew&quot;:false,&quot;name&quot;:&quot;Deno: Mason&quot;},{&quot;accountType&quot;:&quot;authenticator&quot;,&quot;decryptedSecret&quot;:&quot;5OM4WOOGPLQEF6UGN3CPEOOLWU&quot;,&quot;digits&quot;:7,&quot;logo&quot;:&quot;SPDX&quot;,&quot;originalIssuer&quot;:&quot;SPDX&quot;,&quot;originalName&quot;:&quot;SPDX:James&quot;,&quot;timestamp&quot;:1608374963,&quot;upload_state&quot;:&quot;notUploaded&quot;,&quot;hidden&quot;:false,&quot;id&quot;:&quot;1608374991&quot;,&quot;isNew&quot;:false,&quot;name&quot;:&quot;SPDX: James&quot;},{&quot;accountType&quot;:&quot;authenticator&quot;,&quot;decryptedSecret&quot;:&quot;7ELGJSGXNCCTV3O6LKJWYFV2RA&quot;,&quot;digits&quot;:8,&quot;logo&quot;:&quot;Airbnb&quot;,&quot;originalIssuer&quot;:&quot;Airbnb&quot;,&quot;originalName&quot;:&quot;Airbnb:Elijah&quot;,&quot;timestamp&quot;:1608374963,&quot;upload_state&quot;:&quot;notUploaded&quot;,&quot;hidden&quot;:false,&quot;id&quot;:&quot;1608375004&quot;,&quot;isNew&quot;:false,&quot;name&quot;:&quot;Airbnb: Elijah&quot;}]</string>
</map>

View file

@ -0,0 +1,8 @@
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
<string name="WWE:Mason">{&quot;algo&quot;:&quot;SHA512&quot;,&quot;counter&quot;:10299,&quot;digits&quot;:8,&quot;issuerExt&quot;:&quot;WWE&quot;,&quot;issuerInt&quot;:&quot;WWE&quot;,&quot;label&quot;:&quot;Mason&quot;,&quot;period&quot;:30,&quot;secret&quot;:[-19,64,-59,-18,-5,-20,-50,34,6,-79,93,117,-63,24,42,118],&quot;type&quot;:&quot;HOTP&quot;}</string>
<string name="Airbnb:Elijah">{&quot;algo&quot;:&quot;SHA512&quot;,&quot;counter&quot;:0,&quot;digits&quot;:8,&quot;issuerExt&quot;:&quot;Airbnb&quot;,&quot;issuerInt&quot;:&quot;Airbnb&quot;,&quot;label&quot;:&quot;Elijah&quot;,&quot;period&quot;:50,&quot;secret&quot;:[-7,22,100,-56,-41,104,-123,58,-19,-34,90,-109,108,22,-70,-120],&quot;type&quot;:&quot;TOTP&quot;}</string>
<string name="Deno:Mason">{&quot;algo&quot;:&quot;SHA1&quot;,&quot;counter&quot;:0,&quot;digits&quot;:6,&quot;issuerExt&quot;:&quot;Deno&quot;,&quot;issuerInt&quot;:&quot;Deno&quot;,&quot;label&quot;:&quot;Mason&quot;,&quot;period&quot;:30,&quot;secret&quot;:[-28,-110,112,-16,-46,31,54,92,-124,8,-8,-76,117,-59,38,124],&quot;type&quot;:&quot;TOTP&quot;}</string>
<string name="Issuu:James">{&quot;algo&quot;:&quot;SHA1&quot;,&quot;counter&quot;:0,&quot;digits&quot;:6,&quot;issuerExt&quot;:&quot;Issuu&quot;,&quot;issuerInt&quot;:&quot;Issuu&quot;,&quot;label&quot;:&quot;James&quot;,&quot;period&quot;:30,&quot;secret&quot;:[-61,-100,-60,94,-46,-23,-101,-24,-52,47,-95,75,101,78,-35,103],&quot;type&quot;:&quot;HOTP&quot;}</string>
<string name="tokenOrder">[&quot;WWE:Mason&quot;,&quot;Issuu:James&quot;,&quot;Airbnb:Elijah&quot;,&quot;Deno:Mason&quot;]</string>
</map>

View file

@ -0,0 +1 @@
{"tokenOrder":["WWE:Mason","Air Canada:Benjamin","Issuu:James","Airbnb:Elijah","SPDX:James","Deno:Mason"],"tokens":[{"algo":"SHA512","counter":10299,"digits":8,"issuerExt":"WWE","issuerInt":"WWE","label":"Mason","period":30,"secret":[-19,64,-59,-18,-5,-20,-50,34,6,-79,93,117,-63,24,42,118],"type":"HOTP"},{"algo":"SHA256","counter":49,"digits":7,"issuerExt":"Air Canada","issuerInt":"Air Canada","label":"Benjamin","period":30,"secret":[85,42,-108,-71,-97,-18,-48,121,-76,114,-51,68,-43,47,-26,66],"type":"HOTP"},{"algo":"SHA1","counter":0,"digits":6,"issuerExt":"Issuu","issuerInt":"Issuu","label":"James","period":30,"secret":[-61,-100,-60,94,-46,-23,-101,-24,-52,47,-95,75,101,78,-35,103],"type":"HOTP"},{"algo":"SHA512","counter":0,"digits":8,"issuerExt":"Airbnb","issuerInt":"Airbnb","label":"Elijah","period":50,"secret":[-7,22,100,-56,-41,104,-123,58,-19,-34,90,-109,108,22,-70,-120],"type":"TOTP"},{"algo":"SHA256","counter":0,"digits":7,"issuerExt":"SPDX","issuerInt":"SPDX","label":"James","period":20,"secret":[-21,-103,-53,57,-58,122,-32,66,-6,-122,110,-60,-14,57,-53,-75],"type":"TOTP"},{"algo":"SHA1","counter":0,"digits":6,"issuerExt":"Deno","issuerInt":"Deno","label":"Mason","period":30,"secret":[-28,-110,112,-16,-46,31,54,92,-124,8,-8,-76,117,-59,38,124],"type":"TOTP"}]}

View file

@ -0,0 +1,10 @@
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
<string name="WWE:Mason">{&quot;algo&quot;:&quot;SHA512&quot;,&quot;counter&quot;:10299,&quot;digits&quot;:8,&quot;issuerExt&quot;:&quot;WWE&quot;,&quot;issuerInt&quot;:&quot;WWE&quot;,&quot;label&quot;:&quot;Mason&quot;,&quot;period&quot;:30,&quot;secret&quot;:[-19,64,-59,-18,-5,-20,-50,34,6,-79,93,117,-63,24,42,118],&quot;type&quot;:&quot;HOTP&quot;}</string>
<string name="Airbnb:Elijah">{&quot;algo&quot;:&quot;SHA512&quot;,&quot;counter&quot;:0,&quot;digits&quot;:8,&quot;issuerExt&quot;:&quot;Airbnb&quot;,&quot;issuerInt&quot;:&quot;Airbnb&quot;,&quot;label&quot;:&quot;Elijah&quot;,&quot;period&quot;:50,&quot;secret&quot;:[-7,22,100,-56,-41,104,-123,58,-19,-34,90,-109,108,22,-70,-120],&quot;type&quot;:&quot;TOTP&quot;}</string>
<string name="Air Canada:Benjamin">{&quot;algo&quot;:&quot;SHA256&quot;,&quot;counter&quot;:49,&quot;digits&quot;:7,&quot;issuerExt&quot;:&quot;Air Canada&quot;,&quot;issuerInt&quot;:&quot;Air Canada&quot;,&quot;label&quot;:&quot;Benjamin&quot;,&quot;period&quot;:30,&quot;secret&quot;:[85,42,-108,-71,-97,-18,-48,121,-76,114,-51,68,-43,47,-26,66],&quot;type&quot;:&quot;HOTP&quot;}</string>
<string name="SPDX:James">{&quot;algo&quot;:&quot;SHA256&quot;,&quot;counter&quot;:0,&quot;digits&quot;:7,&quot;issuerExt&quot;:&quot;SPDX&quot;,&quot;issuerInt&quot;:&quot;SPDX&quot;,&quot;label&quot;:&quot;James&quot;,&quot;period&quot;:20,&quot;secret&quot;:[-21,-103,-53,57,-58,122,-32,66,-6,-122,110,-60,-14,57,-53,-75],&quot;type&quot;:&quot;TOTP&quot;}</string>
<string name="Deno:Mason">{&quot;algo&quot;:&quot;SHA1&quot;,&quot;counter&quot;:0,&quot;digits&quot;:6,&quot;issuerExt&quot;:&quot;Deno&quot;,&quot;issuerInt&quot;:&quot;Deno&quot;,&quot;label&quot;:&quot;Mason&quot;,&quot;period&quot;:30,&quot;secret&quot;:[-28,-110,112,-16,-46,31,54,92,-124,8,-8,-76,117,-59,38,124],&quot;type&quot;:&quot;TOTP&quot;}</string>
<string name="Issuu:James">{&quot;algo&quot;:&quot;SHA1&quot;,&quot;counter&quot;:0,&quot;digits&quot;:6,&quot;issuerExt&quot;:&quot;Issuu&quot;,&quot;issuerInt&quot;:&quot;Issuu&quot;,&quot;label&quot;:&quot;James&quot;,&quot;period&quot;:30,&quot;secret&quot;:[-61,-100,-60,94,-46,-23,-101,-24,-52,47,-95,75,101,78,-35,103],&quot;type&quot;:&quot;HOTP&quot;}</string>
<string name="tokenOrder">[&quot;WWE:Mason&quot;,&quot;Air Canada:Benjamin&quot;,&quot;Issuu:James&quot;,&quot;Airbnb:Elijah&quot;,&quot;SPDX:James&quot;,&quot;Deno:Mason&quot;]</string>
</map>

View file

@ -0,0 +1,7 @@
otpauth://totp/Deno:Mason?secret=4SJHB4GSD43FZBAI7C2HLRJGPQ&issuer=Deno&algorithm=SHA1&digits=6&period=30
otpauth://totp/SPDX:James?secret=5OM4WOOGPLQEF6UGN3CPEOOLWU&issuer=SPDX&algorithm=SHA256&digits=7&period=20
otpauth://totp/Airbnb:Elijah?secret=7ELGJSGXNCCTV3O6LKJWYFV2RA&issuer=Airbnb&algorithm=SHA512&digits=8&period=50
otpauth://hotp/Issuu:James?secret=YOOMIXWS5GN6RTBPUFFWKTW5M4&issuer=Issuu&algorithm=SHA1&digits=6&counter=1
otpauth://hotp/Air%20Canada:Benjamin?secret=KUVJJOM753IHTNDSZVCNKL7GII&issuer=Air+Canada&algorithm=SHA256&digits=7&counter=50
otpauth://hotp/WWE:Mason?secret=5VAML3X35THCEBVRLV24CGBKOY&issuer=WWE&algorithm=SHA512&digits=8&counter=10300
otpauth://steam/Boeing:Sophia?secret=JRZCL47CMXVOQMNPZR2F7J4RGI&issuer=Boeing&algorithm=SHA1&digits=5&period=30

View file

@ -0,0 +1 @@
{"steamid":"1234","shared_secret":"THIl8+Jl6ugxr8x0X6eRMg==","serial_number":"12345678901234567890","revocation_code":"R1234","uri":"otpauth:\/\/totp\/Steam:Sophia?secret=JRZCL47CMXVOQMNPZR2F7J4RGI&issuer=Steam","server_time":"0","account_name":"Sophia","token_gid":"894820a474c9","identity_secret":"dGhpcyBpcyBhIHRlc3Qgc3RyaW5n","secret_1":"eWV0IGFub3RoZXIgdGVzdCBzdHJpbmc=","status":1,"steamguard_scheme":"2"}

View file

@ -0,0 +1 @@
u51d1LQbPDtXbvihHPguTkF6klbc5rJgSpA6KX7oUM4Lt0GLO027ypc2/LvGJDH6zvmfQ4tASN4Iv/bZBllzMQpmrieyWtKBCA+wlQFR1007CqHQssjfkRs0mrkwd4ENkrnJCVDNG+0+wrCUKn0Kf5Ie7KUjVTcHk01Vo5dUozaXgF20jNHb0q2z4030+2kZ/1g+0+RQZLRtNOErrTacH8hlnPzEY/wHk3mWwaxwtesUQfuSlg4DgUyiot7sCV3hDVIgp378r1REhinSbRe0X/7jklCdPrrAXnT73+ok9kxnMOQglQBjIutXO7dy+bC5EZqk0lxIjcbvga7wQj7zizW75avJw6gpi0ksVtuypp+DEtRSjhMQYSwZY8xWZFloOX8jmHwEmyYJkIdrDep00SmuV8B9qZupBRqqKzaPCS1/fJ1oWrxfgRsDR2W7bH5p3ExSEMtXs6Fv8GSiqAnx0n9K/muVV559XFjOwiNXRnbjCvcuHKtCPNFrLEy9lANlwLGBJLLaOhaGOuwQ+6V7xVC7sGyB79atcg6GSQdmZMCYdic6rGPqK+XcyBp7g0Mc/mGUR7h5PCGMrreIImb7O+XFHyXNbrONN3f31fGHmhuLn3HUe8B3ylqEKajTUFytR7q9fZOjNUhQQj5ev/3UmRAIJpGQxHK5D/qbmslBlWuSB8fAXKly9XiUE3Ui2Jfs2XlZn4s8g5LUGvMvZqqUkGhNJk7eDfNWL/DlYdbdsNTMExp4/KonH3/iMFqtXHHaOIHXMZp5LnaXPF2KeQZJq5utxnXKBzjfFgAfLGXXZQdwOcqlmwFrDNtn+e/Fe8pOmFDsyqkiUwkPAO38cAs+xYrf9MUjtGo08t7IUgDkSEY70hziskbVbBinqJ4aQW7pWaD7slSLw2Qn0bpDw1lXHIoz8TMw9ZiNKql514+ZYcKXJBiMXtK+DASRlC9vcye9KxOnP+rSS3GtcE2YUu0pburNZN5ZhOXPWYWg0XTxvC5anIZ2VL/zs/NbOypix0eGd1LLP9JdjzX6a99jNIcZJzT+4X8RgeRX5d5GrD6Ui243XzaceyrS/cc2UHX/PQHn4IZXC82JmfJ+ic04NB9l8cimygZB0QcCfAbtqoqRYs1WeNa+xkXV7kS1QrgH4hwY/jUU0TPY5s4m6oRAKgBua6wwtV8doO3dOwjZrCTxJzduD3Te9os9IPZXB31Vp1lGAqzUuc4hozdCagrnl675BJJKrZ57Jh8l5LGALIZjEr3R8lKp2V+M54Z2+zO3SIkCzGzdrXwZcU4Vs2MG+rKfi5R9EChU2J8Fjy5R1Iga5a43J88h0M6dlH91EqPjtVDTmdfeNaoZqS5k9A8IzVt5OImFaDCKw2WkA57BuP2BU339NczpuUm7yEUx0gBYiXITk9JDwfdsO0N6BAyc2OYobN19BA1erUjlQW4H7L29h/VvGt4E8E2ncWvFf4Abh/H5WKJykbsvLCTDFSf8RGg8w4sYRaAfX90XC9QAotKiKVFCV8Q4uj9Is707NRn+JTKYtN6qS1n4sc5B7h079fDsXssFpLfrqjp3X8P4wMdEiM6W9N2WsZvGOER/K2IdvU3V8ottGitsQDHaxMZcaTuNx6KwIirP4r7Zrt2uSq/5fzKClMJSBx9aczPLe3A+LIw8

View file

@ -0,0 +1,20 @@
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
<int name="RATE_US_COUNTER" value="13" />
<boolean name="SHOWCASE_FIREST_RUN" value="false" />
<boolean name="FIRST_ACCOUNT_ADDED_BACKUP" value="true" />
<boolean name="SHOULD_SHOW_RATE_US_DIALOG" value="false" />
<int name="BLACK_FRIDAY_MODAL" value="13" />
<string name="STATIC_TOTP_CODES_LIST">[{&quot;accountHeaderLabel&quot;:&quot;&quot;,&quot;allowNotifications&quot;:false,&quot;base&quot;:16,&quot;digits&quot;:&quot;6&quot;,&quot;fileName&quot;:&quot;&quot;,&quot;iconLabel&quot;:&quot;&quot;,&quot;iconPath&quot;:&quot;&quot;,&quot;isFavorite&quot;:false,&quot;isSelected&quot;:false,&quot;isWidgetActive&quot;:false,&quot;issuer&quot;:&quot;Deno&quot;,&quot;key&quot;:&quot;E49270F0D21F365C8408F8B475C5267C&quot;,&quot;name&quot;:&quot;mason&quot;,&quot;period&quot;:&quot;30&quot;,&quot;setIconFromDrawable&quot;:true,&quot;source&quot;:0,&quot;timeRemaining&quot;:0,&quot;totpCode&quot;:&quot;&quot;,&quot;widgetId&quot;:0},{&quot;accountHeaderLabel&quot;:&quot;&quot;,&quot;allowNotifications&quot;:false,&quot;base&quot;:16,&quot;digits&quot;:&quot;7&quot;,&quot;fileName&quot;:&quot;&quot;,&quot;iconLabel&quot;:&quot;&quot;,&quot;iconPath&quot;:&quot;&quot;,&quot;isFavorite&quot;:false,&quot;isSelected&quot;:false,&quot;isWidgetActive&quot;:false,&quot;issuer&quot;:&quot;SPDX&quot;,&quot;key&quot;:&quot;EB99CB39C67AE042FA866EC4F239CBB5&quot;,&quot;name&quot;:&quot;james&quot;,&quot;period&quot;:&quot;20&quot;,&quot;setIconFromDrawable&quot;:true,&quot;source&quot;:0,&quot;timeRemaining&quot;:0,&quot;totpCode&quot;:&quot;&quot;,&quot;widgetId&quot;:0},{&quot;accountHeaderLabel&quot;:&quot;&quot;,&quot;allowNotifications&quot;:false,&quot;base&quot;:16,&quot;digits&quot;:&quot;8&quot;,&quot;fileName&quot;:&quot;&quot;,&quot;iconLabel&quot;:&quot;&quot;,&quot;iconPath&quot;:&quot;&quot;,&quot;isFavorite&quot;:false,&quot;isSelected&quot;:false,&quot;isWidgetActive&quot;:false,&quot;issuer&quot;:&quot;Airbnb&quot;,&quot;key&quot;:&quot;F91664C8D768853AEDDE5A936C16BA88&quot;,&quot;name&quot;:&quot;elijah&quot;,&quot;period&quot;:&quot;50&quot;,&quot;setIconFromDrawable&quot;:true,&quot;source&quot;:0,&quot;timeRemaining&quot;:0,&quot;totpCode&quot;:&quot;&quot;,&quot;widgetId&quot;:0}]</string>
<boolean name="WIDGET_SERVICE_RELOAD_FLAG" value="false" />
<boolean name="SCREENSHOT_FLAG" value="true" />
<boolean name="ASK_FOR_VERIFICATION" value="true" />
<boolean name="FORCE_SYNC_FIRST_TIME_V3" value="false" />
<boolean name="NOTIFICATION_SERVICE_RELOAD_FLAG" value="true" />
<string name="SET_SCREENSHOT_FLAG">COMPLETED</string>
<boolean name="ADD_LABEL" value="true" />
<string name="SORT_ACCOUNT_BY">CUSTOM</string>
<string name="DEFAULT_ENCRYPTION_KEY">Testtest1</string>
<string name="FCM_CLOUD_ID">dU9OMKiERmSSHuXTbuYkkr:APA91bH7yW4Pw-z9QnWYpSWr2Cwrd8aV7OtY-JOTAj0jcar3V_ZvLvFlks37NUV7R2vyl9M47dGAD2iE7ud_IlVgaxo0VWZicY8qKGVT6w-DlrE0Vgre7IX-n69T0bNEykoJpTK_hsmz</string>
<int name="CHANGE_LOG_VERSION" value="55" />
</map>