mirror of
https://github.com/beemdevelopment/Aegis.git
synced 2025-05-04 20:30:36 +00:00
commit
a5ec7666ec
40 changed files with 974 additions and 321 deletions
|
@ -42,14 +42,23 @@ android {
|
|||
testOptions {
|
||||
execution 'ANDROIDX_TEST_ORCHESTRATOR'
|
||||
|
||||
unitTests.all {
|
||||
useJUnitPlatform()
|
||||
unitTests {
|
||||
all {
|
||||
maxHeapSize "3g"
|
||||
|
||||
ignoreFailures false
|
||||
testLogging {
|
||||
events "passed", "skipped", "failed", "standardOut", "standardError"
|
||||
|
||||
showExceptions true
|
||||
exceptionFormat "full"
|
||||
showCauses true
|
||||
showStackTraces true
|
||||
}
|
||||
}
|
||||
|
||||
includeAndroidResources true
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
|
@ -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'
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
||||
|
|
|
@ -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,8 +116,18 @@ public class AndOtpImporter extends DatabaseImporter {
|
|||
}
|
||||
}
|
||||
|
||||
private void decrypt(Context context, char[] password, boolean oldFormat, DecryptListener listener) throws DatabaseImporterException {
|
||||
if (oldFormat) {
|
||||
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
|
||||
|
@ -129,22 +139,29 @@ public class AndOtpImporter extends DatabaseImporter {
|
|||
}
|
||||
byte[] keyBytes = hash.digest(CryptoUtils.toBytes(password));
|
||||
SecretKey key = new SecretKeySpec(keyBytes, "AES");
|
||||
DecryptedState state = decryptData(key, 0);
|
||||
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));
|
||||
return decryptContent(key, 0);
|
||||
}
|
||||
|
||||
byte[] salt = Arrays.copyOfRange(_data, INT_SIZE, offset);
|
||||
AndOtpKeyDerivationTask.Params params = new AndOtpKeyDerivationTask.Params(password, salt, iterations);
|
||||
AndOtpKeyDerivationTask task = new AndOtpKeyDerivationTask(context, key1 -> {
|
||||
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) {
|
||||
DecryptedState state = decryptOldFormat(password);
|
||||
listener.onStateDecrypted(state);
|
||||
} else {
|
||||
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,12 +301,17 @@ public class AndOtpImporter extends DatabaseImporter {
|
|||
_cb.onTaskFinished(key);
|
||||
}
|
||||
|
||||
public static class Params {
|
||||
private char[] _password;
|
||||
private byte[] _salt;
|
||||
private int _iterations;
|
||||
public interface Callback {
|
||||
void onTaskFinished(SecretKey key);
|
||||
}
|
||||
}
|
||||
|
||||
public Params(char[] password, byte[] salt, int iterations) {
|
||||
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;
|
||||
|
@ -305,9 +329,4 @@ public class AndOtpImporter extends DatabaseImporter {
|
|||
return _salt;
|
||||
}
|
||||
}
|
||||
|
||||
public interface Callback {
|
||||
void onTaskFinished(SecretKey key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decrypt(Context context, DecryptListener listener) {
|
||||
Dialogs.showPasswordInputDialog(context, password -> {
|
||||
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(context);
|
||||
DatabaseImporter.State state = importer.read(zipStream);
|
||||
listener.onStateDecrypted(state);
|
||||
return;
|
||||
GoogleAuthUriImporter importer = new GoogleAuthUriImporter(null);
|
||||
return importer.read(zipStream);
|
||||
}
|
||||
}
|
||||
|
||||
throw new FileNotFoundException(FILENAME);
|
||||
} catch (IOException | DatabaseImporterException e) {
|
||||
} catch (IOException e) {
|
||||
throw new DatabaseImporterException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decrypt(Context context, DecryptListener listener) {
|
||||
Dialogs.showPasswordInputDialog(context, password -> {
|
||||
try {
|
||||
DatabaseImporter.State state = decrypt(password);
|
||||
listener.onStateDecrypted(state);
|
||||
} catch (DatabaseImporterException e) {
|
||||
listener.onError(e);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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,9 +155,7 @@ public class AuthyImporter extends DatabaseImporter {
|
|||
_array = array;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decrypt(Context context, DecryptListener listener) {
|
||||
Dialogs.showPasswordInputDialog(context, R.string.enter_password_authy_message, password -> {
|
||||
protected DecryptedState decrypt(char[] password) throws DatabaseImporterException {
|
||||
try {
|
||||
for (int i = 0; i < _array.length(); i++) {
|
||||
JSONObject obj = _array.getJSONObject(i);
|
||||
|
@ -170,8 +169,9 @@ public class AuthyImporter extends DatabaseImporter {
|
|||
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/PKCS7Padding");
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||
IvParameterSpec ivSpec = new IvParameterSpec(IV);
|
||||
cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
|
||||
|
||||
|
@ -181,8 +181,7 @@ public class AuthyImporter extends DatabaseImporter {
|
|||
obj.put("decryptedSecret", new String(secret, StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
DecryptedState state = new DecryptedState(_array);
|
||||
listener.onStateDecrypted(state);
|
||||
return new DecryptedState(_array);
|
||||
} catch (JSONException
|
||||
| EncodingException
|
||||
| NoSuchAlgorithmException
|
||||
|
@ -192,6 +191,17 @@ public class AuthyImporter extends DatabaseImporter {
|
|||
| 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 {
|
||||
DecryptedState state = decrypt(password);
|
||||
listener.onStateDecrypted(state);
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -70,6 +70,10 @@ public class PasswordSlot extends RawSlot {
|
|||
return _repaired;
|
||||
}
|
||||
|
||||
public SCryptParameters getSCryptParameters() {
|
||||
return _params;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getType() {
|
||||
return TYPE_DERIVED;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
27
app/src/test/java/com/beemdevelopment/aegis/otp/OtpTest.java
Normal file
27
app/src/test/java/com/beemdevelopment/aegis/otp/OtpTest.java
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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() {
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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=="
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
Binary file not shown.
Binary file not shown.
|
@ -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":[]}]
|
Binary file not shown.
|
@ -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">[{"accountType":"authenticator","digits":6,"encryptedSecret":"94mk0erEKRYU7UKn4o6WAPWR/gQkyrNIxPWIZcXHeWc\u003d","logo":"Deno","originalIssuer":"Deno","originalName":"Deno:Mason","timestamp":1608377908,"salt":"z01fjH2vyzYks49nxaOTp8M39eMOjxT4","upload_state":"uploaded","hidden":false,"id":"1608374963","isNew":false,"name":"Deno: Mason"},{"accountType":"authenticator","digits":7,"encryptedSecret":"0fdhb8g0FBqjZL9mSi13d0+gQ7myUsxllKIHwzxJB08\u003d","logo":"SPDX","originalIssuer":"SPDX","originalName":"SPDX:James","timestamp":1608377908,"salt":"AVqSXXFR6WGdRyXutRhS2qqtJKMxUmfN","upload_state":"uploaded","hidden":false,"id":"1608374991","isNew":false,"name":"SPDX: James"},{"accountType":"authenticator","digits":8,"encryptedSecret":"SRtA3xEj1iD9Bh1aHITCtPW6wcbURAsWwWs00oH4SZ8\u003d","logo":"Airbnb","originalIssuer":"Airbnb","originalName":"Airbnb:Elijah","timestamp":1608377908,"salt":"oXag8rjuljcU5XWhT2FQXgM9alkIvguT","upload_state":"uploaded","hidden":false,"id":"1608375004","isNew":false,"name":"Airbnb: Elijah"}]</string>
|
||||
</map>
|
|
@ -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">[{"accountType":"authenticator","decryptedSecret":"4SJHB4GSD43FZBAI7C2HLRJGPQ","digits":6,"logo":"Deno","originalIssuer":"Deno","originalName":"Deno:Mason","timestamp":1608374963,"upload_state":"notUploaded","hidden":false,"id":"1608374963","isNew":false,"name":"Deno: Mason"},{"accountType":"authenticator","decryptedSecret":"5OM4WOOGPLQEF6UGN3CPEOOLWU","digits":7,"logo":"SPDX","originalIssuer":"SPDX","originalName":"SPDX:James","timestamp":1608374963,"upload_state":"notUploaded","hidden":false,"id":"1608374991","isNew":false,"name":"SPDX: James"},{"accountType":"authenticator","decryptedSecret":"7ELGJSGXNCCTV3O6LKJWYFV2RA","digits":8,"logo":"Airbnb","originalIssuer":"Airbnb","originalName":"Airbnb:Elijah","timestamp":1608374963,"upload_state":"notUploaded","hidden":false,"id":"1608375004","isNew":false,"name":"Airbnb: Elijah"}]</string>
|
||||
</map>
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
|
||||
<map>
|
||||
<string name="WWE:Mason">{"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"}</string>
|
||||
<string name="Airbnb:Elijah">{"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"}</string>
|
||||
<string name="Deno:Mason">{"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"}</string>
|
||||
<string name="Issuu:James">{"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"}</string>
|
||||
<string name="tokenOrder">["WWE:Mason","Issuu:James","Airbnb:Elijah","Deno:Mason"]</string>
|
||||
</map>
|
|
@ -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"}]}
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
|
||||
<map>
|
||||
<string name="WWE:Mason">{"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"}</string>
|
||||
<string name="Airbnb:Elijah">{"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"}</string>
|
||||
<string name="Air Canada:Benjamin">{"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"}</string>
|
||||
<string name="SPDX:James">{"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"}</string>
|
||||
<string name="Deno:Mason">{"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"}</string>
|
||||
<string name="Issuu:James">{"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"}</string>
|
||||
<string name="tokenOrder">["WWE:Mason","Air Canada:Benjamin","Issuu:James","Airbnb:Elijah","SPDX:James","Deno:Mason"]</string>
|
||||
</map>
|
Binary file not shown.
Binary file not shown.
|
@ -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
|
|
@ -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"}
|
|
@ -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
|
|
@ -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">[{"accountHeaderLabel":"","allowNotifications":false,"base":16,"digits":"6","fileName":"","iconLabel":"","iconPath":"","isFavorite":false,"isSelected":false,"isWidgetActive":false,"issuer":"Deno","key":"E49270F0D21F365C8408F8B475C5267C","name":"mason","period":"30","setIconFromDrawable":true,"source":0,"timeRemaining":0,"totpCode":"","widgetId":0},{"accountHeaderLabel":"","allowNotifications":false,"base":16,"digits":"7","fileName":"","iconLabel":"","iconPath":"","isFavorite":false,"isSelected":false,"isWidgetActive":false,"issuer":"SPDX","key":"EB99CB39C67AE042FA866EC4F239CBB5","name":"james","period":"20","setIconFromDrawable":true,"source":0,"timeRemaining":0,"totpCode":"","widgetId":0},{"accountHeaderLabel":"","allowNotifications":false,"base":16,"digits":"8","fileName":"","iconLabel":"","iconPath":"","isFavorite":false,"isSelected":false,"isWidgetActive":false,"issuer":"Airbnb","key":"F91664C8D768853AEDDE5A936C16BA88","name":"elijah","period":"50","setIconFromDrawable":true,"source":0,"timeRemaining":0,"totpCode":"","widgetId":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>
|
Loading…
Add table
Add a link
Reference in a new issue