diff --git a/.idea/runConfigurations/All_tests.xml b/.idea/runConfigurations/All_tests.xml
index 1882b817b1c49ce97019e5a1b6499a1d154e88c2..71857d973c843a84de15b6a8e9addfed60b743b4 100644
--- a/.idea/runConfigurations/All_tests.xml
+++ b/.idea/runConfigurations/All_tests.xml
@@ -21,6 +21,7 @@
     <method>
       <option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in bramble-api" run_configuration_type="AndroidJUnit" />
       <option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in bramble-core" run_configuration_type="AndroidJUnit" />
+      <option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in bramble-android" run_configuration_type="AndroidJUnit" />
       <option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in bramble-j2se" run_configuration_type="AndroidJUnit" />
       <option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in briar-core" run_configuration_type="AndroidJUnit" />
     </method>
diff --git a/bramble-android/build.gradle b/bramble-android/build.gradle
index b41f1cebe01613e8760ce235adf65e0681e75af9..f7c9415ed49dc42d6e0c25af540a773d02e9f47e 100644
--- a/bramble-android/build.gradle
+++ b/bramble-android/build.gradle
@@ -34,6 +34,14 @@ dependencies {
 
 	compileOnly 'javax.annotation:jsr250-api:1.0'
 
+	testImplementation project(path: ':bramble-api', configuration: 'testOutput')
+	testImplementation 'junit:junit:4.12'
+	testImplementation "org.jmock:jmock:2.8.2"
+	testImplementation "org.jmock:jmock-junit4:2.8.2"
+	testImplementation "org.jmock:jmock-legacy:2.8.2"
+	testImplementation "org.hamcrest:hamcrest-library:1.3"
+	testImplementation "org.hamcrest:hamcrest-core:1.3"
+
 	androidTestImplementation project(path: ':bramble-api', configuration: 'testOutput')
 	androidTestImplementation project(path: ':bramble-core', configuration: 'testOutput')
 	androidTestImplementation 'com.android.support.test:runner:1.0.2'
@@ -43,6 +51,10 @@ dependencies {
 
 dependencyVerification {
 	verify = [
+			'cglib:cglib:3.2.0:cglib-3.2.0.jar:adb13bab79712ad6bdf1bd59f2a3918018a8016e722e8a357065afb9e6690861',
+			'com.android.support.test:monitor:1.0.2:monitor-1.0.2.aar:38ef4fa98a32dc55550ff49bb36a583e178b3a9b830fcb8dcc27bfc4254bc2bc',
+			'com.android.support.test:runner:1.0.2:runner-1.0.2.aar:f04b9ae342975ba1cb3e4a06e13426e3e6b8a73faa45acba604493d83c9a4f00',
+			'com.android.support:support-annotations:27.1.1:support-annotations-27.1.1.jar:3365960206c3d2b09e845f555e7f88f8effc8d2f00b369e66c4be384029299cf',
 			'com.android.tools.analytics-library:protos:26.1.3:protos-26.1.3.jar:818c9f256f141d9dafec03a1aa2b94d240b2c140acfd7ee31a8b3e6c2b9479e3',
 			'com.android.tools.analytics-library:shared:26.1.3:shared-26.1.3.jar:7110706c7ada96c8b6f5ca80c478291bc7899d46277de2c48527e045442401a3',
 			'com.android.tools.analytics-library:tracker:26.1.3:tracker-26.1.3.jar:4155424bf2ce4872da83332579a1707252bc66cbd77c5144fdc4483d0f2e1418',
@@ -91,12 +103,16 @@ dependencyVerification {
 			'javax.annotation:jsr250-api:1.0:jsr250-api-1.0.jar:a1a922d0d9b6d183ed3800dfac01d1e1eb159f0e8c6f94736931c1def54a941f',
 			'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
 			'javax.xml.bind:jaxb-api:2.2.12-b140109.1041:jaxb-api-2.2.12-b140109.1041.jar:b5e60cd8b7b5ff01ce4a74c5dd008f4fbd14ced3495d0b47b85cfedc182211f2',
+			'junit:junit:4.12:junit-4.12.jar:59721f0805e223d84b90677887d9ff567dc534d7c502ca903c0c2b17f05c116a',
 			'net.sf.jopt-simple:jopt-simple:4.9:jopt-simple-4.9.jar:26c5856e954b5f864db76f13b86919b59c6eecf9fd930b96baa8884626baf2f5',
 			'net.sf.kxml:kxml2:2.3.0:kxml2-2.3.0.jar:f264dd9f79a1fde10ce5ecc53221eff24be4c9331c830b7d52f2f08a7b633de2',
+			'org.apache.ant:ant-launcher:1.9.4:ant-launcher-1.9.4.jar:7bccea20b41801ca17bcbc909a78c835d0f443f12d639c77bd6ae3d05861608d',
+			'org.apache.ant:ant:1.9.4:ant-1.9.4.jar:649ae0730251de07b8913f49286d46bba7b92d47c5f332610aa426c4f02161d8',
 			'org.apache.commons:commons-compress:1.12:commons-compress-1.12.jar:2c1542faf343185b7cab9c3d55c8ae5471d6d095d3887a4adefdbdf2984dc0b6',
 			'org.apache.httpcomponents:httpclient:4.2.6:httpclient-4.2.6.jar:362e9324ee7c697e21279e20077b52737ddef3f1b2c1a7abe5ad34b465145550',
 			'org.apache.httpcomponents:httpcore:4.2.5:httpcore-4.2.5.jar:e5e82da4cc66c8d917bbf743e3c0752efe8522735e7fc9dbddb65bccea81cfe9',
 			'org.apache.httpcomponents:httpmime:4.1:httpmime-4.1.jar:31629566148e8a47688ae43b420abc3ecd783ed15b33bebc00824bf24c9b15aa',
+			'org.beanshell:bsh:1.3.0:bsh-1.3.0.jar:9b04edc75d19db54f1b4e8b5355e9364384c6cf71eb0a1b9724c159d779879f8',
 			'org.bouncycastle:bcpkix-jdk15on:1.56:bcpkix-jdk15on-1.56.jar:7043dee4e9e7175e93e0b36f45b1ec1ecb893c5f755667e8b916eb8dd201c6ca',
 			'org.bouncycastle:bcprov-jdk15on:1.56:bcprov-jdk15on-1.56.jar:963e1ee14f808ffb99897d848ddcdb28fa91ddda867eb18d303e82728f878349',
 			'org.briarproject:jtorctl:0.3:jtorctl-0.3.jar:f2939238a097898998432effe93b0334d97a787972ab3a91a8973a1d309fc864',
@@ -106,17 +122,25 @@ dependencyVerification {
 			'org.glassfish.jaxb:jaxb-core:2.2.11:jaxb-core-2.2.11.jar:37bcaee8ebb04362c8352a5bf6221b86967ecdab5164c696b10b9a2bb587b2aa',
 			'org.glassfish.jaxb:jaxb-runtime:2.2.11:jaxb-runtime-2.2.11.jar:a874f2351cfba8e2946be3002d10c18a6da8f21b52ba2acf52f2b85d5520ed70',
 			'org.glassfish.jaxb:txw2:2.2.11:txw2-2.2.11.jar:272a3ccad45a4511351920cd2a8633c53cab8d5220c7a92954da5526bb5eafea',
+			'org.hamcrest:hamcrest-core:1.3:hamcrest-core-1.3.jar:66fdef91e9739348df7a096aa384a5685f4e875584cce89386a7a47251c4d8e9',
+			'org.hamcrest:hamcrest-library:1.3:hamcrest-library-1.3.jar:711d64522f9ec410983bd310934296da134be4254a125080a0416ec178dfad1c',
 			'org.jetbrains.kotlin:kotlin-reflect:1.2.0:kotlin-reflect-1.2.0.jar:4f48a872bad6e4d9c053f4ad610d11e4012ad7e58dc19a03dd5eb811f36069dd',
 			'org.jetbrains.kotlin:kotlin-stdlib-jre7:1.2.0:kotlin-stdlib-jre7-1.2.0.jar:c7a20fb951d437797afe8980aff6c1e5a03f310c661ba58ba1d4fa90cb0f2926',
 			'org.jetbrains.kotlin:kotlin-stdlib-jre8:1.2.0:kotlin-stdlib-jre8-1.2.0.jar:633524eee6ef1941f7cb1dab7ee3927b0a221ceee9047aeb5515f4cbb990c82a',
 			'org.jetbrains.kotlin:kotlin-stdlib:1.2.0:kotlin-stdlib-1.2.0.jar:05cfd9f5ac0b41910703a8925f7211a495909b27a2ffdd1c5106f1689aeafcd4',
 			'org.jetbrains.trove4j:trove4j:20160824:trove4j-20160824.jar:1917871c8deb468307a584680c87a44572f5a8b0b98c6d397fc0f5f86596dbe7',
 			'org.jetbrains:annotations:13.0:annotations-13.0.jar:ace2a10dc8e2d5fd34925ecac03e4988b2c0f851650c94b8cef49ba1bd111478',
+			'org.jmock:jmock-junit4:2.8.2:jmock-junit4-2.8.2.jar:f7ee4df4f7bd7b7f1cafad3b99eb74d579f109d5992ff625347352edb55e674c',
+			'org.jmock:jmock-legacy:2.8.2:jmock-legacy-2.8.2.jar:f2b985a5c08a9edb7f37612330c058809da3f6a6d63ce792426ebf8ff0d6d31b',
+			'org.jmock:jmock-testjar:2.8.2:jmock-testjar-2.8.2.jar:8900860f72c474e027cf97fe78dcbf154a1aa7fc62b6845c5fb4e4f3c7bc8760',
+			'org.jmock:jmock:2.8.2:jmock-2.8.2.jar:6c73cb4a2e6dbfb61fd99c9a768539c170ab6568e57846bd60dbf19596b65b16',
 			'org.jvnet.staxex:stax-ex:1.7.7:stax-ex-1.7.7.jar:a31ff7d77163c0deb09e7fee59ad35ae44c2cee2cc8552a116ccd1583d813fb4',
+			'org.objenesis:objenesis:2.1:objenesis-2.1.jar:c74330cc6b806c804fd37e74487b4fe5d7c2750c5e15fbc6efa13bdee1bdef80',
 			'org.ow2.asm:asm-analysis:5.1:asm-analysis-5.1.jar:a34658f5c5de4b573eef21131cc32cc25f7b66407944f312b28ec2e56abb1fa9',
 			'org.ow2.asm:asm-commons:5.1:asm-commons-5.1.jar:97b3786e1f55e74bddf8ad102bf50e33bbcbc1f6b7fd7b36f0bbbb25cd4981be',
 			'org.ow2.asm:asm-tree:5.1:asm-tree-5.1.jar:c0de2bbc4cb8297419659813ecd4ed1d077ed1dd5c1f5544cc5143e493e84c10',
 			'org.ow2.asm:asm-util:5.1:asm-util-5.1.jar:ee032c39ae5e3cd099148fbba9a2124f9ed613e5cb93e03ee0fa8808ce364040',
+			'org.ow2.asm:asm:5.0.4:asm-5.0.4.jar:896618ed8ae62702521a78bc7be42b7c491a08e6920a15f89a3ecdec31e9a220',
 			'org.ow2.asm:asm:5.1:asm-5.1.jar:d2da399a9967c69f0a21739256fa79d284222c223082cacadc17372244764b54',
 	]
 }
diff --git a/bramble-android/src/main/java/org/briarproject/bramble/account/AndroidAccountManager.java b/bramble-android/src/main/java/org/briarproject/bramble/account/AndroidAccountManager.java
new file mode 100644
index 0000000000000000000000000000000000000000..f23e249ae0031865ef58dea3ecd7a7026e84dc89
--- /dev/null
+++ b/bramble-android/src/main/java/org/briarproject/bramble/account/AndroidAccountManager.java
@@ -0,0 +1,108 @@
+package org.briarproject.bramble.account;
+
+import android.app.Application;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.preference.PreferenceManager;
+
+import org.briarproject.bramble.api.account.AccountManager;
+import org.briarproject.bramble.api.crypto.CryptoComponent;
+import org.briarproject.bramble.api.db.DatabaseConfig;
+import org.briarproject.bramble.api.identity.IdentityManager;
+import org.briarproject.bramble.util.IoUtils;
+
+import java.io.File;
+import java.util.logging.Logger;
+
+import javax.annotation.Nullable;
+import javax.inject.Inject;
+
+class AndroidAccountManager extends AccountManagerImpl
+		implements AccountManager {
+
+	private static final Logger LOG =
+			Logger.getLogger(AndroidAccountManager.class.getName());
+
+	private static final String PREF_DB_KEY = "key";
+
+	protected final Context appContext;
+	private final SharedPreferences prefs;
+
+	@Inject
+	AndroidAccountManager(DatabaseConfig databaseConfig,
+			CryptoComponent crypto, IdentityManager identityManager,
+			SharedPreferences prefs, Application app) {
+		super(databaseConfig, crypto, identityManager);
+		this.prefs = prefs;
+		appContext = app.getApplicationContext();
+	}
+
+	// Locking: stateChangeLock
+	@Override
+	@Nullable
+	protected String loadEncryptedDatabaseKey() {
+		String key = getDatabaseKeyFromPreferences();
+		if (key == null) key = super.loadEncryptedDatabaseKey();
+		else migrateDatabaseKeyToFile(key);
+		return key;
+	}
+
+	// Locking: stateChangeLock
+	@Nullable
+	private String getDatabaseKeyFromPreferences() {
+		String key = prefs.getString(PREF_DB_KEY, null);
+		if (key == null) LOG.info("No database key in preferences");
+		else LOG.info("Found database key in preferences");
+		return key;
+	}
+
+	// Locking: stateChangeLock
+	private void migrateDatabaseKeyToFile(String key) {
+		if (storeEncryptedDatabaseKey(key)) {
+			if (prefs.edit().remove(PREF_DB_KEY).commit())
+				LOG.info("Database key migrated to file");
+			else LOG.warning("Database key not removed from preferences");
+		} else {
+			LOG.warning("Database key not migrated to file");
+		}
+	}
+
+	@Override
+	public void deleteAccount() {
+		synchronized (stateChangeLock) {
+			super.deleteAccount();
+			SharedPreferences defaultPrefs = getDefaultSharedPreferences();
+			deleteAppData(prefs, defaultPrefs);
+		}
+	}
+
+	// Package access for testing
+	SharedPreferences getDefaultSharedPreferences() {
+		return PreferenceManager.getDefaultSharedPreferences(appContext);
+	}
+
+	// Locking: stateChangeLock
+	private void deleteAppData(SharedPreferences... clear) {
+		// Clear and commit shared preferences
+		for (SharedPreferences prefs : clear) {
+			if (!prefs.edit().clear().commit())
+				LOG.warning("Could not clear shared preferences");
+		}
+		// Delete files, except lib and shared_prefs directories
+		File dataDir = new File(appContext.getApplicationInfo().dataDir);
+		File[] children = dataDir.listFiles();
+		if (children == null) {
+			LOG.warning("Could not list files in app data dir");
+		} else {
+			for (File child : children) {
+				String name = child.getName();
+				if (!name.equals("lib") && !name.equals("shared_prefs")) {
+					IoUtils.deleteFileOrDir(child);
+				}
+			}
+		}
+		// Recreate the cache dir as some OpenGL drivers expect it to exist
+		if (!new File(dataDir, "cache").mkdir())
+			LOG.warning("Could not recreate cache dir");
+	}
+}
diff --git a/bramble-android/src/main/java/org/briarproject/bramble/plugin/tor/CircumventionProvider.java b/bramble-android/src/main/java/org/briarproject/bramble/plugin/tor/CircumventionProvider.java
index c1acfff7ee9d4219a1b2391dabfea584e25cb091..3625e206e1237f6ca880a45804151bd5b90319f4 100644
--- a/bramble-android/src/main/java/org/briarproject/bramble/plugin/tor/CircumventionProvider.java
+++ b/bramble-android/src/main/java/org/briarproject/bramble/plugin/tor/CircumventionProvider.java
@@ -4,6 +4,8 @@ import org.briarproject.bramble.api.lifecycle.IoExecutor;
 
 import java.util.List;
 
+// TODO: Create a module for this so it doesn't have to be public
+
 public interface CircumventionProvider {
 
 	/**
diff --git a/bramble-android/src/main/java/org/briarproject/bramble/plugin/tor/CircumventionProviderImpl.java b/bramble-android/src/main/java/org/briarproject/bramble/plugin/tor/CircumventionProviderImpl.java
index 6a9999b9cbf99da81290a512d57087a4eebc8bd8..bf194fa4e12d9a399a00f29e127986f062a6e314 100644
--- a/bramble-android/src/main/java/org/briarproject/bramble/plugin/tor/CircumventionProviderImpl.java
+++ b/bramble-android/src/main/java/org/briarproject/bramble/plugin/tor/CircumventionProviderImpl.java
@@ -16,6 +16,8 @@ import java.util.Set;
 import javax.annotation.Nullable;
 import javax.inject.Inject;
 
+// TODO: Create a module for this so it doesn't need to be public
+
 public class CircumventionProviderImpl implements CircumventionProvider {
 
 	private final static String BRIDGE_FILE_NAME = "bridges";
diff --git a/bramble-android/src/main/java/org/briarproject/bramble/util/AndroidUtils.java b/bramble-android/src/main/java/org/briarproject/bramble/util/AndroidUtils.java
index 9f9dd972d078b36d284c8b1fbfd7c1bfe56f8e15..5c0d073bd2da5b182026b7c3e069420a736dbda8 100644
--- a/bramble-android/src/main/java/org/briarproject/bramble/util/AndroidUtils.java
+++ b/bramble-android/src/main/java/org/briarproject/bramble/util/AndroidUtils.java
@@ -3,7 +3,6 @@ package org.briarproject.bramble.util;
 import android.annotation.SuppressLint;
 import android.bluetooth.BluetoothAdapter;
 import android.content.Context;
-import android.content.SharedPreferences;
 import android.os.Build;
 import android.provider.Settings;
 
@@ -58,30 +57,6 @@ public class AndroidUtils {
 				&& !address.equals(FAKE_BLUETOOTH_ADDRESS);
 	}
 
-	public static void deleteAppData(Context ctx, SharedPreferences... clear) {
-		// Clear and commit shared preferences
-		for (SharedPreferences prefs : clear) {
-			if (!prefs.edit().clear().commit())
-				LOG.warning("Could not clear shared preferences");
-		}
-		// Delete files, except lib and shared_prefs directories
-		File dataDir = new File(ctx.getApplicationInfo().dataDir);
-		File[] children = dataDir.listFiles();
-		if (children == null) {
-			LOG.warning("Could not list files in app data dir");
-		} else {
-			for (File child : children) {
-				String name = child.getName();
-				if (!name.equals("lib") && !name.equals("shared_prefs")) {
-					IoUtils.deleteFileOrDir(child);
-				}
-			}
-		}
-		// Recreate the cache dir as some OpenGL drivers expect it to exist
-		if (!new File(dataDir, "cache").mkdir())
-			LOG.warning("Could not recreate cache dir");
-	}
-
 	public static File getReportDir(Context ctx) {
 		return ctx.getDir(STORED_REPORTS, MODE_PRIVATE);
 	}
diff --git a/bramble-android/src/test/java/org/briarproject/bramble/account/AndroidAccountManagerTest.java b/bramble-android/src/test/java/org/briarproject/bramble/account/AndroidAccountManagerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..6736533a69fee377ddd180789dec515816a1f5c0
--- /dev/null
+++ b/bramble-android/src/test/java/org/briarproject/bramble/account/AndroidAccountManagerTest.java
@@ -0,0 +1,162 @@
+package org.briarproject.bramble.account;
+
+import android.app.Application;
+import android.content.SharedPreferences;
+import android.content.pm.ApplicationInfo;
+
+import org.briarproject.bramble.api.crypto.CryptoComponent;
+import org.briarproject.bramble.api.db.DatabaseConfig;
+import org.briarproject.bramble.api.identity.IdentityManager;
+import org.briarproject.bramble.test.BrambleMockTestCase;
+import org.jmock.Expectations;
+import org.jmock.lib.legacy.ClassImposteriser;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
+import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
+import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
+import static org.briarproject.bramble.util.StringUtils.toHexString;
+
+public class AndroidAccountManagerTest extends BrambleMockTestCase {
+
+	private final SharedPreferences prefs =
+			context.mock(SharedPreferences.class, "prefs");
+	private final SharedPreferences defaultPrefs =
+			context.mock(SharedPreferences.class, "defaultPrefs");
+	private final DatabaseConfig databaseConfig =
+			context.mock(DatabaseConfig.class);
+	private final CryptoComponent crypto = context.mock(CryptoComponent.class);
+	private final IdentityManager identityManager =
+			context.mock(IdentityManager.class);
+	private final SharedPreferences.Editor
+			editor = context.mock(SharedPreferences.Editor.class);
+	private final Application app;
+	private final ApplicationInfo applicationInfo;
+
+	private final String encryptedKeyHex = toHexString(getRandomBytes(123));
+	private final File testDir = getTestDirectory();
+	private final File keyDir = new File(testDir, "key");
+	private final File keyFile = new File(keyDir, "db.key");
+	private final File keyBackupFile = new File(keyDir, "db.key.bak");
+	private final File dbDir = new File(testDir, "db");
+
+	private AndroidAccountManager accountManager;
+
+	public AndroidAccountManagerTest() {
+		context.setImposteriser(ClassImposteriser.INSTANCE);
+		app = context.mock(Application.class);
+		applicationInfo = new ApplicationInfo();
+		applicationInfo.dataDir = testDir.getAbsolutePath();
+	}
+
+	@Before
+	public void setUp() {
+		context.checking(new Expectations() {{
+			allowing(databaseConfig).getDatabaseDirectory();
+			will(returnValue(dbDir));
+			allowing(databaseConfig).getDatabaseKeyDirectory();
+			will(returnValue(keyDir));
+			allowing(app).getApplicationContext();
+			will(returnValue(app));
+		}});
+		accountManager = new AndroidAccountManager(databaseConfig, crypto,
+				identityManager, prefs, app) {
+			@Override
+			SharedPreferences getDefaultSharedPreferences() {
+				return defaultPrefs;
+			}
+		};
+	}
+
+	@Test
+	public void testDbKeyIsMigratedFromPreferencesToFile() {
+		context.checking(new Expectations() {{
+			oneOf(prefs).getString("key", null);
+			will(returnValue(encryptedKeyHex));
+			oneOf(prefs).edit();
+			will(returnValue(editor));
+			oneOf(editor).remove("key");
+			will(returnValue(editor));
+			oneOf(editor).commit();
+			will(returnValue(true));
+		}});
+
+		assertFalse(keyFile.exists());
+		assertFalse(keyBackupFile.exists());
+
+		assertEquals(encryptedKeyHex,
+				accountManager.loadEncryptedDatabaseKey());
+
+		assertTrue(keyFile.exists());
+		assertTrue(keyBackupFile.exists());
+	}
+
+	@Test
+	public void testDeleteAccountClearsSharedPrefsAndDeletesFiles()
+			throws Exception {
+		// Directories 'lib' and 'shared_prefs' should be spared
+		File libDir = new File(testDir, "lib");
+		File libFile = new File(libDir, "file");
+		File sharedPrefsDir = new File(testDir, "shared_prefs");
+		File sharedPrefsFile = new File(sharedPrefsDir, "file");
+		// Directory 'cache' should be emptied
+		File cacheDir = new File(testDir, "cache");
+		File cacheFile = new File(cacheDir, "file");
+		// Other directories should be deleted
+		File potatoDir = new File(testDir, ".potato");
+		File potatoFile = new File(potatoDir, "file");
+
+		context.checking(new Expectations() {{
+			oneOf(prefs).edit();
+			will(returnValue(editor));
+			oneOf(editor).clear();
+			will(returnValue(editor));
+			oneOf(editor).commit();
+			will(returnValue(true));
+			oneOf(defaultPrefs).edit();
+			will(returnValue(editor));
+			oneOf(editor).clear();
+			will(returnValue(editor));
+			oneOf(editor).commit();
+			will(returnValue(true));
+			oneOf(app).getApplicationInfo();
+			will(returnValue(applicationInfo));
+		}});
+
+		assertTrue(dbDir.mkdirs());
+		assertTrue(keyDir.mkdirs());
+		assertTrue(libDir.mkdirs());
+		assertTrue(libFile.createNewFile());
+		assertTrue(sharedPrefsDir.mkdirs());
+		assertTrue(sharedPrefsFile.createNewFile());
+		assertTrue(cacheDir.mkdirs());
+		assertTrue(cacheFile.createNewFile());
+		assertTrue(potatoDir.mkdirs());
+		assertTrue(potatoFile.createNewFile());
+
+		accountManager.deleteAccount();
+
+		assertFalse(dbDir.exists());
+		assertFalse(keyDir.exists());
+		assertTrue(libDir.exists());
+		assertTrue(libFile.exists());
+		assertTrue(sharedPrefsDir.exists());
+		assertTrue(sharedPrefsFile.exists());
+		assertTrue(cacheDir.exists());
+		assertFalse(cacheFile.exists());
+		assertFalse(potatoDir.exists());
+		assertFalse(potatoFile.exists());
+	}
+
+	@After
+	public void tearDown() {
+		deleteTestDirectory(testDir);
+	}
+}
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/account/AccountManager.java b/bramble-api/src/main/java/org/briarproject/bramble/api/account/AccountManager.java
new file mode 100644
index 0000000000000000000000000000000000000000..2e1b5a9510066907b55c83621c613fe8c2616366
--- /dev/null
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/account/AccountManager.java
@@ -0,0 +1,70 @@
+package org.briarproject.bramble.api.account;
+
+import org.briarproject.bramble.api.crypto.SecretKey;
+import org.briarproject.bramble.api.identity.IdentityManager;
+import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
+
+import javax.annotation.Nullable;
+
+@NotNullByDefault
+public interface AccountManager {
+
+	/**
+	 * Returns true if the manager has the database key. This will be false
+	 * before {@link #createAccount(String, String)} or {@link #signIn(String)}
+	 * has been called, and true after {@link #createAccount(String, String)}
+	 * or {@link #signIn(String)} has returned true, until the process exits.
+	 */
+	boolean hasDatabaseKey();
+
+	/**
+	 * Returns the database key if the manager has it. This will be null
+	 * before {@link #createAccount(String, String)} or {@link #signIn(String)}
+	 * has been called, and non-null after
+	 * {@link #createAccount(String, String)} or {@link #signIn(String)} has
+	 * returned true, until the process exits.
+	 */
+	@Nullable
+	SecretKey getDatabaseKey();
+
+	/**
+	 * Returns true if the encrypted database key can be loaded from disk, and
+	 * the database directory exists and is a directory.
+	 */
+	boolean accountExists();
+
+	/**
+	 * Creates an identity with the given name and registers it with the
+	 * {@link IdentityManager}. Creates a database key, encrypts it with the
+	 * given password and stores it on disk.
+	 * <p/>
+	 * This method does not create the database directory, so
+	 * {@link #accountExists()} will continue to return false until the
+	 * database directory is created.
+	 */
+	boolean createAccount(String name, String password);
+
+	/**
+	 * Deletes all account state from disk. {@link #accountExists()} will
+	 * return false after this method returns.
+	 */
+	void deleteAccount();
+
+	/**
+	 * Loads the encrypted database key from disk and decrypts it with the
+	 * given password.
+	 *
+	 * @return true if the database key was successfully loaded and decrypted.
+	 */
+	boolean signIn(String password);
+
+	/**
+	 * Loads the encrypted database key from disk, decrypts it with the old
+	 * password, encrypts it with the new password, and stores it on disk,
+	 * replacing the old key.
+	 *
+	 * @return true if the database key was successfully loaded, re-encrypted
+	 * and stored.
+	 */
+	boolean changePassword(String oldPassword, String newPassword);
+}
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/contact/ContactManager.java b/bramble-api/src/main/java/org/briarproject/bramble/api/contact/ContactManager.java
index 1f9daf48afc077a71ca87fa1a0a00de4db46cd99..517dcb89f3647d9d95b9c90fe3acc2461b442407 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/contact/ContactManager.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/contact/ContactManager.java
@@ -16,7 +16,7 @@ public interface ContactManager {
 	/**
 	 * Registers a hook to be called whenever a contact is added or removed.
 	 * This method should be called before
-	 * {@link LifecycleManager#startServices(String)}.
+	 * {@link LifecycleManager#startServices(SecretKey)}.
 	 */
 	void registerContactHook(ContactHook hook);
 
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseComponent.java b/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseComponent.java
index 914b88a2e2d1db1a8bafa328850ca07dd80ed189..f4161cee44ef19c37a4604c5ad6c24a585e36f09 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseComponent.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseComponent.java
@@ -2,6 +2,7 @@ package org.briarproject.bramble.api.db;
 
 import org.briarproject.bramble.api.contact.Contact;
 import org.briarproject.bramble.api.contact.ContactId;
+import org.briarproject.bramble.api.crypto.SecretKey;
 import org.briarproject.bramble.api.identity.Author;
 import org.briarproject.bramble.api.identity.AuthorId;
 import org.briarproject.bramble.api.identity.LocalAuthor;
@@ -44,7 +45,8 @@ public interface DatabaseComponent {
 	 * @throws DataTooOldException if the data uses an older schema than the
 	 * current code and cannot be migrated
 	 */
-	boolean open(@Nullable MigrationListener listener) throws DbException;
+	boolean open(SecretKey key, @Nullable MigrationListener listener)
+			throws DbException;
 
 	/**
 	 * Waits for any open transactions to finish and closes the database.
@@ -267,7 +269,7 @@ public interface DatabaseComponent {
 	 * Read-only.
 	 */
 	Collection<MessageId> getMessageIds(Transaction txn, GroupId g)
-		throws DbException;
+			throws DbException;
 
 	/**
 	 * Returns the IDs of any messages that need to be validated.
@@ -487,7 +489,7 @@ public interface DatabaseComponent {
 	 * Removes the given transport keys from the database.
 	 */
 	void removeTransportKeys(Transaction txn, TransportId t, KeySetId k)
-		throws DbException;
+			throws DbException;
 
 	/**
 	 * Marks the given contact as verified.
@@ -534,7 +536,7 @@ public interface DatabaseComponent {
 	 * Marks the given transport keys as usable for outgoing streams.
 	 */
 	void setTransportKeysActive(Transaction txn, TransportId t, KeySetId k)
-		throws DbException;
+			throws DbException;
 
 	/**
 	 * Stores the given transport keys, deleting any keys they have replaced.
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseConfig.java b/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseConfig.java
index 0be85eba93a8ab16a915e5574cc4b0329ea8112b..f096f1fec8e99185d0679846e536613525e38ec0 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseConfig.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseConfig.java
@@ -1,30 +1,15 @@
 package org.briarproject.bramble.api.db;
 
-import org.briarproject.bramble.api.crypto.SecretKey;
 import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
 
 import java.io.File;
 
-import javax.annotation.Nullable;
-
 @NotNullByDefault
 public interface DatabaseConfig {
 
-	boolean databaseExists();
-
 	File getDatabaseDirectory();
 
 	File getDatabaseKeyDirectory();
 
-	void setEncryptionKey(SecretKey key);
-
-	@Nullable
-	SecretKey getEncryptionKey();
-
-	void setLocalAuthorName(String nickname);
-
-	@Nullable
-	String getLocalAuthorName();
-
 	long getMaxSize();
 }
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/identity/IdentityManager.java b/bramble-api/src/main/java/org/briarproject/bramble/api/identity/IdentityManager.java
index bb584fec45dc2f9734aff102718ef412577dafa4..8160eb06d3bd5dc93ee8babad28436962a50b078 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/identity/IdentityManager.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/identity/IdentityManager.java
@@ -1,5 +1,6 @@
 package org.briarproject.bramble.api.identity;
 
+import org.briarproject.bramble.api.crypto.CryptoExecutor;
 import org.briarproject.bramble.api.db.DbException;
 import org.briarproject.bramble.api.db.Transaction;
 import org.briarproject.bramble.api.identity.Author.Status;
@@ -9,29 +10,40 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
 public interface IdentityManager {
 
 	/**
-	 * Stores the local pseudonym.
+	 * Creates a local identity with the given name.
 	 */
-	void registerLocalAuthor(LocalAuthor a) throws DbException;
+	@CryptoExecutor
+	LocalAuthor createLocalAuthor(String name);
 
 	/**
-	 * Returns the cached main local identity, non-blocking, or loads it from
-	 * the db, blocking
+	 * Registers the given local identity with the manager. The identity is
+	 * not stored until {@link #storeLocalAuthor()} is called.
+	 */
+	void registerLocalAuthor(LocalAuthor a);
+
+	/**
+	 * Stores the local identity registered with
+	 * {@link #registerLocalAuthor(LocalAuthor)}, if any.
+	 */
+	void storeLocalAuthor() throws DbException;
+
+	/**
+	 * Returns the cached local identity or loads it from the database.
 	 */
 	LocalAuthor getLocalAuthor() throws DbException;
 
 	/**
-	 * Returns the cached main local identity, non-blocking, or loads it from
-	 * the db, blocking, within the given Transaction.
+	 * Returns the cached local identity or loads it from the database.
 	 */
 	LocalAuthor getLocalAuthor(Transaction txn) throws DbException;
 
 	/**
-	 * Returns the trust-level status of the author
+	 * Returns the {@link Status} of the given author.
 	 */
 	Status getAuthorStatus(AuthorId a) throws DbException;
 
 	/**
-	 * Returns the trust-level status of the author
+	 * Returns the {@link Status} of the given author.
 	 */
 	Status getAuthorStatus(Transaction txn, AuthorId a) throws DbException;
 
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/lifecycle/LifecycleManager.java b/bramble-api/src/main/java/org/briarproject/bramble/api/lifecycle/LifecycleManager.java
index d3cd1bc245f33b855d522f47b32f0501689668b7..c44cf8879e4bc117f14ad244155f2142023d14af 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/lifecycle/LifecycleManager.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/lifecycle/LifecycleManager.java
@@ -1,13 +1,12 @@
 package org.briarproject.bramble.api.lifecycle;
 
+import org.briarproject.bramble.api.crypto.SecretKey;
 import org.briarproject.bramble.api.db.DatabaseComponent;
 import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
 import org.briarproject.bramble.api.sync.Client;
 
 import java.util.concurrent.ExecutorService;
 
-import javax.annotation.Nullable;
-
 /**
  * Manages the lifecycle of the app, starting {@link Client Clients}, starting
  * and stopping {@link Service Services}, shutting down
@@ -18,7 +17,7 @@ import javax.annotation.Nullable;
 public interface LifecycleManager {
 
 	/**
-	 * The result of calling {@link #startServices(String)}.
+	 * The result of calling {@link #startServices(SecretKey)}.
 	 */
 	enum StartResult {
 		ALREADY_RUNNING,
@@ -44,28 +43,27 @@ public interface LifecycleManager {
 
 	/**
 	 * Registers a {@link Service} to be started and stopped. This method
-	 * should be called before {@link #startServices(String)}.
+	 * should be called before {@link #startServices(SecretKey)}.
 	 */
 	void registerService(Service s);
 
 	/**
 	 * Registers a {@link Client} to be started. This method should be called
-	 * before {@link #startServices(String)}.
+	 * before {@link #startServices(SecretKey)}.
 	 */
 	void registerClient(Client c);
 
 	/**
 	 * Registers an {@link ExecutorService} to be shut down. This method
-	 * should be called before {@link #startServices(String)}.
+	 * should be called before {@link #startServices(SecretKey)}.
 	 */
 	void registerForShutdown(ExecutorService e);
 
 	/**
-	 * Opens the {@link DatabaseComponent}, optionally creates a local author
-	 * with the provided nickname, and starts any registered
-	 * {@link Client Clients} and {@link Service Services}.
+	 * Opens the {@link DatabaseComponent} using the given key and starts any
+	 * registered {@link Client Clients} and {@link Service Services}.
 	 */
-	StartResult startServices(@Nullable String nickname);
+	StartResult startServices(SecretKey dbKey);
 
 	/**
 	 * Stops any registered {@link Service Services}, shuts down any
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/ValidationManager.java b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/ValidationManager.java
index 1718cac8101fc0b9a03719219b5bdaad23f72d84..3b5c90342e73968df59c660f49fe52e55c39abe1 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/ValidationManager.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/ValidationManager.java
@@ -1,5 +1,6 @@
 package org.briarproject.bramble.api.sync;
 
+import org.briarproject.bramble.api.crypto.SecretKey;
 import org.briarproject.bramble.api.db.DbException;
 import org.briarproject.bramble.api.db.Metadata;
 import org.briarproject.bramble.api.db.Transaction;
@@ -35,7 +36,8 @@ public interface ValidationManager {
 
 	/**
 	 * Registers the message validator for the given client. This method
-	 * should be called before {@link LifecycleManager#startServices(String)}.
+	 * should be called before
+	 * {@link LifecycleManager#startServices(SecretKey)}.
 	 */
 	void registerMessageValidator(ClientId c, int majorVersion,
 			MessageValidator v);
@@ -44,7 +46,7 @@ public interface ValidationManager {
 	 * Registers the incoming message hook for the given client. The hook will
 	 * be called once for each incoming message that passes validation. This
 	 * method should be called before
-	 * {@link LifecycleManager#startServices(String)}.
+	 * {@link LifecycleManager#startServices(SecretKey)}.
 	 */
 	void registerIncomingMessageHook(ClientId c, int majorVersion,
 			IncomingMessageHook hook);
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/versioning/ClientVersioningManager.java b/bramble-api/src/main/java/org/briarproject/bramble/api/versioning/ClientVersioningManager.java
index 0cb2fc478d34206fa5b3fa3420d2c0486b747a42..00b3d672cf85aac26b559df0d92f412c27ff9445 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/versioning/ClientVersioningManager.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/versioning/ClientVersioningManager.java
@@ -2,6 +2,7 @@ package org.briarproject.bramble.api.versioning;
 
 import org.briarproject.bramble.api.contact.Contact;
 import org.briarproject.bramble.api.contact.ContactId;
+import org.briarproject.bramble.api.crypto.SecretKey;
 import org.briarproject.bramble.api.db.DbException;
 import org.briarproject.bramble.api.db.Transaction;
 import org.briarproject.bramble.api.lifecycle.LifecycleManager;
@@ -25,7 +26,7 @@ public interface ClientVersioningManager {
 	/**
 	 * Registers a client that will be advertised to contacts. The hook will
 	 * be called when the visibility of the client changes. This method should
-	 * be called before {@link LifecycleManager#startServices(String)}.
+	 * be called before {@link LifecycleManager#startServices(SecretKey)}.
 	 */
 	void registerClient(ClientId clientId, int majorVersion, int minorVersion,
 			ClientVersioningHook hook);
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/account/AccountManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/account/AccountManagerImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..7eda4688be9d8e0cdf87abf0dcbb1dcc99a83124
--- /dev/null
+++ b/bramble-core/src/main/java/org/briarproject/bramble/account/AccountManagerImpl.java
@@ -0,0 +1,221 @@
+package org.briarproject.bramble.account;
+
+import org.briarproject.bramble.api.account.AccountManager;
+import org.briarproject.bramble.api.crypto.CryptoComponent;
+import org.briarproject.bramble.api.crypto.SecretKey;
+import org.briarproject.bramble.api.db.DatabaseConfig;
+import org.briarproject.bramble.api.identity.IdentityManager;
+import org.briarproject.bramble.api.identity.LocalAuthor;
+import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
+import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
+import org.briarproject.bramble.util.IoUtils;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.logging.Logger;
+
+import javax.annotation.Nullable;
+import javax.inject.Inject;
+
+import static java.util.logging.Level.WARNING;
+import static org.briarproject.bramble.util.LogUtils.logException;
+import static org.briarproject.bramble.util.StringUtils.fromHexString;
+import static org.briarproject.bramble.util.StringUtils.toHexString;
+
+@MethodsNotNullByDefault
+@ParametersNotNullByDefault
+class AccountManagerImpl implements AccountManager {
+
+	private static final Logger LOG =
+			Logger.getLogger(AccountManagerImpl.class.getName());
+
+	private static final String DB_KEY_FILENAME = "db.key";
+	private static final String DB_KEY_BACKUP_FILENAME = "db.key.bak";
+
+	private final DatabaseConfig databaseConfig;
+	private final CryptoComponent crypto;
+	private final IdentityManager identityManager;
+	private final File dbKeyFile, dbKeyBackupFile;
+
+	final Object stateChangeLock = new Object();
+
+	@Nullable
+	private volatile SecretKey databaseKey = null;
+
+	@Inject
+	AccountManagerImpl(DatabaseConfig databaseConfig, CryptoComponent crypto,
+			IdentityManager identityManager) {
+		this.databaseConfig = databaseConfig;
+		this.crypto = crypto;
+		this.identityManager = identityManager;
+		File keyDir = databaseConfig.getDatabaseKeyDirectory();
+		dbKeyFile = new File(keyDir, DB_KEY_FILENAME);
+		dbKeyBackupFile = new File(keyDir, DB_KEY_BACKUP_FILENAME);
+	}
+
+	@Override
+	public boolean hasDatabaseKey() {
+		return databaseKey != null;
+	}
+
+	@Override
+	@Nullable
+	public SecretKey getDatabaseKey() {
+		return databaseKey;
+	}
+
+	// Locking: stateChangeLock
+	@Nullable
+	protected String loadEncryptedDatabaseKey() {
+		String key = readDbKeyFromFile(dbKeyFile);
+		if (key == null) {
+			LOG.info("No database key in primary file");
+			key = readDbKeyFromFile(dbKeyBackupFile);
+			if (key == null) LOG.info("No database key in backup file");
+			else LOG.warning("Found database key in backup file");
+		} else {
+			LOG.info("Found database key in primary file");
+		}
+		return key;
+	}
+
+	// Locking: stateChangeLock
+	@Nullable
+	private String readDbKeyFromFile(File f) {
+		if (!f.exists()) {
+			LOG.info("Key file does not exist");
+			return null;
+		}
+		try {
+			BufferedReader reader = new BufferedReader(new InputStreamReader(
+					new FileInputStream(f), "UTF-8"));
+			String key = reader.readLine();
+			reader.close();
+			return key;
+		} catch (IOException e) {
+			logException(LOG, WARNING, e);
+			return null;
+		}
+	}
+
+	// Locking: stateChangeLock
+	protected boolean storeEncryptedDatabaseKey(String hex) {
+		LOG.info("Storing database key in file");
+		// Create the directory if necessary
+		if (databaseConfig.getDatabaseKeyDirectory().mkdirs())
+			LOG.info("Created database key directory");
+		// If only the backup file exists, rename it so we don't overwrite it
+		if (dbKeyBackupFile.exists() && !dbKeyFile.exists()) {
+			if (dbKeyBackupFile.renameTo(dbKeyFile))
+				LOG.info("Renamed old backup");
+			else LOG.warning("Failed to rename old backup");
+		}
+		try {
+			// Write to the backup file
+			writeDbKeyToFile(hex, dbKeyBackupFile);
+			LOG.info("Stored database key in backup file");
+			// Delete the old primary file, if it exists
+			if (dbKeyFile.exists()) {
+				if (dbKeyFile.delete()) LOG.info("Deleted primary file");
+				else LOG.warning("Failed to delete primary file");
+			}
+			// The backup file becomes the new primary
+			if (dbKeyBackupFile.renameTo(dbKeyFile)) {
+				LOG.info("Renamed backup file to primary");
+			} else {
+				LOG.warning("Failed to rename backup file to primary");
+				return false; // Don't overwrite our only copy
+			}
+			// Write a second copy to the backup file
+			writeDbKeyToFile(hex, dbKeyBackupFile);
+			LOG.info("Stored second copy of database key in backup file");
+			return true;
+		} catch (IOException e) {
+			logException(LOG, WARNING, e);
+			return false;
+		}
+	}
+
+	// Locking: stateChangeLock
+	private void writeDbKeyToFile(String key, File f) throws IOException {
+		FileOutputStream out = new FileOutputStream(f);
+		out.write(key.getBytes("UTF-8"));
+		out.flush();
+		out.close();
+	}
+
+	@Override
+	public boolean accountExists() {
+		synchronized (stateChangeLock) {
+			return loadEncryptedDatabaseKey() != null
+					&& databaseConfig.getDatabaseDirectory().isDirectory();
+		}
+	}
+
+	@Override
+	public boolean createAccount(String name, String password) {
+		synchronized (stateChangeLock) {
+			LocalAuthor localAuthor = identityManager.createLocalAuthor(name);
+			identityManager.registerLocalAuthor(localAuthor);
+			SecretKey key = crypto.generateSecretKey();
+			if (!encryptAndStoreDatabaseKey(key, password)) return false;
+			databaseKey = key;
+			return true;
+		}
+	}
+
+	// Locking: stateChangeLock
+	private boolean encryptAndStoreDatabaseKey(SecretKey key, String password) {
+		byte[] plaintext = key.getBytes();
+		byte[] ciphertext = crypto.encryptWithPassword(plaintext, password);
+		return storeEncryptedDatabaseKey(toHexString(ciphertext));
+	}
+
+	@Override
+	public void deleteAccount() {
+		synchronized (stateChangeLock) {
+			LOG.info("Deleting account");
+			IoUtils.deleteFileOrDir(databaseConfig.getDatabaseKeyDirectory());
+			IoUtils.deleteFileOrDir(databaseConfig.getDatabaseDirectory());
+		}
+	}
+
+	@Override
+	public boolean signIn(String password) {
+		synchronized (stateChangeLock) {
+			SecretKey key = loadAndDecryptDatabaseKey(password);
+			if (key == null) return false;
+			databaseKey = key;
+			return true;
+		}
+	}
+
+	// Locking: stateChangeLock
+	@Nullable
+	private SecretKey loadAndDecryptDatabaseKey(String password) {
+		String hex = loadEncryptedDatabaseKey();
+		if (hex == null) {
+			LOG.warning("Failed to load encrypted database key");
+			return null;
+		}
+		byte[] ciphertext = fromHexString(hex);
+		byte[] plaintext = crypto.decryptWithPassword(ciphertext, password);
+		if (plaintext == null) {
+			LOG.info("Failed to decrypt database key");
+			return null;
+		}
+		return new SecretKey(plaintext);
+	}
+
+	@Override
+	public boolean changePassword(String oldPassword, String newPassword) {
+		synchronized (stateChangeLock) {
+			SecretKey key = loadAndDecryptDatabaseKey(oldPassword);
+			return key != null && encryptAndStoreDatabaseKey(key, newPassword);
+		}
+	}
+}
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/account/AccountModule.java b/bramble-core/src/main/java/org/briarproject/bramble/account/AccountModule.java
new file mode 100644
index 0000000000000000000000000000000000000000..e8220176a8ab5863328988945125138ff89c2124
--- /dev/null
+++ b/bramble-core/src/main/java/org/briarproject/bramble/account/AccountModule.java
@@ -0,0 +1,18 @@
+package org.briarproject.bramble.account;
+
+import org.briarproject.bramble.api.account.AccountManager;
+
+import javax.inject.Singleton;
+
+import dagger.Module;
+import dagger.Provides;
+
+@Module
+public class AccountModule {
+
+	@Provides
+	@Singleton
+	AccountManager provideAccountManager(AccountManagerImpl accountManager) {
+		return accountManager;
+	}
+}
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/db/Database.java b/bramble-core/src/main/java/org/briarproject/bramble/db/Database.java
index 66e5cfcf6d72ec90eed0dffa4bbf56c51d38b071..796e4bbe40720e23a9e6ab98b8e5559fb4c4cd8d 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/db/Database.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/db/Database.java
@@ -2,6 +2,7 @@ package org.briarproject.bramble.db;
 
 import org.briarproject.bramble.api.contact.Contact;
 import org.briarproject.bramble.api.contact.ContactId;
+import org.briarproject.bramble.api.crypto.SecretKey;
 import org.briarproject.bramble.api.db.DataTooNewException;
 import org.briarproject.bramble.api.db.DataTooOldException;
 import org.briarproject.bramble.api.db.DbException;
@@ -48,7 +49,8 @@ interface Database<T> {
 	 * @throws DataTooOldException if the data uses an older schema than the
 	 * current code and cannot be migrated
 	 */
-	boolean open(@Nullable MigrationListener listener) throws DbException;
+	boolean open(SecretKey key, @Nullable MigrationListener listener)
+			throws DbException;
 
 	/**
 	 * Prevents new transactions from starting, waits for all current
@@ -641,7 +643,7 @@ interface Database<T> {
 	 * Marks the given transport keys as usable for outgoing streams.
 	 */
 	void setTransportKeysActive(T txn, TransportId t, KeySetId k)
-		throws DbException;
+			throws DbException;
 
 	/**
 	 * Updates the transmission count and expiry time of the given message
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java
index f2e4de1c6fff7cbd3fbc6e92f52dead50c34b458..2913c783f4407344a04ab9e63d8b87bafbd2863f 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java
@@ -6,6 +6,7 @@ import org.briarproject.bramble.api.contact.event.ContactAddedEvent;
 import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
 import org.briarproject.bramble.api.contact.event.ContactStatusChangedEvent;
 import org.briarproject.bramble.api.contact.event.ContactVerifiedEvent;
+import org.briarproject.bramble.api.crypto.SecretKey;
 import org.briarproject.bramble.api.db.ContactExistsException;
 import org.briarproject.bramble.api.db.DatabaseComponent;
 import org.briarproject.bramble.api.db.DbException;
@@ -103,9 +104,9 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
 	}
 
 	@Override
-	public boolean open(@Nullable MigrationListener listener)
+	public boolean open(SecretKey key, @Nullable MigrationListener listener)
 			throws DbException {
-		boolean reopened = db.open(listener);
+		boolean reopened = db.open(key, listener);
 		shutdown.addShutdownHook(() -> {
 			try {
 				close();
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/db/H2Database.java b/bramble-core/src/main/java/org/briarproject/bramble/db/H2Database.java
index 1c1983e6771a2cc174ebcbf68ede5879aab7569f..f400d1ce984c7c485fb50a8828fb3c273fa482aa 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/db/H2Database.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/db/H2Database.java
@@ -32,6 +32,9 @@ class H2Database extends JdbcDatabase {
 	private final DatabaseConfig config;
 	private final String url;
 
+	@Nullable
+	private volatile SecretKey key = null;
+
 	@Inject
 	H2Database(DatabaseConfig config, Clock clock) {
 		super(HASH_TYPE, SECRET_TYPE, BINARY_TYPE, COUNTER_TYPE, STRING_TYPE,
@@ -44,11 +47,11 @@ class H2Database extends JdbcDatabase {
 	}
 
 	@Override
-	public boolean open(@Nullable MigrationListener listener)
+	public boolean open(SecretKey key, @Nullable MigrationListener listener)
 			throws DbException {
-		boolean reopen = config.databaseExists();
-		if (!reopen) config.getDatabaseDirectory().mkdirs();
-		super.open("org.h2.Driver", reopen, listener);
+		this.key = key;
+		boolean reopen = !config.getDatabaseDirectory().mkdirs();
+		super.open("org.h2.Driver", reopen, key, listener);
 		return reopen;
 	}
 
@@ -63,7 +66,7 @@ class H2Database extends JdbcDatabase {
 	}
 
 	@Override
-	public long getFreeSpace() throws DbException {
+	public long getFreeSpace() {
 		File dir = config.getDatabaseDirectory();
 		long maxSize = config.getMaxSize();
 		long free = dir.getFreeSpace();
@@ -88,7 +91,7 @@ class H2Database extends JdbcDatabase {
 
 	@Override
 	protected Connection createConnection() throws SQLException {
-		SecretKey key = config.getEncryptionKey();
+		SecretKey key = this.key;
 		if (key == null) throw new IllegalStateException();
 		Properties props = new Properties();
 		props.setProperty("user", "user");
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/db/HyperSqlDatabase.java b/bramble-core/src/main/java/org/briarproject/bramble/db/HyperSqlDatabase.java
index 6a87ededae7b862c992745f15ac5695f881a6cf0..447537d8c8098a730e3c7ea217f1e7f001dd5c63 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/db/HyperSqlDatabase.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/db/HyperSqlDatabase.java
@@ -33,6 +33,9 @@ class HyperSqlDatabase extends JdbcDatabase {
 	private final DatabaseConfig config;
 	private final String url;
 
+	@Nullable
+	private volatile SecretKey key = null;
+
 	@Inject
 	HyperSqlDatabase(DatabaseConfig config, Clock clock) {
 		super(HASH_TYPE, SECRET_TYPE, BINARY_TYPE, COUNTER_TYPE, STRING_TYPE,
@@ -46,10 +49,11 @@ class HyperSqlDatabase extends JdbcDatabase {
 	}
 
 	@Override
-	public boolean open(@Nullable MigrationListener listener) throws DbException {
-		boolean reopen = config.databaseExists();
-		if (!reopen) config.getDatabaseDirectory().mkdirs();
-		super.open("org.hsqldb.jdbc.JDBCDriver", reopen, listener);
+	public boolean open(SecretKey key, @Nullable MigrationListener listener)
+			throws DbException {
+		this.key = key;
+		boolean reopen = !config.getDatabaseDirectory().mkdirs();
+		super.open("org.hsqldb.jdbc.JDBCDriver", reopen, key, listener);
 		return reopen;
 	}
 
@@ -93,7 +97,7 @@ class HyperSqlDatabase extends JdbcDatabase {
 
 	@Override
 	protected Connection createConnection() throws SQLException {
-		SecretKey key = config.getEncryptionKey();
+		SecretKey key = this.key;
 		if (key == null) throw new IllegalStateException();
 		String hex = StringUtils.toHexString(key.getBytes());
 		return DriverManager.getConnection(url + ";crypt_key=" + hex);
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java b/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java
index baa67a29ac6397c6c7e26b17a18feb455924c111..fd248f811e9b8c703ead29088b49317409792785 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java
@@ -328,7 +328,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 		this.clock = clock;
 	}
 
-	protected void open(String driverClass, boolean reopen,
+	protected void open(String driverClass, boolean reopen, SecretKey key,
 			@Nullable MigrationListener listener) throws DbException {
 		// Load the JDBC driver
 		try {
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/identity/IdentityManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/identity/IdentityManagerImpl.java
index 11e8220c9c5a72206604e24968eaeeaa388807af..44bf94550620c9bfcf3ce9a2772d4e511c4d6857 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/identity/IdentityManagerImpl.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/identity/IdentityManagerImpl.java
@@ -1,10 +1,13 @@
 package org.briarproject.bramble.identity;
 
 import org.briarproject.bramble.api.contact.Contact;
+import org.briarproject.bramble.api.crypto.CryptoComponent;
+import org.briarproject.bramble.api.crypto.KeyPair;
 import org.briarproject.bramble.api.db.DatabaseComponent;
 import org.briarproject.bramble.api.db.DbException;
 import org.briarproject.bramble.api.db.Transaction;
 import org.briarproject.bramble.api.identity.Author.Status;
+import org.briarproject.bramble.api.identity.AuthorFactory;
 import org.briarproject.bramble.api.identity.AuthorId;
 import org.briarproject.bramble.api.identity.IdentityManager;
 import org.briarproject.bramble.api.identity.LocalAuthor;
@@ -21,6 +24,8 @@ import static org.briarproject.bramble.api.identity.Author.Status.OURSELVES;
 import static org.briarproject.bramble.api.identity.Author.Status.UNKNOWN;
 import static org.briarproject.bramble.api.identity.Author.Status.UNVERIFIED;
 import static org.briarproject.bramble.api.identity.Author.Status.VERIFIED;
+import static org.briarproject.bramble.util.LogUtils.logDuration;
+import static org.briarproject.bramble.util.LogUtils.now;
 
 @ThreadSafe
 @NotNullByDefault
@@ -30,25 +35,51 @@ class IdentityManagerImpl implements IdentityManager {
 			Logger.getLogger(IdentityManagerImpl.class.getName());
 
 	private final DatabaseComponent db;
+	private final CryptoComponent crypto;
+	private final AuthorFactory authorFactory;
 
 	// The local author is immutable so we can cache it
 	@Nullable
 	private volatile LocalAuthor cachedAuthor;
 
 	@Inject
-	IdentityManagerImpl(DatabaseComponent db) {
+	IdentityManagerImpl(DatabaseComponent db, CryptoComponent crypto,
+			AuthorFactory authorFactory) {
 		this.db = db;
+		this.crypto = crypto;
+		this.authorFactory = authorFactory;
 	}
 
 	@Override
-	public void registerLocalAuthor(LocalAuthor localAuthor)
-			throws DbException {
+	public LocalAuthor createLocalAuthor(String name) {
+		long start = now();
+		KeyPair keyPair = crypto.generateSignatureKeyPair();
+		byte[] publicKey = keyPair.getPublic().getEncoded();
+		byte[] privateKey = keyPair.getPrivate().getEncoded();
+		LocalAuthor localAuthor = authorFactory.createLocalAuthor(name,
+				publicKey, privateKey);
+		logDuration(LOG, "Creating local author", start);
+		return localAuthor;
+	}
+
+	@Override
+	public void registerLocalAuthor(LocalAuthor a) {
+		cachedAuthor = a;
+		LOG.info("Local author registered");
+	}
+
+	@Override
+	public void storeLocalAuthor() throws DbException {
+		LocalAuthor cached = cachedAuthor;
+		if (cached == null) {
+			LOG.info("No local author to store");
+			return;
+		}
 		Transaction txn = db.startTransaction(false);
 		try {
-			db.addLocalAuthor(txn, localAuthor);
+			db.addLocalAuthor(txn, cached);
 			db.commitTransaction(txn);
-			cachedAuthor = localAuthor;
-			LOG.info("Local author registered");
+			LOG.info("Local author stored");
 		} finally {
 			db.endTransaction(txn);
 		}
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/lifecycle/LifecycleManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/lifecycle/LifecycleManagerImpl.java
index 68c694189b663a9fda79f5614f428ef876e74640..130595ab63b3ac235af747a4a4ec8662cc07733e 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/lifecycle/LifecycleManagerImpl.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/lifecycle/LifecycleManagerImpl.java
@@ -1,7 +1,6 @@
 package org.briarproject.bramble.lifecycle;
 
-import org.briarproject.bramble.api.crypto.CryptoComponent;
-import org.briarproject.bramble.api.crypto.KeyPair;
+import org.briarproject.bramble.api.crypto.SecretKey;
 import org.briarproject.bramble.api.db.DataTooNewException;
 import org.briarproject.bramble.api.db.DataTooOldException;
 import org.briarproject.bramble.api.db.DatabaseComponent;
@@ -9,9 +8,7 @@ import org.briarproject.bramble.api.db.DbException;
 import org.briarproject.bramble.api.db.MigrationListener;
 import org.briarproject.bramble.api.db.Transaction;
 import org.briarproject.bramble.api.event.EventBus;
-import org.briarproject.bramble.api.identity.AuthorFactory;
 import org.briarproject.bramble.api.identity.IdentityManager;
-import org.briarproject.bramble.api.identity.LocalAuthor;
 import org.briarproject.bramble.api.lifecycle.LifecycleManager;
 import org.briarproject.bramble.api.lifecycle.Service;
 import org.briarproject.bramble.api.lifecycle.ServiceException;
@@ -26,7 +23,6 @@ import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Semaphore;
 import java.util.logging.Logger;
 
-import javax.annotation.Nullable;
 import javax.annotation.concurrent.ThreadSafe;
 import javax.inject.Inject;
 
@@ -60,8 +56,6 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
 	private final List<Service> services;
 	private final List<Client> clients;
 	private final List<ExecutorService> executors;
-	private final CryptoComponent crypto;
-	private final AuthorFactory authorFactory;
 	private final IdentityManager identityManager;
 	private final Semaphore startStopSemaphore = new Semaphore(1);
 	private final CountDownLatch dbLatch = new CountDownLatch(1);
@@ -72,12 +66,9 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
 
 	@Inject
 	LifecycleManagerImpl(DatabaseComponent db, EventBus eventBus,
-			CryptoComponent crypto, AuthorFactory authorFactory,
 			IdentityManager identityManager) {
 		this.db = db;
 		this.eventBus = eventBus;
-		this.crypto = crypto;
-		this.authorFactory = authorFactory;
 		this.identityManager = identityManager;
 		services = new CopyOnWriteArrayList<>();
 		clients = new CopyOnWriteArrayList<>();
@@ -104,25 +95,8 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
 		executors.add(e);
 	}
 
-	private LocalAuthor createLocalAuthor(String nickname) {
-		long start = now();
-		KeyPair keyPair = crypto.generateSignatureKeyPair();
-		byte[] publicKey = keyPair.getPublic().getEncoded();
-		byte[] privateKey = keyPair.getPrivate().getEncoded();
-		LocalAuthor localAuthor = authorFactory
-				.createLocalAuthor(nickname, publicKey, privateKey);
-		logDuration(LOG, "Creating local author", start);
-		return localAuthor;
-	}
-
-	private void registerLocalAuthor(LocalAuthor author) throws DbException {
-		long start = now();
-		identityManager.registerLocalAuthor(author);
-		logDuration(LOG, "Registering local author", start);
-	}
-
 	@Override
-	public StartResult startServices(@Nullable String nickname) {
+	public StartResult startServices(SecretKey dbKey) {
 		if (!startStopSemaphore.tryAcquire()) {
 			LOG.info("Already starting or stopping");
 			return ALREADY_RUNNING;
@@ -131,13 +105,10 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
 			LOG.info("Starting services");
 			long start = now();
 
-			boolean reopened = db.open(this);
+			boolean reopened = db.open(dbKey, this);
 			if (reopened) logDuration(LOG, "Reopening database", start);
 			else logDuration(LOG, "Creating database", start);
-
-			if (nickname != null) {
-				registerLocalAuthor(createLocalAuthor(nickname));
-			}
+			identityManager.storeLocalAuthor();
 
 			state = STARTING_SERVICES;
 			dbLatch.countDown();
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/lifecycle/LifecycleModule.java b/bramble-core/src/main/java/org/briarproject/bramble/lifecycle/LifecycleModule.java
index 8fcf789ed91d725b1dbb14ae2493324f785ca7d6..43db266efb188d5b3a3c2bbe90d48e194f7b1990 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/lifecycle/LifecycleModule.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/lifecycle/LifecycleModule.java
@@ -1,10 +1,5 @@
 package org.briarproject.bramble.lifecycle;
 
-import org.briarproject.bramble.api.crypto.CryptoComponent;
-import org.briarproject.bramble.api.db.DatabaseComponent;
-import org.briarproject.bramble.api.event.EventBus;
-import org.briarproject.bramble.api.identity.AuthorFactory;
-import org.briarproject.bramble.api.identity.IdentityManager;
 import org.briarproject.bramble.api.lifecycle.IoExecutor;
 import org.briarproject.bramble.api.lifecycle.LifecycleManager;
 import org.briarproject.bramble.api.lifecycle.ShutdownManager;
@@ -54,11 +49,9 @@ public class LifecycleModule {
 
 	@Provides
 	@Singleton
-	LifecycleManager provideLifecycleManager(DatabaseComponent db,
-			EventBus eventBus, CryptoComponent crypto,
-			AuthorFactory authorFactory, IdentityManager identityManager) {
-		return new LifecycleManagerImpl(db, eventBus, crypto, authorFactory,
-				identityManager);
+	LifecycleManager provideLifecycleManager(
+			LifecycleManagerImpl lifecycleManager) {
+		return lifecycleManager;
 	}
 
 	@Provides
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/account/AccountManagerImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/account/AccountManagerImplTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..84c5da72f4a70d79bb8c45254d4436fc6ea0afc8
--- /dev/null
+++ b/bramble-core/src/test/java/org/briarproject/bramble/account/AccountManagerImplTest.java
@@ -0,0 +1,341 @@
+package org.briarproject.bramble.account;
+
+import org.briarproject.bramble.api.crypto.CryptoComponent;
+import org.briarproject.bramble.api.crypto.SecretKey;
+import org.briarproject.bramble.api.db.DatabaseConfig;
+import org.briarproject.bramble.api.identity.IdentityManager;
+import org.briarproject.bramble.api.identity.LocalAuthor;
+import org.briarproject.bramble.test.BrambleMockTestCase;
+import org.jmock.Expectations;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+
+import javax.annotation.Nullable;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
+import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
+import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
+import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
+import static org.briarproject.bramble.test.TestUtils.getSecretKey;
+import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
+import static org.briarproject.bramble.util.StringUtils.getRandomString;
+import static org.briarproject.bramble.util.StringUtils.toHexString;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+public class AccountManagerImplTest extends BrambleMockTestCase {
+
+	private final DatabaseConfig databaseConfig =
+			context.mock(DatabaseConfig.class);
+	private final CryptoComponent crypto = context.mock(CryptoComponent.class);
+	private final IdentityManager identityManager =
+			context.mock(IdentityManager.class);
+
+	private final SecretKey key = getSecretKey();
+	private final byte[] encryptedKey = getRandomBytes(123);
+	private final String encryptedKeyHex = toHexString(encryptedKey);
+	private final byte[] newEncryptedKey = getRandomBytes(123);
+	private final String newEncryptedKeyHex = toHexString(newEncryptedKey);
+	private final LocalAuthor localAuthor = getLocalAuthor();
+	private final String authorName = localAuthor.getName();
+	private final String password = getRandomString(10);
+	private final String newPassword = getRandomString(10);
+	private final File testDir = getTestDirectory();
+	private final File dbDir = new File(testDir, "db");
+	private final File keyDir = new File(testDir, "key");
+	private final File keyFile = new File(keyDir, "db.key");
+	private final File keyBackupFile = new File(keyDir, "db.key.bak");
+
+	private AccountManagerImpl accountManager;
+
+	@Before
+	public void setUp() {
+		context.checking(new Expectations() {{
+			allowing(databaseConfig).getDatabaseDirectory();
+			will(returnValue(dbDir));
+			allowing(databaseConfig).getDatabaseKeyDirectory();
+			will(returnValue(keyDir));
+		}});
+
+		accountManager =
+				new AccountManagerImpl(databaseConfig, crypto, identityManager);
+
+		assertFalse(keyFile.exists());
+		assertFalse(keyBackupFile.exists());
+	}
+
+	@Test
+	public void testSignInReturnsFalseIfDbKeyCannotBeLoaded() {
+		assertFalse(accountManager.signIn(password));
+		assertFalse(accountManager.hasDatabaseKey());
+
+		assertFalse(keyFile.exists());
+		assertFalse(keyBackupFile.exists());
+	}
+
+	@Test
+	public void testSignInReturnsFalseIfPasswordIsWrong() throws Exception {
+		context.checking(new Expectations() {{
+			oneOf(crypto).decryptWithPassword(encryptedKey, password);
+			will(returnValue(null));
+		}});
+
+		storeDatabaseKey(keyFile, encryptedKeyHex);
+		storeDatabaseKey(keyBackupFile, encryptedKeyHex);
+
+		assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
+		assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
+
+		assertFalse(accountManager.signIn(password));
+		assertFalse(accountManager.hasDatabaseKey());
+
+		assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
+		assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
+	}
+
+	@Test
+	public void testSignInReturnsTrueIfPasswordIsRight() throws Exception {
+		context.checking(new Expectations() {{
+			oneOf(crypto).decryptWithPassword(encryptedKey, password);
+			will(returnValue(key.getBytes()));
+		}});
+
+		storeDatabaseKey(keyFile, encryptedKeyHex);
+		storeDatabaseKey(keyBackupFile, encryptedKeyHex);
+
+		assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
+		assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
+
+		assertTrue(accountManager.signIn(password));
+		assertTrue(accountManager.hasDatabaseKey());
+		SecretKey decrypted = accountManager.getDatabaseKey();
+		assertNotNull(decrypted);
+		assertArrayEquals(key.getBytes(), decrypted.getBytes());
+
+		assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
+		assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
+	}
+
+	@Test
+	public void testDbKeyIsLoadedFromPrimaryFile() throws Exception {
+		storeDatabaseKey(keyFile, encryptedKeyHex);
+
+		assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
+		assertFalse(keyBackupFile.exists());
+
+		assertEquals(encryptedKeyHex,
+				accountManager.loadEncryptedDatabaseKey());
+
+		assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
+		assertFalse(keyBackupFile.exists());
+	}
+
+	@Test
+	public void testDbKeyIsLoadedFromBackupFile() throws Exception {
+		storeDatabaseKey(keyBackupFile, encryptedKeyHex);
+
+		assertFalse(keyFile.exists());
+		assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
+
+		assertEquals(encryptedKeyHex,
+				accountManager.loadEncryptedDatabaseKey());
+
+		assertFalse(keyFile.exists());
+		assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
+	}
+
+	@Test
+	public void testDbKeyIsNullIfNotFound() {
+		assertNull(accountManager.loadEncryptedDatabaseKey());
+
+		assertFalse(keyFile.exists());
+		assertFalse(keyBackupFile.exists());
+	}
+
+	@Test
+	public void testStoringDbKeyOverwritesPrimary() throws Exception {
+		storeDatabaseKey(keyFile, encryptedKeyHex);
+
+		assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
+		assertFalse(keyBackupFile.exists());
+
+		assertTrue(accountManager.storeEncryptedDatabaseKey(
+				newEncryptedKeyHex));
+
+		assertEquals(newEncryptedKeyHex, loadDatabaseKey(keyFile));
+		assertEquals(newEncryptedKeyHex, loadDatabaseKey(keyBackupFile));
+	}
+
+	@Test
+	public void testStoringDbKeyOverwritesBackup() throws Exception {
+		storeDatabaseKey(keyBackupFile, encryptedKeyHex);
+
+		assertFalse(keyFile.exists());
+		assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
+
+		assertTrue(accountManager.storeEncryptedDatabaseKey(
+				newEncryptedKeyHex));
+
+		assertEquals(newEncryptedKeyHex, loadDatabaseKey(keyFile));
+		assertEquals(newEncryptedKeyHex, loadDatabaseKey(keyBackupFile));
+	}
+
+	@Test
+	public void testAccountExistsReturnsFalseIfDbKeyCannotBeLoaded() {
+		assertFalse(accountManager.accountExists());
+
+		assertFalse(keyFile.exists());
+		assertFalse(keyBackupFile.exists());
+	}
+
+	@Test
+	public void testAccountExistsReturnsFalseIfDbDirectoryDoesNotExist()
+			throws Exception {
+		storeDatabaseKey(keyFile, encryptedKeyHex);
+		storeDatabaseKey(keyBackupFile, encryptedKeyHex);
+
+		assertFalse(dbDir.exists());
+
+		assertFalse(accountManager.accountExists());
+
+		assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
+		assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
+		assertFalse(dbDir.exists());
+	}
+
+	@Test
+	public void testAccountExistsReturnsFalseIfDbDirectoryIsNotDirectory()
+			throws Exception {
+		storeDatabaseKey(keyFile, encryptedKeyHex);
+		storeDatabaseKey(keyBackupFile, encryptedKeyHex);
+
+		assertTrue(dbDir.createNewFile());
+		assertFalse(dbDir.isDirectory());
+
+		assertFalse(accountManager.accountExists());
+
+		assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
+		assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
+		assertTrue(dbDir.exists());
+		assertFalse(dbDir.isDirectory());
+	}
+
+	@Test
+	public void testAccountExistsReturnsTrueIfDbDirectoryIsDirectory()
+			throws Exception {
+		storeDatabaseKey(keyFile, encryptedKeyHex);
+		storeDatabaseKey(keyBackupFile, encryptedKeyHex);
+
+		assertTrue(dbDir.mkdirs());
+		assertTrue(dbDir.isDirectory());
+
+		assertTrue(accountManager.accountExists());
+
+		assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
+		assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
+		assertTrue(dbDir.exists());
+		assertTrue(dbDir.isDirectory());
+	}
+
+	@Test
+	public void testCreateAccountStoresDbKey() throws Exception {
+		context.checking(new Expectations() {{
+			oneOf(identityManager).createLocalAuthor(authorName);
+			will(returnValue(localAuthor));
+			oneOf(identityManager).registerLocalAuthor(localAuthor);
+			oneOf(crypto).generateSecretKey();
+			will(returnValue(key));
+			oneOf(crypto).encryptWithPassword(key.getBytes(), password);
+			will(returnValue(encryptedKey));
+		}});
+
+		assertFalse(accountManager.hasDatabaseKey());
+
+		assertTrue(accountManager.createAccount(authorName, password));
+
+		assertTrue(accountManager.hasDatabaseKey());
+		SecretKey dbKey = accountManager.getDatabaseKey();
+		assertNotNull(dbKey);
+		assertArrayEquals(key.getBytes(), dbKey.getBytes());
+
+		assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
+		assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
+	}
+
+	@Test
+	public void testChangePasswordReturnsFalseIfDbKeyCannotBeLoaded() {
+		assertFalse(accountManager.changePassword(password, newPassword));
+
+		assertFalse(keyFile.exists());
+		assertFalse(keyBackupFile.exists());
+	}
+
+	@Test
+	public void testChangePasswordReturnsFalseIfPasswordIsWrong()
+			throws Exception {
+		context.checking(new Expectations() {{
+			oneOf(crypto).decryptWithPassword(encryptedKey, password);
+			will(returnValue(null));
+		}});
+
+		storeDatabaseKey(keyFile, encryptedKeyHex);
+		storeDatabaseKey(keyBackupFile, encryptedKeyHex);
+
+		assertFalse(accountManager.changePassword(password, newPassword));
+
+		assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
+		assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
+	}
+
+	@Test
+	public void testChangePasswordReturnsTrueIfPasswordIsRight()
+			throws Exception {
+		context.checking(new Expectations() {{
+			oneOf(crypto).decryptWithPassword(encryptedKey, password);
+			will(returnValue(key.getBytes()));
+			oneOf(crypto).encryptWithPassword(key.getBytes(), newPassword);
+			will(returnValue(newEncryptedKey));
+		}});
+
+		storeDatabaseKey(keyFile, encryptedKeyHex);
+		storeDatabaseKey(keyBackupFile, encryptedKeyHex);
+
+		assertTrue(accountManager.changePassword(password, newPassword));
+
+		assertEquals(newEncryptedKeyHex, loadDatabaseKey(keyFile));
+		assertEquals(newEncryptedKeyHex, loadDatabaseKey(keyBackupFile));
+	}
+
+	private void storeDatabaseKey(File f, String hex) throws IOException {
+		f.getParentFile().mkdirs();
+		FileOutputStream out = new FileOutputStream(f);
+		out.write(hex.getBytes("UTF-8"));
+		out.flush();
+		out.close();
+	}
+
+	@Nullable
+	private String loadDatabaseKey(File f) throws IOException {
+		BufferedReader reader = new BufferedReader(new InputStreamReader(
+				new FileInputStream(f), "UTF-8"));
+		String hex = reader.readLine();
+		reader.close();
+		return hex;
+	}
+
+	@After
+	public void tearDown() {
+		deleteTestDirectory(testDir);
+	}
+}
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/db/DatabaseComponentImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/db/DatabaseComponentImplTest.java
index e16e03da0dc51086f73c9c6ed45ee81483f8a3b1..676cac2e3708b0465afe2bc08ccaf3ff7a308988 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/db/DatabaseComponentImplTest.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/db/DatabaseComponentImplTest.java
@@ -89,6 +89,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
 			context.mock(ShutdownManager.class);
 	private final EventBus eventBus = context.mock(EventBus.class);
 
+	private final SecretKey key = getSecretKey();
 	private final Object txn = new Object();
 	private final ClientId clientId;
 	private final int majorVersion;
@@ -141,7 +142,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
 		int shutdownHandle = 12345;
 		context.checking(new Expectations() {{
 			// open()
-			oneOf(database).open(null);
+			oneOf(database).open(key, null);
 			will(returnValue(false));
 			oneOf(shutdown).addShutdownHook(with(any(Runnable.class)));
 			will(returnValue(shutdownHandle));
@@ -208,7 +209,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
 		DatabaseComponent db = createDatabaseComponent(database, eventBus,
 				shutdown);
 
-		assertFalse(db.open(null));
+		assertFalse(db.open(key, null));
 		Transaction transaction = db.startTransaction(false);
 		try {
 			db.addLocalAuthor(transaction, localAuthor);
@@ -1602,7 +1603,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
 		MessageId messageId2 = new MessageId(getRandomId());
 		context.checking(new Expectations() {{
 			// open()
-			oneOf(database).open(null);
+			oneOf(database).open(key, null);
 			will(returnValue(false));
 			oneOf(shutdown).addShutdownHook(with(any(Runnable.class)));
 			will(returnValue(shutdownHandle));
@@ -1646,7 +1647,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
 		DatabaseComponent db = createDatabaseComponent(database, eventBus,
 				shutdown);
 
-		assertFalse(db.open(null));
+		assertFalse(db.open(key, null));
 		Transaction transaction = db.startTransaction(false);
 		try {
 			db.addLocalMessage(transaction, message, metadata, true);
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/db/DatabaseMigrationTest.java b/bramble-core/src/test/java/org/briarproject/bramble/db/DatabaseMigrationTest.java
index 89bdf8a4c1a8da94963bc41c7473be59b3f7c063..0740bdfc2301bff10330aba7dc99114936f0ace3 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/db/DatabaseMigrationTest.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/db/DatabaseMigrationTest.java
@@ -1,5 +1,6 @@
 package org.briarproject.bramble.db;
 
+import org.briarproject.bramble.api.crypto.SecretKey;
 import org.briarproject.bramble.api.db.DataTooNewException;
 import org.briarproject.bramble.api.db.DataTooOldException;
 import org.briarproject.bramble.api.db.DatabaseConfig;
@@ -26,6 +27,7 @@ import static java.util.Collections.singletonList;
 import static org.briarproject.bramble.db.DatabaseConstants.DB_SETTINGS_NAMESPACE;
 import static org.briarproject.bramble.db.DatabaseConstants.SCHEMA_VERSION_KEY;
 import static org.briarproject.bramble.db.JdbcDatabase.CODE_SCHEMA_VERSION;
+import static org.briarproject.bramble.test.TestUtils.getSecretKey;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -43,6 +45,7 @@ public abstract class DatabaseMigrationTest extends BrambleMockTestCase {
 
 	protected final DatabaseConfig config =
 			new TestDatabaseConfig(testDir, 1024 * 1024);
+	protected final SecretKey key = getSecretKey();
 	protected final Clock clock = new SystemClock();
 
 	abstract Database<Connection> createDatabase(
@@ -62,7 +65,7 @@ public abstract class DatabaseMigrationTest extends BrambleMockTestCase {
 	public void testDoesNotRunMigrationsWhenCreatingDatabase()
 			throws Exception {
 		Database<Connection> db = createDatabase(singletonList(migration));
-		assertFalse(db.open(null));
+		assertFalse(db.open(key, null));
 		assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
 		db.close();
 	}
@@ -72,14 +75,14 @@ public abstract class DatabaseMigrationTest extends BrambleMockTestCase {
 			throws Exception {
 		// Open the DB for the first time
 		Database<Connection> db = createDatabase(asList(migration, migration1));
-		assertFalse(db.open(null));
+		assertFalse(db.open(key, null));
 		assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
 		// Override the data schema version
 		setDataSchemaVersion(db, -1);
 		db.close();
 		// Reopen the DB - an exception should be thrown
 		db = createDatabase(asList(migration, migration1));
-		db.open(null);
+		db.open(key, null);
 	}
 
 	@Test
@@ -87,12 +90,12 @@ public abstract class DatabaseMigrationTest extends BrambleMockTestCase {
 			throws Exception {
 		// Open the DB for the first time
 		Database<Connection> db = createDatabase(asList(migration, migration1));
-		assertFalse(db.open(null));
+		assertFalse(db.open(key, null));
 		assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
 		db.close();
 		// Reopen the DB - migrations should not be run
 		db = createDatabase(asList(migration, migration1));
-		assertTrue(db.open(null));
+		assertTrue(db.open(key, null));
 		assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
 		db.close();
 	}
@@ -101,14 +104,14 @@ public abstract class DatabaseMigrationTest extends BrambleMockTestCase {
 	public void testThrowsExceptionIfDataIsNewerThanCode() throws Exception {
 		// Open the DB for the first time
 		Database<Connection> db = createDatabase(asList(migration, migration1));
-		assertFalse(db.open(null));
+		assertFalse(db.open(key, null));
 		assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
 		// Override the data schema version
 		setDataSchemaVersion(db, CODE_SCHEMA_VERSION + 1);
 		db.close();
 		// Reopen the DB - an exception should be thrown
 		db = createDatabase(asList(migration, migration1));
-		db.open(null);
+		db.open(key, null);
 	}
 
 	@Test(expected = DataTooOldException.class)
@@ -116,13 +119,13 @@ public abstract class DatabaseMigrationTest extends BrambleMockTestCase {
 			throws Exception {
 		// Open the DB for the first time
 		Database<Connection> db = createDatabase(emptyList());
-		assertFalse(db.open(null));
+		assertFalse(db.open(key, null));
 		assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
 		setDataSchemaVersion(db, CODE_SCHEMA_VERSION - 1);
 		db.close();
 		// Reopen the DB - an exception should be thrown
 		db = createDatabase(emptyList());
-		db.open(null);
+		db.open(key, null);
 	}
 
 	@Test(expected = DataTooOldException.class)
@@ -141,14 +144,14 @@ public abstract class DatabaseMigrationTest extends BrambleMockTestCase {
 
 		// Open the DB for the first time
 		Database<Connection> db = createDatabase(asList(migration, migration1));
-		assertFalse(db.open(null));
+		assertFalse(db.open(key, null));
 		assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
 		// Override the data schema version
 		setDataSchemaVersion(db, CODE_SCHEMA_VERSION - 3);
 		db.close();
 		// Reopen the DB - an exception should be thrown
 		db = createDatabase(asList(migration, migration1));
-		db.open(null);
+		db.open(key, null);
 	}
 
 	@Test
@@ -170,14 +173,14 @@ public abstract class DatabaseMigrationTest extends BrambleMockTestCase {
 
 		// Open the DB for the first time
 		Database<Connection> db = createDatabase(asList(migration, migration1));
-		assertFalse(db.open(null));
+		assertFalse(db.open(key, null));
 		assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
 		// Override the data schema version
 		setDataSchemaVersion(db, CODE_SCHEMA_VERSION - 2);
 		db.close();
 		// Reopen the DB - the first migration should be run
 		db = createDatabase(asList(migration, migration1));
-		assertTrue(db.open(null));
+		assertTrue(db.open(key, null));
 		assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
 		db.close();
 	}
@@ -202,14 +205,14 @@ public abstract class DatabaseMigrationTest extends BrambleMockTestCase {
 
 		// Open the DB for the first time
 		Database<Connection> db = createDatabase(asList(migration, migration1));
-		assertFalse(db.open(null));
+		assertFalse(db.open(key, null));
 		assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
 		// Override the data schema version
 		setDataSchemaVersion(db, CODE_SCHEMA_VERSION - 2);
 		db.close();
 		// Reopen the DB - both migrations should be run
 		db = createDatabase(asList(migration, migration1));
-		assertTrue(db.open(null));
+		assertTrue(db.open(key, null));
 		assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
 		db.close();
 	}
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/db/DatabasePerformanceComparisonTest.java b/bramble-core/src/test/java/org/briarproject/bramble/db/DatabasePerformanceComparisonTest.java
index 1807d30dc33374654a740b4acb58336bee1f5b52..279d1b56e6e0ae44fdb1929a37154a7af6c854e9 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/db/DatabasePerformanceComparisonTest.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/db/DatabasePerformanceComparisonTest.java
@@ -15,6 +15,7 @@ import java.util.List;
 import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
 import static org.briarproject.bramble.test.TestUtils.getMean;
 import static org.briarproject.bramble.test.TestUtils.getMedian;
+import static org.briarproject.bramble.test.TestUtils.getSecretKey;
 import static org.briarproject.bramble.test.TestUtils.getStandardDeviation;
 import static org.briarproject.bramble.test.UTest.Z_CRITICAL_0_01;
 
@@ -71,7 +72,7 @@ public abstract class DatabasePerformanceComparisonTest
 			throws DbException {
 		Database<Connection> db = createDatabase(conditionA,
 				new TestDatabaseConfig(testDir, MAX_SIZE), new SystemClock());
-		db.open(null);
+		db.open(getSecretKey(), null);
 		return db;
 	}
 
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/db/DatabaseTraceTest.java b/bramble-core/src/test/java/org/briarproject/bramble/db/DatabaseTraceTest.java
index f4e0daa19dedf3d6e0187a4aa1b7e8d181136906..2060ca71ec35807190e04518fe123e257fbc6a59 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/db/DatabaseTraceTest.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/db/DatabaseTraceTest.java
@@ -16,6 +16,7 @@ import java.sql.Connection;
 import javax.annotation.Nullable;
 
 import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
+import static org.briarproject.bramble.test.TestUtils.getSecretKey;
 
 public abstract class DatabaseTraceTest extends DatabasePerformanceTest {
 
@@ -43,7 +44,7 @@ public abstract class DatabaseTraceTest extends DatabasePerformanceTest {
 	private Database<Connection> openDatabase() throws DbException {
 		Database<Connection> db = createDatabase(
 				new TestDatabaseConfig(testDir, MAX_SIZE), new SystemClock());
-		db.open(null);
+		db.open(getSecretKey(), null);
 		return db;
 	}
 
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/db/H2MigrationTest.java b/bramble-core/src/test/java/org/briarproject/bramble/db/H2MigrationTest.java
index 6a868fd42fa9ff2098087fbf9aa5b5c9f3d346c9..29bdbfa5f5b962a427ffdeeef8f9f94429be8ca4 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/db/H2MigrationTest.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/db/H2MigrationTest.java
@@ -9,8 +9,8 @@ import java.util.List;
 public class H2MigrationTest extends DatabaseMigrationTest {
 
 	@Override
-	Database<Connection> createDatabase(List<Migration<Connection>> migrations)
-			throws Exception {
+	Database<Connection> createDatabase(
+			List<Migration<Connection>> migrations) {
 		return new H2Database(config, clock) {
 			@Override
 			List<Migration<Connection>> getMigrations() {
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/db/JdbcDatabaseTest.java b/bramble-core/src/test/java/org/briarproject/bramble/db/JdbcDatabaseTest.java
index 2655e9ef7f571cdbcc80abb2a492e3b0e7c5bdcd..e88038258ad7007b909eb499cc5243eaee11d398 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/db/JdbcDatabaseTest.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/db/JdbcDatabaseTest.java
@@ -64,6 +64,7 @@ import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
 import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
 import static org.briarproject.bramble.test.TestUtils.getRandomId;
 import static org.briarproject.bramble.test.TestUtils.getSecretKey;
+import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
 import static org.briarproject.bramble.test.TestUtils.getTransportId;
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
@@ -79,7 +80,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
 	private static final int ONE_MEGABYTE = 1024 * 1024;
 	private static final int MAX_SIZE = 5 * ONE_MEGABYTE;
 
-	private final File testDir = TestUtils.getTestDirectory();
+	private final SecretKey key = getSecretKey();
+	private final File testDir = getTestDirectory();
 	private final GroupId groupId;
 	private final ClientId clientId;
 	private final int majorVersion;
@@ -96,7 +98,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
 	private final KeySetId keySetId, keySetId1;
 	private final Random random = new Random();
 
-	JdbcDatabaseTest() throws Exception {
+	JdbcDatabaseTest() {
 		clientId = getClientId();
 		majorVersion = 123;
 		group = getGroup(clientId, majorVersion);
@@ -1819,7 +1821,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
 		Database<Connection> db = createDatabase(
 				new TestDatabaseConfig(testDir, MAX_SIZE), clock);
 		if (!resume) TestUtils.deleteTestDirectory(testDir);
-		db.open(null);
+		db.open(key, null);
 		return db;
 	}
 
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/db/SingleDatabasePerformanceTest.java b/bramble-core/src/test/java/org/briarproject/bramble/db/SingleDatabasePerformanceTest.java
index 670c7cddb79746be691317387a0c779d0c6b5213..6256d39cd07c51d5ee2bba18983fdecef20f41c1 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/db/SingleDatabasePerformanceTest.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/db/SingleDatabasePerformanceTest.java
@@ -13,6 +13,7 @@ import java.util.List;
 import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
 import static org.briarproject.bramble.test.TestUtils.getMean;
 import static org.briarproject.bramble.test.TestUtils.getMedian;
+import static org.briarproject.bramble.test.TestUtils.getSecretKey;
 import static org.briarproject.bramble.test.TestUtils.getStandardDeviation;
 
 public abstract class SingleDatabasePerformanceTest
@@ -40,7 +41,7 @@ public abstract class SingleDatabasePerformanceTest
 	private Database<Connection> openDatabase() throws DbException {
 		Database<Connection> db = createDatabase(
 				new TestDatabaseConfig(testDir, MAX_SIZE), new SystemClock());
-		db.open(null);
+		db.open(getSecretKey(), null);
 		return db;
 	}
 
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/identity/IdentityManagerImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/identity/IdentityManagerImplTest.java
index e24499ba7f8be40b8a8e685510b4582bf19bdd3a..2926cd3f3edb00a88e05e0fa78bbe04c875b70b0 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/identity/IdentityManagerImplTest.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/identity/IdentityManagerImplTest.java
@@ -2,15 +2,21 @@ package org.briarproject.bramble.identity;
 
 import org.briarproject.bramble.api.contact.Contact;
 import org.briarproject.bramble.api.contact.ContactId;
+import org.briarproject.bramble.api.crypto.CryptoComponent;
+import org.briarproject.bramble.api.crypto.KeyPair;
+import org.briarproject.bramble.api.crypto.PrivateKey;
+import org.briarproject.bramble.api.crypto.PublicKey;
 import org.briarproject.bramble.api.db.DatabaseComponent;
 import org.briarproject.bramble.api.db.DbException;
 import org.briarproject.bramble.api.db.Transaction;
 import org.briarproject.bramble.api.identity.Author;
+import org.briarproject.bramble.api.identity.AuthorFactory;
 import org.briarproject.bramble.api.identity.AuthorId;
 import org.briarproject.bramble.api.identity.IdentityManager;
 import org.briarproject.bramble.api.identity.LocalAuthor;
 import org.briarproject.bramble.test.BrambleMockTestCase;
 import org.jmock.Expectations;
+import org.junit.Before;
 import org.junit.Test;
 
 import java.util.ArrayList;
@@ -27,24 +33,48 @@ import static org.junit.Assert.assertEquals;
 
 public class IdentityManagerImplTest extends BrambleMockTestCase {
 
-	private final IdentityManager identityManager;
 	private final DatabaseComponent db = context.mock(DatabaseComponent.class);
+	private final CryptoComponent crypto = context.mock(CryptoComponent.class);
+	private final AuthorFactory authorFactory =
+			context.mock(AuthorFactory.class);
+	private final PublicKey publicKey = context.mock(PublicKey.class);
+	private final PrivateKey privateKey = context.mock(PrivateKey.class);
+
 	private final Transaction txn = new Transaction(null, false);
 	private final LocalAuthor localAuthor = getLocalAuthor();
 	private final Collection<LocalAuthor> localAuthors =
 			Collections.singletonList(localAuthor);
-
-	public IdentityManagerImplTest() {
-		identityManager = new IdentityManagerImpl(db);
+	private final String authorName = localAuthor.getName();
+	private final KeyPair keyPair = new KeyPair(publicKey, privateKey);
+	private final byte[] publicKeyBytes = localAuthor.getPublicKey();
+	private final byte[] privateKeyBytes = localAuthor.getPrivateKey();
+	private IdentityManager identityManager;
+
+	@Before
+	public void setUp() {
+		identityManager = new IdentityManagerImpl(db, crypto, authorFactory);
 	}
 
 	@Test
-	public void testRegisterLocalAuthor() throws DbException {
-		expectRegisterLocalAuthor();
-		identityManager.registerLocalAuthor(localAuthor);
+	public void testCreateLocalAuthor() {
+		context.checking(new Expectations() {{
+			oneOf(crypto).generateSignatureKeyPair();
+			will(returnValue(keyPair));
+			oneOf(publicKey).getEncoded();
+			will(returnValue(publicKeyBytes));
+			oneOf(privateKey).getEncoded();
+			will(returnValue(privateKeyBytes));
+			oneOf(authorFactory).createLocalAuthor(authorName,
+					publicKeyBytes, privateKeyBytes);
+			will(returnValue(localAuthor));
+		}});
+
+		assertEquals(localAuthor,
+				identityManager.createLocalAuthor(authorName));
 	}
 
-	private void expectRegisterLocalAuthor() throws DbException {
+	@Test
+	public void testRegisterAndStoreLocalAuthor() throws DbException {
 		context.checking(new Expectations() {{
 			oneOf(db).startTransaction(false);
 			will(returnValue(txn));
@@ -52,6 +82,10 @@ public class IdentityManagerImplTest extends BrambleMockTestCase {
 			oneOf(db).commitTransaction(txn);
 			oneOf(db).endTransaction(txn);
 		}});
+
+		identityManager.registerLocalAuthor(localAuthor);
+		assertEquals(localAuthor, identityManager.getLocalAuthor());
+		identityManager.storeLocalAuthor();
 	}
 
 	@Test
@@ -69,7 +103,6 @@ public class IdentityManagerImplTest extends BrambleMockTestCase {
 
 	@Test
 	public void testGetCachedLocalAuthor() throws DbException {
-		expectRegisterLocalAuthor();
 		identityManager.registerLocalAuthor(localAuthor);
 		assertEquals(localAuthor, identityManager.getLocalAuthor());
 	}
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/test/TestDatabaseConfig.java b/bramble-core/src/test/java/org/briarproject/bramble/test/TestDatabaseConfig.java
index a830795f3dd4d28fecb7876df4965d543de1fb5c..1c626b483beb0fe426c8e7e9268f264b353cead9 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/test/TestDatabaseConfig.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/test/TestDatabaseConfig.java
@@ -1,6 +1,5 @@
 package org.briarproject.bramble.test;
 
-import org.briarproject.bramble.api.crypto.SecretKey;
 import org.briarproject.bramble.api.db.DatabaseConfig;
 import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
 
@@ -11,7 +10,6 @@ public class TestDatabaseConfig implements DatabaseConfig {
 
 	private final File dbDir, keyDir;
 	private final long maxSize;
-	private volatile SecretKey key = new SecretKey(new byte[SecretKey.LENGTH]);
 
 	public TestDatabaseConfig(File testDir, long maxSize) {
 		dbDir = new File(testDir, "db");
@@ -19,13 +17,6 @@ public class TestDatabaseConfig implements DatabaseConfig {
 		this.maxSize = maxSize;
 	}
 
-	@Override
-	public boolean databaseExists() {
-		if (!dbDir.isDirectory()) return false;
-		File[] files = dbDir.listFiles();
-		return files != null && files.length > 0;
-	}
-
 	@Override
 	public File getDatabaseDirectory() {
 		return dbDir;
@@ -36,26 +27,6 @@ public class TestDatabaseConfig implements DatabaseConfig {
 		return keyDir;
 	}
 
-	@Override
-	public void setEncryptionKey(SecretKey key) {
-		this.key = key;
-	}
-
-	@Override
-	public SecretKey getEncryptionKey() {
-		return key;
-	}
-
-	@Override
-	public void setLocalAuthorName(String nickname) {
-
-	}
-
-	@Override
-	public String getLocalAuthorName() {
-		return null;
-	}
-
 	@Override
 	public long getMaxSize() {
 		return maxSize;
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/test/TestLifecycleModule.java b/bramble-core/src/test/java/org/briarproject/bramble/test/TestLifecycleModule.java
index 15943adc2b6dabfa86fc8ebb72d270ad396a00ed..21f6ccfe9f8e1020542c806c746387c2f29b8db6 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/test/TestLifecycleModule.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/test/TestLifecycleModule.java
@@ -1,5 +1,6 @@
 package org.briarproject.bramble.test;
 
+import org.briarproject.bramble.api.crypto.SecretKey;
 import org.briarproject.bramble.api.lifecycle.IoExecutor;
 import org.briarproject.bramble.api.lifecycle.LifecycleManager;
 import org.briarproject.bramble.api.lifecycle.Service;
@@ -11,7 +12,6 @@ import java.util.concurrent.Executor;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 
-import javax.annotation.Nullable;
 import javax.inject.Singleton;
 
 import dagger.Module;
@@ -40,7 +40,7 @@ public class TestLifecycleModule {
 			}
 
 			@Override
-			public StartResult startServices(@Nullable String nickname) {
+			public StartResult startServices(SecretKey dbKey) {
 				return StartResult.SUCCESS;
 			}
 
@@ -49,15 +49,15 @@ public class TestLifecycleModule {
 			}
 
 			@Override
-			public void waitForDatabase() throws InterruptedException {
+			public void waitForDatabase() {
 			}
 
 			@Override
-			public void waitForStartup() throws InterruptedException {
+			public void waitForStartup() {
 			}
 
 			@Override
-			public void waitForShutdown() throws InterruptedException {
+			public void waitForShutdown() {
 			}
 
 			@Override
diff --git a/briar-android/src/main/java/org/briarproject/bramble/account/BriarAccountManager.java b/briar-android/src/main/java/org/briarproject/bramble/account/BriarAccountManager.java
new file mode 100644
index 0000000000000000000000000000000000000000..eb1e2faaae939632ddbca1d6e1a1284f9a8eff94
--- /dev/null
+++ b/briar-android/src/main/java/org/briarproject/bramble/account/BriarAccountManager.java
@@ -0,0 +1,33 @@
+package org.briarproject.bramble.account;
+
+import android.app.Application;
+import android.content.SharedPreferences;
+
+import org.briarproject.bramble.api.crypto.CryptoComponent;
+import org.briarproject.bramble.api.db.DatabaseConfig;
+import org.briarproject.bramble.api.identity.IdentityManager;
+import org.briarproject.briar.R;
+import org.briarproject.briar.android.Localizer;
+import org.briarproject.briar.android.util.UiUtils;
+
+import javax.inject.Inject;
+
+class BriarAccountManager extends AndroidAccountManager {
+
+	@Inject
+	BriarAccountManager(DatabaseConfig databaseConfig, CryptoComponent crypto,
+			IdentityManager identityManager, SharedPreferences prefs,
+			Application app) {
+		super(databaseConfig, crypto, identityManager, prefs, app);
+	}
+
+	@Override
+	public void deleteAccount() {
+		synchronized (stateChangeLock) {
+			super.deleteAccount();
+			Localizer.reinitialize();
+			UiUtils.setTheme(appContext,
+					appContext.getString(R.string.pref_theme_light_value));
+		}
+	}
+}
diff --git a/briar-android/src/main/java/org/briarproject/bramble/account/BriarAccountModule.java b/briar-android/src/main/java/org/briarproject/bramble/account/BriarAccountModule.java
new file mode 100644
index 0000000000000000000000000000000000000000..3ed782d77ac791db1b68f0fcf1504e195de57ddd
--- /dev/null
+++ b/briar-android/src/main/java/org/briarproject/bramble/account/BriarAccountModule.java
@@ -0,0 +1,18 @@
+package org.briarproject.bramble.account;
+
+import org.briarproject.bramble.api.account.AccountManager;
+
+import javax.inject.Singleton;
+
+import dagger.Module;
+import dagger.Provides;
+
+@Module
+public class BriarAccountModule {
+
+	@Provides
+	@Singleton
+	AccountManager provideAccountManager(BriarAccountManager accountManager) {
+		return accountManager;
+	}
+}
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/AndroidComponent.java b/briar-android/src/main/java/org/briarproject/briar/android/AndroidComponent.java
index 087aa5ea0cd7d6a2f6b76eb9c9b3a82d92e269fc..75b25b0996b4af139b5c46e7b6d41e54a57f151b 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/AndroidComponent.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/AndroidComponent.java
@@ -1,16 +1,14 @@
 package org.briarproject.briar.android;
 
-import android.content.SharedPreferences;
-
 import org.briarproject.bramble.BrambleAndroidModule;
 import org.briarproject.bramble.BrambleCoreEagerSingletons;
 import org.briarproject.bramble.BrambleCoreModule;
+import org.briarproject.bramble.account.BriarAccountModule;
+import org.briarproject.bramble.api.account.AccountManager;
 import org.briarproject.bramble.api.contact.ContactExchangeTask;
 import org.briarproject.bramble.api.contact.ContactManager;
-import org.briarproject.bramble.api.crypto.CryptoComponent;
 import org.briarproject.bramble.api.crypto.CryptoExecutor;
 import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator;
-import org.briarproject.bramble.api.db.DatabaseConfig;
 import org.briarproject.bramble.api.db.DatabaseExecutor;
 import org.briarproject.bramble.api.event.EventBus;
 import org.briarproject.bramble.api.identity.IdentityManager;
@@ -62,6 +60,7 @@ import dagger.Component;
 		BrambleCoreModule.class,
 		BriarCoreModule.class,
 		BrambleAndroidModule.class,
+		BriarAccountModule.class,
 		AppModule.class
 })
 public interface AndroidComponent
@@ -73,10 +72,6 @@ public interface AndroidComponent
 
 	PasswordStrengthEstimator passwordStrengthIndicator();
 
-	CryptoComponent cryptoComponent();
-
-	DatabaseConfig databaseConfig();
-
 	@DatabaseExecutor
 	Executor databaseExecutor();
 
@@ -92,8 +87,6 @@ public interface AndroidComponent
 
 	AndroidNotificationManager androidNotificationManager();
 
-	SharedPreferences sharedPreferences();
-
 	ScreenFilterMonitor screenFilterMonitor();
 
 	ConnectionRegistry connectionRegistry();
@@ -151,6 +144,8 @@ public interface AndroidComponent
 	@IoExecutor
 	Executor ioExecutor();
 
+	AccountManager accountManager();
+
 	void inject(SignInReminderReceiver briarService);
 
 	void inject(BriarService briarService);
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/AndroidDatabaseConfig.java b/briar-android/src/main/java/org/briarproject/briar/android/AndroidDatabaseConfig.java
index e92a356ee42f675fb8777cd1c4e361629d82367f..797fb907c129a904c3ee173774d7285cb3fd380c 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/AndroidDatabaseConfig.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/AndroidDatabaseConfig.java
@@ -1,99 +1,30 @@
 package org.briarproject.briar.android;
 
-import org.briarproject.bramble.api.crypto.SecretKey;
 import org.briarproject.bramble.api.db.DatabaseConfig;
 import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
 
 import java.io.File;
-import java.util.logging.Logger;
-
-import javax.annotation.Nullable;
-
-import static java.util.logging.Level.INFO;
 
 @NotNullByDefault
 class AndroidDatabaseConfig implements DatabaseConfig {
 
-	private static final Logger LOG =
-			Logger.getLogger(AndroidDatabaseConfig.class.getName());
-
 	private final File dbDir, keyDir;
 
-	@Nullable
-	private volatile SecretKey key = null;
-	@Nullable
-	private volatile String nickname = null;
-
 	AndroidDatabaseConfig(File dbDir, File keyDir) {
 		this.dbDir = dbDir;
 		this.keyDir = keyDir;
 	}
 
-	@Override
-	public boolean databaseExists() {
-		// FIXME should not run on UiThread #620
-		if (!dbDir.isDirectory()) {
-			if (LOG.isLoggable(INFO))
-				LOG.info(dbDir.getAbsolutePath() + " is not a directory");
-			return false;
-		}
-		File[] files = dbDir.listFiles();
-		if (LOG.isLoggable(INFO)) {
-			if (files == null) {
-				LOG.info("Could not list files in " + dbDir.getAbsolutePath());
-			} else {
-				LOG.info("Files in " + dbDir.getAbsolutePath() + ":");
-				for (File f : files) LOG.info(f.getName());
-			}
-			LOG.info("Database exists: " + (files != null && files.length > 0));
-		}
-		return files != null && files.length > 0;
-	}
-
 	@Override
 	public File getDatabaseDirectory() {
-		if (LOG.isLoggable(INFO))
-			LOG.info("Database directory: " + dbDir.getAbsolutePath());
 		return dbDir;
 	}
 
 	@Override
 	public File getDatabaseKeyDirectory() {
-		if (LOG.isLoggable(INFO))
-			LOG.info("Database key directory: " + keyDir.getAbsolutePath());
 		return keyDir;
 	}
 
-	@Override
-	public void setEncryptionKey(SecretKey key) {
-		LOG.info("Setting database key");
-		this.key = key;
-	}
-
-	@Override
-	public void setLocalAuthorName(String nickname) {
-		LOG.info("Setting local author name");
-		this.nickname = nickname;
-	}
-
-	@Override
-	@Nullable
-	public String getLocalAuthorName() {
-		String nickname = this.nickname;
-		if (LOG.isLoggable(INFO))
-			LOG.info("Local author name has been set: " + (nickname != null));
-		return nickname;
-	}
-
-	@Override
-	@Nullable
-	public SecretKey getEncryptionKey() {
-		SecretKey key = this.key;
-		if (LOG.isLoggable(INFO))
-			LOG.info("Database key has been set: " + (key != null));
-		return key;
-	}
-
 	@Override
 	public long getMaxSize() {
 		return Long.MAX_VALUE;
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java b/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java
index 2296a8bafffb48d0e8c315e3460846b9b3d3bef8..3af71627783a7632a3f2c848456f200207a7b318 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java
@@ -11,9 +11,7 @@ import org.briarproject.bramble.api.db.DatabaseConfig;
 import org.briarproject.bramble.api.event.EventBus;
 import org.briarproject.bramble.api.lifecycle.IoExecutor;
 import org.briarproject.bramble.api.lifecycle.LifecycleManager;
-import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
 import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
 import org.briarproject.bramble.api.plugin.BackoffFactory;
 import org.briarproject.bramble.api.plugin.PluginConfig;
 import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
@@ -86,11 +84,7 @@ public class AppModule {
 		File dbDir = app.getApplicationContext().getDir("db", MODE_PRIVATE);
 		File keyDir = app.getApplicationContext().getDir("key", MODE_PRIVATE);
 		StrictMode.setThreadPolicy(tp);
-		@MethodsNotNullByDefault
-		@ParametersNotNullByDefault
-		DatabaseConfig databaseConfig =
-				new AndroidDatabaseConfig(dbDir, keyDir);
-		return databaseConfig;
+		return new AndroidDatabaseConfig(dbDir, keyDir);
 	}
 
 	@Provides
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/BriarService.java b/briar-android/src/main/java/org/briarproject/briar/android/BriarService.java
index b2a30ecb25bafc0b65d5e42fa1a58e8c4d57cafe..022fa3195154fbe83d759ea046b8e5cd3d4fa178 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/BriarService.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/BriarService.java
@@ -17,7 +17,8 @@ import android.os.IBinder;
 import android.support.v4.app.NotificationCompat;
 import android.support.v4.content.ContextCompat;
 
-import org.briarproject.bramble.api.db.DatabaseConfig;
+import org.briarproject.bramble.api.account.AccountManager;
+import org.briarproject.bramble.api.crypto.SecretKey;
 import org.briarproject.bramble.api.lifecycle.LifecycleManager;
 import org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult;
 import org.briarproject.bramble.api.system.AndroidExecutor;
@@ -74,12 +75,13 @@ public class BriarService extends Service {
 	private BroadcastReceiver receiver = null;
 
 	@Inject
-	protected DatabaseConfig databaseConfig;
+	AccountManager accountManager;
+
 	// Fields that are accessed from background threads must be volatile
 	@Inject
-	protected volatile LifecycleManager lifecycleManager;
+	volatile LifecycleManager lifecycleManager;
 	@Inject
-	protected volatile AndroidExecutor androidExecutor;
+	volatile AndroidExecutor androidExecutor;
 	private volatile boolean started = false;
 
 	@Override
@@ -95,7 +97,8 @@ public class BriarService extends Service {
 			stopSelf();
 			return;
 		}
-		if (databaseConfig.getEncryptionKey() == null) {
+		SecretKey dbKey = accountManager.getDatabaseKey();
+		if (dbKey == null) {
 			LOG.info("No database key");
 			stopSelf();
 			return;
@@ -138,8 +141,7 @@ public class BriarService extends Service {
 		startForeground(ONGOING_NOTIFICATION_ID, b.build());
 		// Start the services in a background thread
 		new Thread(() -> {
-			String nickname = databaseConfig.getLocalAuthorName();
-			StartResult result = lifecycleManager.startServices(nickname);
+			StartResult result = lifecycleManager.startServices(dbKey);
 			if (result == SUCCESS) {
 				started = true;
 			} else if (result == ALREADY_RUNNING) {
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/activity/ActivityModule.java b/briar-android/src/main/java/org/briarproject/briar/android/activity/ActivityModule.java
index 2651833f194b065326a5e063c17e2f0bc3ac2285..b21d072321464b72d3e20d4123f46b12235651d3 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/activity/ActivityModule.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/activity/ActivityModule.java
@@ -4,8 +4,6 @@ import android.app.Activity;
 
 import org.briarproject.briar.android.controller.BriarController;
 import org.briarproject.briar.android.controller.BriarControllerImpl;
-import org.briarproject.briar.android.controller.ConfigController;
-import org.briarproject.briar.android.controller.ConfigControllerImpl;
 import org.briarproject.briar.android.controller.DbController;
 import org.briarproject.briar.android.controller.DbControllerImpl;
 import org.briarproject.briar.android.login.PasswordController;
@@ -48,13 +46,6 @@ public class ActivityModule {
 		return setupController;
 	}
 
-	@ActivityScope
-	@Provides
-	ConfigController provideConfigController(
-			ConfigControllerImpl configController) {
-		return configController;
-	}
-
 	@ActivityScope
 	@Provides
 	PasswordController providePasswordController(
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/activity/BriarActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/activity/BriarActivity.java
index 3d2d8361cb4f79ef0645058700dfff1d62aba354..cf71839f9f757d4ed0f2eadd950d5c3cef97f804 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/activity/BriarActivity.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/activity/BriarActivity.java
@@ -61,7 +61,7 @@ public abstract class BriarActivity extends BaseActivity {
 	@Override
 	public void onStart() {
 		super.onStart();
-		if (!briarController.hasEncryptionKey() && !isFinishing()) {
+		if (!briarController.accountSignedIn() && !isFinishing()) {
 			Intent i = new Intent(this, PasswordActivity.class);
 			startActivityForResult(i, REQUEST_PASSWORD);
 		} else if (SDK_INT >= 23) {
@@ -138,7 +138,7 @@ public abstract class BriarActivity extends BaseActivity {
 	}
 
 	protected void signOut(boolean removeFromRecentApps) {
-		if (briarController.hasEncryptionKey()) {
+		if (briarController.accountSignedIn()) {
 			// Don't use UiResultHandler because we want the result even if
 			// this activity has been destroyed
 			briarController.signOut(result -> runOnUiThread(
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/controller/BriarController.java b/briar-android/src/main/java/org/briarproject/briar/android/controller/BriarController.java
index 350faeabaa0d8565b6a5ef5d477716a09cb1e2c3..e4e60c6926c4afdab326a952d88facd2368f80be 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/controller/BriarController.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/controller/BriarController.java
@@ -6,7 +6,7 @@ public interface BriarController extends ActivityLifecycleController {
 
 	void startAndBindService();
 
-	boolean hasEncryptionKey();
+	boolean accountSignedIn();
 
 	/**
 	 * Returns true via the handler when the app has dozed
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/controller/BriarControllerImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/controller/BriarControllerImpl.java
index a668a450d0b389652ce8ecb88a177ccce2d2014e..1271f4d212c29113f133f3e1a132cfc63c936b83 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/controller/BriarControllerImpl.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/controller/BriarControllerImpl.java
@@ -5,7 +5,7 @@ import android.content.Intent;
 import android.os.IBinder;
 import android.support.annotation.CallSuper;
 
-import org.briarproject.bramble.api.db.DatabaseConfig;
+import org.briarproject.bramble.api.account.AccountManager;
 import org.briarproject.bramble.api.db.DatabaseExecutor;
 import org.briarproject.bramble.api.db.DbException;
 import org.briarproject.bramble.api.settings.Settings;
@@ -33,8 +33,7 @@ public class BriarControllerImpl implements BriarController {
 	public static final String DOZE_ASK_AGAIN = "dozeAskAgain";
 
 	private final BriarServiceConnection serviceConnection;
-	private final DatabaseConfig databaseConfig;
-	@DatabaseExecutor
+	private final AccountManager accountManager;
 	private final Executor databaseExecutor;
 	private final SettingsManager settingsManager;
 	private final DozeWatchdog dozeWatchdog;
@@ -44,12 +43,12 @@ public class BriarControllerImpl implements BriarController {
 
 	@Inject
 	BriarControllerImpl(BriarServiceConnection serviceConnection,
-			DatabaseConfig databaseConfig,
+			AccountManager accountManager,
 			@DatabaseExecutor Executor databaseExecutor,
 			SettingsManager settingsManager, DozeWatchdog dozeWatchdog,
 			Activity activity) {
 		this.serviceConnection = serviceConnection;
-		this.databaseConfig = databaseConfig;
+		this.accountManager = accountManager;
 		this.databaseExecutor = databaseExecutor;
 		this.settingsManager = settingsManager;
 		this.dozeWatchdog = dozeWatchdog;
@@ -59,7 +58,7 @@ public class BriarControllerImpl implements BriarController {
 	@Override
 	@CallSuper
 	public void onActivityCreate(Activity activity) {
-		if (databaseConfig.getEncryptionKey() != null) startAndBindService();
+		if (accountManager.hasDatabaseKey()) startAndBindService();
 	}
 
 	@Override
@@ -84,8 +83,8 @@ public class BriarControllerImpl implements BriarController {
 	}
 
 	@Override
-	public boolean hasEncryptionKey() {
-		return databaseConfig.getEncryptionKey() != null;
+	public boolean accountSignedIn() {
+		return accountManager.hasDatabaseKey();
 	}
 
 	@Override
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/controller/ConfigController.java b/briar-android/src/main/java/org/briarproject/briar/android/controller/ConfigController.java
deleted file mode 100644
index e19452a9ce134d3baedbfe0c8478726ee90da922..0000000000000000000000000000000000000000
--- a/briar-android/src/main/java/org/briarproject/briar/android/controller/ConfigController.java
+++ /dev/null
@@ -1,23 +0,0 @@
-package org.briarproject.briar.android.controller;
-
-import android.content.Context;
-
-import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-
-import javax.annotation.Nullable;
-
-@NotNullByDefault
-public interface ConfigController {
-
-	@Nullable
-	String getEncryptedDatabaseKey();
-
-	boolean storeEncryptedDatabaseKey(String hex);
-
-	void deleteAccount(Context ctx);
-
-	boolean accountExists();
-
-	boolean accountSignedIn();
-
-}
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/controller/ConfigControllerImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/controller/ConfigControllerImpl.java
deleted file mode 100644
index 1e0bce3a26aeb5a027bf60c4c3af3256754402b8..0000000000000000000000000000000000000000
--- a/briar-android/src/main/java/org/briarproject/briar/android/controller/ConfigControllerImpl.java
+++ /dev/null
@@ -1,171 +0,0 @@
-package org.briarproject.briar.android.controller;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.support.v7.preference.PreferenceManager;
-
-import org.briarproject.bramble.api.db.DatabaseConfig;
-import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-import org.briarproject.bramble.util.AndroidUtils;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.util.logging.Logger;
-
-import javax.annotation.Nullable;
-import javax.inject.Inject;
-
-import static java.util.logging.Level.WARNING;
-import static org.briarproject.bramble.util.LogUtils.logException;
-
-@NotNullByDefault
-public class ConfigControllerImpl implements ConfigController {
-
-	private static final Logger LOG =
-			Logger.getLogger(ConfigControllerImpl.class.getName());
-
-	private static final String PREF_DB_KEY = "key";
-	private static final String DB_KEY_FILENAME = "db.key";
-	private static final String DB_KEY_BACKUP_FILENAME = "db.key.bak";
-
-	private final SharedPreferences briarPrefs;
-	private final File dbKeyFile, dbKeyBackupFile;
-	protected final DatabaseConfig databaseConfig;
-
-	@Inject
-	public ConfigControllerImpl(SharedPreferences briarPrefs,
-			DatabaseConfig databaseConfig) {
-		this.briarPrefs = briarPrefs;
-		this.databaseConfig = databaseConfig;
-		File keyDir = databaseConfig.getDatabaseKeyDirectory();
-		dbKeyFile = new File(keyDir, DB_KEY_FILENAME);
-		dbKeyBackupFile = new File(keyDir, DB_KEY_BACKUP_FILENAME);
-	}
-
-	@Override
-	@Nullable
-	public String getEncryptedDatabaseKey() {
-		String key = getDatabaseKeyFromPreferences();
-		if (key == null) key = getDatabaseKeyFromFile();
-		else migrateDatabaseKeyToFile(key);
-		return key;
-	}
-
-	@Nullable
-	private String getDatabaseKeyFromPreferences() {
-		String key = briarPrefs.getString(PREF_DB_KEY, null);
-		if (key == null) LOG.info("No database key in preferences");
-		else LOG.info("Found database key in preferences");
-		return key;
-	}
-
-	@Nullable
-	private String getDatabaseKeyFromFile() {
-		String key = readDbKeyFromFile(dbKeyFile);
-		if (key == null) {
-			LOG.info("No database key in primary file");
-			key = readDbKeyFromFile(dbKeyBackupFile);
-			if (key == null) LOG.info("No database key in backup file");
-			else LOG.warning("Found database key in backup file");
-		} else {
-			LOG.info("Found database key in primary file");
-		}
-		return key;
-	}
-
-	@Nullable
-	private String readDbKeyFromFile(File f) {
-		if (!f.exists()) {
-			LOG.info("Key file does not exist");
-			return null;
-		}
-		try {
-			BufferedReader reader = new BufferedReader(new InputStreamReader(
-					new FileInputStream(f), "UTF-8"));
-			String key = reader.readLine();
-			reader.close();
-			return key;
-		} catch (IOException e) {
-			logException(LOG, WARNING, e);
-			return null;
-		}
-	}
-
-	private void migrateDatabaseKeyToFile(String key) {
-		if (storeEncryptedDatabaseKey(key)) {
-			if (briarPrefs.edit().remove(PREF_DB_KEY).commit())
-				LOG.info("Database key migrated to file");
-			else LOG.warning("Database key not removed from preferences");
-		} else {
-			LOG.warning("Database key not migrated to file");
-		}
-	}
-
-	@Override
-	public boolean storeEncryptedDatabaseKey(String hex) {
-		LOG.info("Storing database key in file");
-		// Create the directory if necessary
-		if (databaseConfig.getDatabaseKeyDirectory().mkdirs())
-			LOG.info("Created database key directory");
-		// If only the backup file exists, rename it so we don't overwrite it
-		if (dbKeyBackupFile.exists() && !dbKeyFile.exists()) {
-			if (dbKeyBackupFile.renameTo(dbKeyFile))
-				LOG.info("Renamed old backup");
-			else LOG.warning("Failed to rename old backup");
-		}
-		try {
-			// Write to the backup file
-			writeDbKeyToFile(hex, dbKeyBackupFile);
-			LOG.info("Stored database key in backup file");
-			// Delete the old primary file, if it exists
-			if (dbKeyFile.exists()) {
-				if (dbKeyFile.delete()) LOG.info("Deleted primary file");
-				else LOG.warning("Failed to delete primary file");
-			}
-			// The backup file becomes the new primary
-			if (dbKeyBackupFile.renameTo(dbKeyFile)) {
-				LOG.info("Renamed backup file to primary");
-			} else {
-				LOG.warning("Failed to rename backup file to primary");
-				return false; // Don't overwrite our only copy
-			}
-			// Write a second copy to the backup file
-			writeDbKeyToFile(hex, dbKeyBackupFile);
-			LOG.info("Stored second copy of database key in backup file");
-			return true;
-		} catch (IOException e) {
-			logException(LOG, WARNING, e);
-			return false;
-		}
-	}
-
-	private void writeDbKeyToFile(String key, File f) throws IOException {
-		FileOutputStream out = new FileOutputStream(f);
-		out.write(key.getBytes("UTF-8"));
-		out.flush();
-		out.close();
-	}
-
-	@Override
-	public void deleteAccount(Context ctx) {
-		LOG.info("Deleting account");
-		SharedPreferences defaultPrefs =
-				PreferenceManager.getDefaultSharedPreferences(ctx);
-		AndroidUtils.deleteAppData(ctx, briarPrefs, defaultPrefs);
-	}
-
-	@Override
-	public boolean accountExists() {
-		String hex = getEncryptedDatabaseKey();
-		return hex != null && databaseConfig.databaseExists();
-	}
-
-	@Override
-	public boolean accountSignedIn() {
-		return databaseConfig.getEncryptionKey() != null;
-	}
-}
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/login/PasswordActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/login/PasswordActivity.java
index 40580191306895ae8d75f39ed5b261681fe354a4..79940a314c9b5c014e20b27d7b3967e6adc6de1f 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/login/PasswordActivity.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/login/PasswordActivity.java
@@ -13,8 +13,8 @@ import android.widget.Button;
 import android.widget.EditText;
 import android.widget.ProgressBar;
 
+import org.briarproject.bramble.api.account.AccountManager;
 import org.briarproject.briar.R;
-import org.briarproject.briar.android.Localizer;
 import org.briarproject.briar.android.activity.ActivityComponent;
 import org.briarproject.briar.android.activity.BaseActivity;
 import org.briarproject.briar.android.controller.BriarController;
@@ -33,6 +33,9 @@ import static org.briarproject.briar.api.android.AndroidNotificationManager.REMI
 
 public class PasswordActivity extends BaseActivity {
 
+	@Inject
+	AccountManager accountManager;
+
 	@Inject
 	PasswordController passwordController;
 
@@ -50,7 +53,8 @@ public class PasswordActivity extends BaseActivity {
 		// fade-in after splash screen instead of default animation
 		overridePendingTransition(R.anim.fade_in, R.anim.fade_out);
 
-		if (!passwordController.accountExists()) {
+		if (!accountManager.accountExists()) {
+			// TODO: Finish instead of deleting account?
 			deleteAccount();
 			return;
 		}
@@ -87,7 +91,7 @@ public class PasswordActivity extends BaseActivity {
 	public void onStart() {
 		super.onStart();
 		// If the user has already signed in, clean up this instance
-		if (briarController.hasEncryptionKey()) {
+		if (briarController.accountSignedIn()) {
 			setResult(RESULT_OK);
 			finish();
 		} else {
@@ -112,9 +116,7 @@ public class PasswordActivity extends BaseActivity {
 	}
 
 	private void deleteAccount() {
-		passwordController.deleteAccount(this);
-		Localizer.reinitialize();
-		UiUtils.setTheme(this, getString(R.string.pref_theme_light_value));
+		accountManager.deleteAccount();
 		setResult(RESULT_CANCELED);
 		Intent i = new Intent(this, SetupActivity.class);
 		i.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/login/PasswordController.java b/briar-android/src/main/java/org/briarproject/briar/android/login/PasswordController.java
index ec3cb7ed7ff0dc198b3c295ee2bec54e282533b9..cef864152918f4c115dd13fb7aefb99d9c99c6cf 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/login/PasswordController.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/login/PasswordController.java
@@ -1,18 +1,17 @@
 package org.briarproject.briar.android.login;
 
 import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-import org.briarproject.briar.android.controller.ConfigController;
 import org.briarproject.briar.android.controller.handler.ResultHandler;
 
 @NotNullByDefault
-public interface PasswordController extends ConfigController {
+public interface PasswordController {
 
 	float estimatePasswordStrength(String password);
 
 	void validatePassword(String password,
 			ResultHandler<Boolean> resultHandler);
 
-	void changePassword(String password, String newPassword,
+	void changePassword(String oldPassword, String newPassword,
 			ResultHandler<Boolean> resultHandler);
 
 }
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/login/PasswordControllerImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/login/PasswordControllerImpl.java
index 905797acc6eae94d09f1686da63db1dccacfc084..0ecf784756a1155c6620e870fdc855f2e73a504e 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/login/PasswordControllerImpl.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/login/PasswordControllerImpl.java
@@ -1,44 +1,28 @@
 package org.briarproject.briar.android.login;
 
-import android.content.SharedPreferences;
-
-import org.briarproject.bramble.api.crypto.CryptoComponent;
-import org.briarproject.bramble.api.crypto.CryptoExecutor;
+import org.briarproject.bramble.api.account.AccountManager;
 import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator;
-import org.briarproject.bramble.api.crypto.SecretKey;
-import org.briarproject.bramble.api.db.DatabaseConfig;
+import org.briarproject.bramble.api.lifecycle.IoExecutor;
 import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-import org.briarproject.bramble.util.StringUtils;
-import org.briarproject.briar.android.controller.ConfigControllerImpl;
 import org.briarproject.briar.android.controller.handler.ResultHandler;
 
 import java.util.concurrent.Executor;
-import java.util.logging.Logger;
 
 import javax.inject.Inject;
 
-import static org.briarproject.bramble.util.LogUtils.logDuration;
-import static org.briarproject.bramble.util.LogUtils.now;
-
 @NotNullByDefault
-public class PasswordControllerImpl extends ConfigControllerImpl
-		implements PasswordController {
+public class PasswordControllerImpl implements PasswordController {
 
-	private static final Logger LOG =
-			Logger.getLogger(PasswordControllerImpl.class.getName());
-
-	protected final Executor cryptoExecutor;
-	protected final CryptoComponent crypto;
+	protected final AccountManager accountManager;
+	protected final Executor ioExecutor;
 	private final PasswordStrengthEstimator strengthEstimator;
 
 	@Inject
-	PasswordControllerImpl(SharedPreferences briarPrefs,
-			DatabaseConfig databaseConfig,
-			@CryptoExecutor Executor cryptoExecutor, CryptoComponent crypto,
+	PasswordControllerImpl(AccountManager accountManager,
+			@IoExecutor Executor ioExecutor,
 			PasswordStrengthEstimator strengthEstimator) {
-		super(briarPrefs, databaseConfig);
-		this.cryptoExecutor = cryptoExecutor;
-		this.crypto = crypto;
+		this.accountManager = accountManager;
+		this.ioExecutor = ioExecutor;
 		this.strengthEstimator = strengthEstimator;
 	}
 
@@ -50,46 +34,17 @@ public class PasswordControllerImpl extends ConfigControllerImpl
 	@Override
 	public void validatePassword(String password,
 			ResultHandler<Boolean> resultHandler) {
-		byte[] encrypted = getEncryptedKey();
-		cryptoExecutor.execute(() -> {
-			byte[] key = crypto.decryptWithPassword(encrypted, password);
-			if (key == null) {
-				resultHandler.onResult(false);
-			} else {
-				databaseConfig.setEncryptionKey(new SecretKey(key));
-				resultHandler.onResult(true);
-			}
-		});
+		ioExecutor.execute(() ->
+				resultHandler.onResult(accountManager.signIn(password)));
 	}
 
 	@Override
-	public void changePassword(String password, String newPassword,
+	public void changePassword(String oldPassword, String newPassword,
 			ResultHandler<Boolean> resultHandler) {
-		byte[] encrypted = getEncryptedKey();
-		cryptoExecutor.execute(() -> {
-			byte[] key = crypto.decryptWithPassword(encrypted, password);
-			if (key == null) {
-				resultHandler.onResult(false);
-			} else {
-				String hex =
-						encryptDatabaseKey(new SecretKey(key), newPassword);
-				resultHandler.onResult(storeEncryptedDatabaseKey(hex));
-			}
+		ioExecutor.execute(() -> {
+			boolean changed =
+					accountManager.changePassword(oldPassword, newPassword);
+			resultHandler.onResult(changed);
 		});
 	}
-
-	private byte[] getEncryptedKey() {
-		String hex = getEncryptedDatabaseKey();
-		if (hex == null)
-			throw new IllegalStateException("Encrypted database key is null");
-		return StringUtils.fromHexString(hex);
-	}
-
-	@CryptoExecutor
-	String encryptDatabaseKey(SecretKey key, String password) {
-		long start = now();
-		byte[] encrypted = crypto.encryptWithPassword(key.getBytes(), password);
-		logDuration(LOG, "Key derivation", start);
-		return StringUtils.toHexString(encrypted);
-	}
 }
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/login/SetupActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/login/SetupActivity.java
index f91c57512f1c02e9a53d591d17f7dbdfbc68154e..e38cfeb0c9e2acda08155cc9be2be824f19dd768 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/login/SetupActivity.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/login/SetupActivity.java
@@ -4,6 +4,7 @@ import android.annotation.TargetApi;
 import android.content.Intent;
 import android.os.Bundle;
 
+import org.briarproject.bramble.api.account.AccountManager;
 import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
 import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
 import org.briarproject.briar.R;
@@ -25,6 +26,9 @@ public class SetupActivity extends BaseActivity
 	private static final String STATE_KEY_AUTHOR_NAME = "authorName";
 	private static final String STATE_KEY_PASSWORD = "password";
 
+	@Inject
+	AccountManager accountManager;
+
 	@Inject
 	SetupController setupController;
 
@@ -39,8 +43,7 @@ public class SetupActivity extends BaseActivity
 		setContentView(R.layout.activity_fragment_container);
 
 		if (state == null) {
-			if (setupController.accountExists())
-				throw new AssertionError();
+			if (accountManager.accountExists()) throw new AssertionError();
 			showInitialFragment(AuthorNameFragment.newInstance());
 		} else {
 			authorName = state.getString(STATE_KEY_AUTHOR_NAME);
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/login/SetupControllerImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/login/SetupControllerImpl.java
index 3923cce4abf75284e3d0666059985122ce52f43b..02f40226e404806620b53c2df0323140cdccaa9c 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/login/SetupControllerImpl.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/login/SetupControllerImpl.java
@@ -1,13 +1,10 @@
 package org.briarproject.briar.android.login;
 
-import android.content.SharedPreferences;
 import android.support.annotation.Nullable;
 
-import org.briarproject.bramble.api.crypto.CryptoComponent;
-import org.briarproject.bramble.api.crypto.CryptoExecutor;
+import org.briarproject.bramble.api.account.AccountManager;
 import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator;
-import org.briarproject.bramble.api.crypto.SecretKey;
-import org.briarproject.bramble.api.db.DatabaseConfig;
+import org.briarproject.bramble.api.lifecycle.IoExecutor;
 import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
 import org.briarproject.briar.android.controller.handler.ResultHandler;
 import org.briarproject.briar.android.controller.handler.UiResultHandler;
@@ -28,12 +25,10 @@ public class SetupControllerImpl extends PasswordControllerImpl
 	private volatile SetupActivity setupActivity;
 
 	@Inject
-	SetupControllerImpl(SharedPreferences briarPrefs,
-			DatabaseConfig databaseConfig,
-			@CryptoExecutor Executor cryptoExecutor, CryptoComponent crypto,
+	SetupControllerImpl(AccountManager accountManager,
+			@IoExecutor Executor ioExecutor,
 			PasswordStrengthEstimator strengthEstimator) {
-		super(briarPrefs, databaseConfig, cryptoExecutor, crypto,
-				strengthEstimator);
+		super(accountManager, ioExecutor, strengthEstimator);
 	}
 
 	@Override
@@ -80,10 +75,11 @@ public class SetupControllerImpl extends PasswordControllerImpl
 	@Override
 	public void createAccount() {
 		SetupActivity setupActivity = this.setupActivity;
-		UiResultHandler<Void> resultHandler =
-				new UiResultHandler<Void>(setupActivity) {
+		UiResultHandler<Boolean> resultHandler =
+				new UiResultHandler<Boolean>(setupActivity) {
 					@Override
-					public void onResultUi(Void result) {
+					public void onResultUi(Boolean result) {
+						// TODO: Show an error if result is false
 						if (setupActivity == null)
 							throw new IllegalStateException();
 						setupActivity.showApp();
@@ -93,22 +89,17 @@ public class SetupControllerImpl extends PasswordControllerImpl
 	}
 
 	// Package access for testing
-	void createAccount(ResultHandler<Void> resultHandler) {
+	void createAccount(ResultHandler<Boolean> resultHandler) {
 		SetupActivity setupActivity = this.setupActivity;
 		if (setupActivity == null) throw new IllegalStateException();
 		String authorName = setupActivity.getAuthorName();
 		if (authorName == null) throw new IllegalStateException();
 		String password = setupActivity.getPassword();
 		if (password == null) throw new IllegalStateException();
-		cryptoExecutor.execute(() -> {
+		ioExecutor.execute(() -> {
 			LOG.info("Creating account");
-			databaseConfig.setLocalAuthorName(authorName);
-			SecretKey key = crypto.generateSecretKey();
-			databaseConfig.setEncryptionKey(key);
-			String hex = encryptDatabaseKey(key, password);
-			storeEncryptedDatabaseKey(hex);
-			resultHandler.onResult(null);
+			resultHandler.onResult(accountManager.createAccount(authorName,
+					password));
 		});
 	}
-
 }
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/login/SignInReminderReceiver.java b/briar-android/src/main/java/org/briarproject/briar/android/login/SignInReminderReceiver.java
index cf1edaae668db3a49a938478a3e7633066e3e79e..0ed79eddfa93190a7264c23b55e88a55534cdab7 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/login/SignInReminderReceiver.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/login/SignInReminderReceiver.java
@@ -10,7 +10,7 @@ import android.content.SharedPreferences;
 import android.support.v4.app.NotificationCompat;
 import android.support.v4.content.ContextCompat;
 
-import org.briarproject.bramble.api.db.DatabaseConfig;
+import org.briarproject.bramble.api.account.AccountManager;
 import org.briarproject.briar.R;
 import org.briarproject.briar.android.AndroidComponent;
 import org.briarproject.briar.android.BriarApplication;
@@ -37,7 +37,7 @@ public class SignInReminderReceiver extends BroadcastReceiver {
 	public static final String DISMISS_REMINDER = "dismissReminder";
 
 	@Inject
-	DatabaseConfig databaseConfig;
+	AccountManager accountManager;
 
 	@Override
 	public void onReceive(Context ctx, Intent intent) {
@@ -51,7 +51,7 @@ public class SignInReminderReceiver extends BroadcastReceiver {
 		if (action == null) return;
 		if (action.equals(ACTION_BOOT_COMPLETED) ||
 				action.equals(ACTION_MY_PACKAGE_REPLACED)) {
-			if (databaseConfig.databaseExists()) {
+			if (accountManager.accountExists()) {
 				SharedPreferences prefs = app.getDefaultSharedPreferences();
 				if (prefs.getBoolean(NOTIFY_SIGN_IN, true)) {
 					showSignInNotification(ctx);
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/panic/PanicResponderActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/panic/PanicResponderActivity.java
index dd122e1498b958338de9931e462e13f8e90e1d48..9d64d15000e00ef02b7cbfb4e4b1d6f10200e723 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/panic/PanicResponderActivity.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/panic/PanicResponderActivity.java
@@ -7,10 +7,10 @@ import android.os.Build;
 import android.os.Bundle;
 import android.support.v7.preference.PreferenceManager;
 
+import org.briarproject.bramble.api.account.AccountManager;
 import org.briarproject.bramble.api.system.AndroidExecutor;
 import org.briarproject.briar.android.activity.ActivityComponent;
 import org.briarproject.briar.android.activity.BriarActivity;
-import org.briarproject.briar.android.controller.ConfigController;
 import org.iilab.IilabEngineeringRSA2048Pin;
 
 import java.util.logging.Logger;
@@ -33,7 +33,7 @@ public class PanicResponderActivity extends BriarActivity {
 			Logger.getLogger(PanicResponderActivity.class.getName());
 
 	@Inject
-	protected ConfigController configController;
+	protected AccountManager accountManager;
 	@Inject
 	protected AndroidExecutor androidExecutor;
 
@@ -94,7 +94,7 @@ public class PanicResponderActivity extends BriarActivity {
 
 	private void deleteAllData() {
 		androidExecutor.runOnBackgroundThread(() -> {
-			configController.deleteAccount(PanicResponderActivity.this);
+			accountManager.deleteAccount();
 			// TODO somehow delete/shred the database more thoroughly
 			PanicResponder.deleteAllAppData(PanicResponderActivity.this);
 
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/splash/SplashScreenActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/splash/SplashScreenActivity.java
index d207508a40775081f98e990540caccf91d9e9906..e878d2376278c4ce83352f55bb9ef07301505d25 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/splash/SplashScreenActivity.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/splash/SplashScreenActivity.java
@@ -7,11 +7,11 @@ import android.os.Handler;
 import android.support.v7.preference.PreferenceManager;
 import android.transition.Fade;
 
+import org.briarproject.bramble.api.account.AccountManager;
 import org.briarproject.bramble.api.system.AndroidExecutor;
 import org.briarproject.briar.R;
 import org.briarproject.briar.android.activity.ActivityComponent;
 import org.briarproject.briar.android.activity.BaseActivity;
-import org.briarproject.briar.android.controller.ConfigController;
 import org.briarproject.briar.android.login.OpenDatabaseActivity;
 import org.briarproject.briar.android.login.SetupActivity;
 
@@ -27,7 +27,7 @@ public class SplashScreenActivity extends BaseActivity {
 			Logger.getLogger(SplashScreenActivity.class.getName());
 
 	@Inject
-	protected ConfigController configController;
+	protected AccountManager accountManager;
 	@Inject
 	protected AndroidExecutor androidExecutor;
 
@@ -43,7 +43,7 @@ public class SplashScreenActivity extends BaseActivity {
 
 		setContentView(R.layout.splash);
 
-		if (configController.accountSignedIn()) {
+		if (accountManager.hasDatabaseKey()) {
 			startActivity(new Intent(this, OpenDatabaseActivity.class));
 			finish();
 		} else {
@@ -64,12 +64,12 @@ public class SplashScreenActivity extends BaseActivity {
 			LOG.info("Expired");
 			startActivity(new Intent(this, ExpiredActivity.class));
 		} else {
-			if (configController.accountExists()) {
+			if (accountManager.accountExists()) {
 				LOG.info("Account exists");
 				startActivity(new Intent(this, OpenDatabaseActivity.class));
 			} else {
 				LOG.info("Account does not exist");
-				configController.deleteAccount(this);
+				accountManager.deleteAccount();
 				startActivity(new Intent(this, SetupActivity.class));
 			}
 		}
diff --git a/briar-android/src/test/java/org/briarproject/briar/android/TestDatabaseKeyUtils.java b/briar-android/src/test/java/org/briarproject/briar/android/TestDatabaseKeyUtils.java
deleted file mode 100644
index 0e9173105fa19f99747e91c4c68d25fbb2bd2138..0000000000000000000000000000000000000000
--- a/briar-android/src/test/java/org/briarproject/briar/android/TestDatabaseKeyUtils.java
+++ /dev/null
@@ -1,35 +0,0 @@
-package org.briarproject.briar.android;
-
-import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStreamReader;
-
-import javax.annotation.Nullable;
-
-import static junit.framework.Assert.assertTrue;
-
-@NotNullByDefault
-public class TestDatabaseKeyUtils {
-
-	public static void storeDatabaseKey(File f, String hex) throws IOException {
-		f.getParentFile().mkdirs();
-		FileOutputStream out = new FileOutputStream(f);
-		out.write(hex.getBytes("UTF-8"));
-		out.flush();
-		out.close();
-	}
-
-	@Nullable
-	public static String loadDatabaseKey(File f) throws IOException {
-		BufferedReader reader = new BufferedReader(new InputStreamReader(
-				new FileInputStream(f), "UTF-8"));
-		String hex = reader.readLine();
-		reader.close();
-		return hex;
-	}
-}
diff --git a/briar-android/src/test/java/org/briarproject/briar/android/controller/ConfigControllerImplTest.java b/briar-android/src/test/java/org/briarproject/briar/android/controller/ConfigControllerImplTest.java
deleted file mode 100644
index 859792b3c9872de53ef0c8d03b430be30ef14f30..0000000000000000000000000000000000000000
--- a/briar-android/src/test/java/org/briarproject/briar/android/controller/ConfigControllerImplTest.java
+++ /dev/null
@@ -1,205 +0,0 @@
-package org.briarproject.briar.android.controller;
-
-import android.content.SharedPreferences;
-import android.content.SharedPreferences.Editor;
-
-import org.briarproject.bramble.api.db.DatabaseConfig;
-import org.briarproject.bramble.test.BrambleMockTestCase;
-import org.jmock.Expectations;
-import org.junit.After;
-import org.junit.Test;
-
-import java.io.File;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertNull;
-import static junit.framework.Assert.assertTrue;
-import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
-import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
-import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
-import static org.briarproject.bramble.util.StringUtils.toHexString;
-import static org.briarproject.briar.android.TestDatabaseKeyUtils.loadDatabaseKey;
-import static org.briarproject.briar.android.TestDatabaseKeyUtils.storeDatabaseKey;
-
-public class ConfigControllerImplTest extends BrambleMockTestCase {
-
-	private final SharedPreferences prefs =
-			context.mock(SharedPreferences.class);
-	private final DatabaseConfig databaseConfig =
-			context.mock(DatabaseConfig.class);
-	private final Editor editor = context.mock(Editor.class);
-
-	private final byte[] encryptedKey = getRandomBytes(123);
-	private final String encryptedKeyHex = toHexString(encryptedKey);
-	private final String oldEncryptedKeyHex = toHexString(getRandomBytes(123));
-	private final File testDir = getTestDirectory();
-	private final File keyDir = new File(testDir, "key");
-	private final File keyFile = new File(keyDir, "db.key");
-	private final File keyBackupFile = new File(keyDir, "db.key.bak");
-
-	@Test
-	public void testDbKeyIsMigratedFromPreferencesToFile() throws Exception {
-		context.checking(new Expectations() {{
-			oneOf(prefs).getString("key", null);
-			will(returnValue(encryptedKeyHex));
-			allowing(databaseConfig).getDatabaseKeyDirectory();
-			will(returnValue(keyDir));
-			oneOf(prefs).edit();
-			will(returnValue(editor));
-			oneOf(editor).remove("key");
-			will(returnValue(editor));
-			oneOf(editor).commit();
-			will(returnValue(true));
-		}});
-
-		assertFalse(keyFile.exists());
-		assertFalse(keyBackupFile.exists());
-
-		ConfigControllerImpl c = new ConfigControllerImpl(prefs,
-				databaseConfig);
-
-		assertEquals(encryptedKeyHex, c.getEncryptedDatabaseKey());
-
-		assertTrue(keyFile.exists());
-		assertTrue(keyBackupFile.exists());
-		assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
-		assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
-	}
-
-	@Test
-	public void testDbKeyIsLoadedFromPrimaryFile() throws Exception {
-		context.checking(new Expectations() {{
-			oneOf(prefs).getString("key", null);
-			will(returnValue(null));
-			allowing(databaseConfig).getDatabaseKeyDirectory();
-			will(returnValue(keyDir));
-		}});
-
-		assertFalse(keyFile.exists());
-		assertFalse(keyBackupFile.exists());
-
-		storeDatabaseKey(keyFile, encryptedKeyHex);
-
-		assertTrue(keyFile.exists());
-		assertFalse(keyBackupFile.exists());
-		assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
-
-		ConfigControllerImpl c = new ConfigControllerImpl(prefs,
-				databaseConfig);
-
-		assertEquals(encryptedKeyHex, c.getEncryptedDatabaseKey());
-
-		assertTrue(keyFile.exists());
-		assertFalse(keyBackupFile.exists());
-		assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
-	}
-
-	@Test
-	public void testDbKeyIsLoadedFromBackupFile() throws Exception {
-		context.checking(new Expectations() {{
-			oneOf(prefs).getString("key", null);
-			will(returnValue(null));
-			allowing(databaseConfig).getDatabaseKeyDirectory();
-			will(returnValue(keyDir));
-		}});
-
-		assertFalse(keyFile.exists());
-		assertFalse(keyBackupFile.exists());
-
-		storeDatabaseKey(keyBackupFile, encryptedKeyHex);
-
-		assertFalse(keyFile.exists());
-		assertTrue(keyBackupFile.exists());
-		assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
-
-		ConfigControllerImpl c = new ConfigControllerImpl(prefs,
-				databaseConfig);
-
-		assertEquals(encryptedKeyHex, c.getEncryptedDatabaseKey());
-
-		assertFalse(keyFile.exists());
-		assertTrue(keyBackupFile.exists());
-		assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
-	}
-
-	@Test
-	public void testDbKeyIsNullIfNotFound() {
-		context.checking(new Expectations() {{
-			oneOf(prefs).getString("key", null);
-			will(returnValue(null));
-			allowing(databaseConfig).getDatabaseKeyDirectory();
-			will(returnValue(keyDir));
-		}});
-
-		assertFalse(keyFile.exists());
-		assertFalse(keyBackupFile.exists());
-
-		ConfigControllerImpl c = new ConfigControllerImpl(prefs,
-				databaseConfig);
-
-		assertNull(c.getEncryptedDatabaseKey());
-
-		assertFalse(keyFile.exists());
-		assertFalse(keyBackupFile.exists());
-	}
-
-	@Test
-	public void testStoringDbKeyOverwritesPrimary() throws Exception {
-		context.checking(new Expectations() {{
-			allowing(databaseConfig).getDatabaseKeyDirectory();
-			will(returnValue(keyDir));
-		}});
-
-		assertFalse(keyFile.exists());
-		assertFalse(keyBackupFile.exists());
-
-		storeDatabaseKey(keyFile, oldEncryptedKeyHex);
-
-		assertTrue(keyFile.exists());
-		assertFalse(keyBackupFile.exists());
-		assertEquals(oldEncryptedKeyHex, loadDatabaseKey(keyFile));
-
-		ConfigController c = new ConfigControllerImpl(prefs,
-				databaseConfig);
-
-		assertTrue(c.storeEncryptedDatabaseKey(encryptedKeyHex));
-
-		assertTrue(keyFile.exists());
-		assertTrue(keyBackupFile.exists());
-		assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
-		assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
-	}
-
-	@Test
-	public void testStoringDbKeyOverwritesBackup() throws Exception {
-		context.checking(new Expectations() {{
-			allowing(databaseConfig).getDatabaseKeyDirectory();
-			will(returnValue(keyDir));
-		}});
-
-		assertFalse(keyFile.exists());
-		assertFalse(keyBackupFile.exists());
-
-		storeDatabaseKey(keyBackupFile, oldEncryptedKeyHex);
-
-		assertFalse(keyFile.exists());
-		assertTrue(keyBackupFile.exists());
-		assertEquals(oldEncryptedKeyHex, loadDatabaseKey(keyBackupFile));
-
-		ConfigController c = new ConfigControllerImpl(prefs,
-				databaseConfig);
-
-		assertTrue(c.storeEncryptedDatabaseKey(encryptedKeyHex));
-
-		assertTrue(keyFile.exists());
-		assertTrue(keyBackupFile.exists());
-		assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
-		assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
-	}
-
-	@After
-	public void tearDown() {
-		deleteTestDirectory(testDir);
-	}
-}
diff --git a/briar-android/src/test/java/org/briarproject/briar/android/forum/TestForumActivity.java b/briar-android/src/test/java/org/briarproject/briar/android/forum/TestForumActivity.java
index 72b8d66448bfcff0d92accabdd47bf68ddc5f76f..7a04b4517bc2765b0e0d6d2f9e85e219d748088b 100644
--- a/briar-android/src/test/java/org/briarproject/briar/android/forum/TestForumActivity.java
+++ b/briar-android/src/test/java/org/briarproject/briar/android/forum/TestForumActivity.java
@@ -45,7 +45,7 @@ public class TestForumActivity extends ForumActivity {
 			protected BriarController provideBriarController(
 					BriarControllerImpl briarController) {
 				BriarController c = Mockito.mock(BriarController.class);
-				Mockito.when(c.hasEncryptionKey()).thenReturn(true);
+				Mockito.when(c.accountSignedIn()).thenReturn(true);
 				return c;
 			}
 
diff --git a/briar-android/src/test/java/org/briarproject/briar/android/login/PasswordControllerImplTest.java b/briar-android/src/test/java/org/briarproject/briar/android/login/PasswordControllerImplTest.java
index 427dfa772d1cb52b75e2264344e8f4c2c8cf66d3..987155ffcb848003c9f52f018f2fbd3669ada52d 100644
--- a/briar-android/src/test/java/org/briarproject/briar/android/login/PasswordControllerImplTest.java
+++ b/briar-android/src/test/java/org/briarproject/briar/android/login/PasswordControllerImplTest.java
@@ -1,124 +1,58 @@
 package org.briarproject.briar.android.login;
 
-import android.content.SharedPreferences;
-
-import org.briarproject.bramble.api.crypto.CryptoComponent;
+import org.briarproject.bramble.api.account.AccountManager;
 import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator;
-import org.briarproject.bramble.api.db.DatabaseConfig;
 import org.briarproject.bramble.test.BrambleMockTestCase;
 import org.briarproject.bramble.test.ImmediateExecutor;
 import org.jmock.Expectations;
-import org.junit.After;
 import org.junit.Test;
 
-import java.io.File;
 import java.util.concurrent.Executor;
 import java.util.concurrent.atomic.AtomicBoolean;
 
-import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
-import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
-import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
-import static org.briarproject.bramble.test.TestUtils.getSecretKey;
-import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
-import static org.briarproject.bramble.util.StringUtils.toHexString;
-import static org.briarproject.briar.android.TestDatabaseKeyUtils.loadDatabaseKey;
-import static org.briarproject.briar.android.TestDatabaseKeyUtils.storeDatabaseKey;
+import static org.briarproject.bramble.util.StringUtils.getRandomString;
 
 public class PasswordControllerImplTest extends BrambleMockTestCase {
 
-	private final SharedPreferences briarPrefs =
-			context.mock(SharedPreferences.class);
-	private final DatabaseConfig databaseConfig =
-			context.mock(DatabaseConfig.class);
-	private final CryptoComponent crypto = context.mock(CryptoComponent.class);
+	private final AccountManager accountManager =
+			context.mock(AccountManager.class);
 	private final PasswordStrengthEstimator estimator =
 			context.mock(PasswordStrengthEstimator.class);
 
-	private final Executor cryptoExecutor = new ImmediateExecutor();
+	private final Executor ioExecutor = new ImmediateExecutor();
 
-	private final String oldPassword = "some.old.pass";
-	private final String newPassword = "some.new.pass";
-	private final byte[] oldEncryptedKey = getRandomBytes(123);
-	private final byte[] newEncryptedKey = getRandomBytes(123);
-	private final byte[] key = getSecretKey().getBytes();
-	private final File testDir = getTestDirectory();
-	private final File keyDir = new File(testDir, "key");
-	private final File keyFile = new File(keyDir, "db.key");
-	private final File keyBackupFile = new File(keyDir, "db.key.bak");
+	private final String oldPassword = getRandomString(10);
+	private final String newPassword = getRandomString(10);
 
 	@Test
-	public void testChangePasswordReturnsTrue() throws Exception {
+	public void testChangePasswordReturnsTrue() {
 		context.checking(new Expectations() {{
-			// Look up the encrypted DB key
-			oneOf(briarPrefs).getString("key", null);
-			will(returnValue(null));
-			allowing(databaseConfig).getDatabaseKeyDirectory();
-			will(returnValue(keyDir));
-			// Decrypt and re-encrypt the key
-			oneOf(crypto).decryptWithPassword(oldEncryptedKey, oldPassword);
-			will(returnValue(key));
-			oneOf(crypto).encryptWithPassword(key, newPassword);
-			will(returnValue(newEncryptedKey));
+			oneOf(accountManager).changePassword(oldPassword, newPassword);
+			will(returnValue(true));
 		}});
 
-		assertFalse(keyFile.exists());
-		assertFalse(keyBackupFile.exists());
-
-		storeDatabaseKey(keyFile, toHexString(oldEncryptedKey));
-		storeDatabaseKey(keyBackupFile, toHexString(oldEncryptedKey));
-
-		PasswordControllerImpl p = new PasswordControllerImpl(briarPrefs,
-				databaseConfig, cryptoExecutor, crypto, estimator);
+		PasswordControllerImpl p = new PasswordControllerImpl(accountManager,
+				ioExecutor, estimator);
 
 		AtomicBoolean capturedResult = new AtomicBoolean(false);
 		p.changePassword(oldPassword, newPassword, capturedResult::set);
 		assertTrue(capturedResult.get());
-
-		assertTrue(keyFile.exists());
-		assertTrue(keyBackupFile.exists());
-		assertEquals(toHexString(newEncryptedKey), loadDatabaseKey(keyFile));
-		assertEquals(toHexString(newEncryptedKey),
-				loadDatabaseKey(keyBackupFile));
 	}
 
 	@Test
-	public void testChangePasswordReturnsFalseIfOldPasswordIsWrong()
-			throws Exception {
+	public void testChangePasswordReturnsFalseIfOldPasswordIsWrong() {
 		context.checking(new Expectations() {{
-			// Look up the encrypted DB key
-			oneOf(briarPrefs).getString("key", null);
-			will(returnValue(null));
-			allowing(databaseConfig).getDatabaseKeyDirectory();
-			will(returnValue(keyDir));
-			// Try to decrypt the key - the password is wrong
-			oneOf(crypto).decryptWithPassword(oldEncryptedKey, oldPassword);
-			will(returnValue(null));
+			oneOf(accountManager).changePassword(oldPassword, newPassword);
+			will(returnValue(false));
 		}});
 
-		assertFalse(keyFile.exists());
-		assertFalse(keyBackupFile.exists());
-
-		storeDatabaseKey(keyFile, toHexString(oldEncryptedKey));
-		storeDatabaseKey(keyBackupFile, toHexString(oldEncryptedKey));
-
-		PasswordControllerImpl p = new PasswordControllerImpl(briarPrefs,
-				databaseConfig, cryptoExecutor, crypto, estimator);
+		PasswordControllerImpl p = new PasswordControllerImpl(accountManager,
+				ioExecutor, estimator);
 
 		AtomicBoolean capturedResult = new AtomicBoolean(true);
 		p.changePassword(oldPassword, newPassword, capturedResult::set);
 		assertFalse(capturedResult.get());
-
-		assertTrue(keyFile.exists());
-		assertTrue(keyBackupFile.exists());
-		assertEquals(toHexString(oldEncryptedKey), loadDatabaseKey(keyFile));
-		assertEquals(toHexString(oldEncryptedKey),
-				loadDatabaseKey(keyBackupFile));
-	}
-
-	@After
-	public void tearDown() {
-		deleteTestDirectory(testDir);
 	}
 }
diff --git a/briar-android/src/test/java/org/briarproject/briar/android/login/SetupControllerImplTest.java b/briar-android/src/test/java/org/briarproject/briar/android/login/SetupControllerImplTest.java
index b0df4cde8b324b80c67a42f4656749b08828a5b8..2ee6d0594b28c2cfc24d17372a9e241acf1ef5d2 100644
--- a/briar-android/src/test/java/org/briarproject/briar/android/login/SetupControllerImplTest.java
+++ b/briar-android/src/test/java/org/briarproject/briar/android/login/SetupControllerImplTest.java
@@ -1,56 +1,32 @@
 package org.briarproject.briar.android.login;
 
-import android.content.SharedPreferences;
-import android.content.pm.ApplicationInfo;
-
-import org.briarproject.bramble.api.crypto.CryptoComponent;
+import org.briarproject.bramble.api.account.AccountManager;
 import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator;
-import org.briarproject.bramble.api.crypto.SecretKey;
-import org.briarproject.bramble.api.db.DatabaseConfig;
 import org.briarproject.bramble.test.BrambleMockTestCase;
 import org.briarproject.bramble.test.ImmediateExecutor;
 import org.jmock.Expectations;
 import org.jmock.lib.legacy.ClassImposteriser;
-import org.junit.After;
 import org.junit.Test;
 
-import java.io.File;
 import java.util.concurrent.Executor;
 import java.util.concurrent.atomic.AtomicBoolean;
 
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
-import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
-import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
-import static org.briarproject.bramble.test.TestUtils.getSecretKey;
-import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
 import static org.briarproject.bramble.util.StringUtils.getRandomString;
-import static org.briarproject.bramble.util.StringUtils.toHexString;
-import static org.briarproject.briar.android.TestDatabaseKeyUtils.loadDatabaseKey;
 
 public class SetupControllerImplTest extends BrambleMockTestCase {
 
-	private final SharedPreferences briarPrefs =
-			context.mock(SharedPreferences.class);
-	private final DatabaseConfig databaseConfig =
-			context.mock(DatabaseConfig.class);
-	private final CryptoComponent crypto = context.mock(CryptoComponent.class);
+	private final AccountManager accountManager =
+			context.mock(AccountManager.class);
 	private final PasswordStrengthEstimator estimator =
 			context.mock(PasswordStrengthEstimator.class);
 	private final SetupActivity setupActivity;
 
-	private final Executor cryptoExecutor = new ImmediateExecutor();
+	private final Executor ioExecutor = new ImmediateExecutor();
 
 	private final String authorName = getRandomString(MAX_AUTHOR_NAME_LENGTH);
-	private final String password = "some.strong.pass";
-	private final byte[] encryptedKey = getRandomBytes(123);
-	private final SecretKey key = getSecretKey();
-	private final File testDir = getTestDirectory();
-	private final File keyDir = new File(testDir, "key");
-	private final File keyFile = new File(keyDir, "db.key");
-	private final File keyBackupFile = new File(keyDir, "db.key.bak");
+	private final String password = getRandomString(10);
 
 	public SetupControllerImplTest() {
 		context.setImposteriser(ClassImposteriser.INSTANCE);
@@ -59,13 +35,8 @@ public class SetupControllerImplTest extends BrambleMockTestCase {
 
 	@Test
 	@SuppressWarnings("ResultOfMethodCallIgnored")
-	public void testCreateAccount() throws Exception {
+	public void testCreateAccount() {
 		context.checking(new Expectations() {{
-			// Allow the contents of the data directory to be logged
-			allowing(setupActivity).getApplicationInfo();
-			will(returnValue(new ApplicationInfo() {{
-				dataDir = testDir.getAbsolutePath();
-			}}));
 			// Set the author name and password
 			oneOf(setupActivity).setAuthorName(authorName);
 			oneOf(setupActivity).setPassword(password);
@@ -74,25 +45,13 @@ public class SetupControllerImplTest extends BrambleMockTestCase {
 			will(returnValue(authorName));
 			oneOf(setupActivity).getPassword();
 			will(returnValue(password));
-			// Generate a database key
-			oneOf(crypto).generateSecretKey();
-			will(returnValue(key));
-			// Attach the author name and database key to the database config
-			oneOf(databaseConfig).setLocalAuthorName(authorName);
-			oneOf(databaseConfig).setEncryptionKey(key);
-			// Encrypt the key with the password
-			oneOf(crypto).encryptWithPassword(key.getBytes(), password);
-			will(returnValue(encryptedKey));
-			// Store the encrypted key
-			allowing(databaseConfig).getDatabaseKeyDirectory();
-			will(returnValue(keyDir));
+			// Create the account
+			oneOf(accountManager).createAccount(authorName, password);
+			will(returnValue(true));
 		}});
 
-		assertFalse(keyFile.exists());
-		assertFalse(keyBackupFile.exists());
-
-		SetupControllerImpl s = new SetupControllerImpl(briarPrefs,
-				databaseConfig, cryptoExecutor, crypto, estimator);
+		SetupControllerImpl s = new SetupControllerImpl(accountManager,
+				ioExecutor, estimator);
 		s.setSetupActivity(setupActivity);
 
 		AtomicBoolean called = new AtomicBoolean(false);
@@ -100,15 +59,5 @@ public class SetupControllerImplTest extends BrambleMockTestCase {
 		s.setPassword(password);
 		s.createAccount(result -> called.set(true));
 		assertTrue(called.get());
-
-		assertTrue(keyFile.exists());
-		assertTrue(keyBackupFile.exists());
-		assertEquals(toHexString(encryptedKey), loadDatabaseKey(keyFile));
-		assertEquals(toHexString(encryptedKey), loadDatabaseKey(keyBackupFile));
-	}
-
-	@After
-	public void tearDown() {
-		deleteTestDirectory(testDir);
 	}
 }
diff --git a/briar-core/src/test/java/org/briarproject/briar/feed/FeedManagerIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/feed/FeedManagerIntegrationTest.java
index 24c7334e72210cc96fa46ac4dfb4a379d46542ba..ced26a4a15d2ae19db7527d6bddbedfcee1137dd 100644
--- a/briar-core/src/test/java/org/briarproject/briar/feed/FeedManagerIntegrationTest.java
+++ b/briar-core/src/test/java/org/briarproject/briar/feed/FeedManagerIntegrationTest.java
@@ -1,5 +1,7 @@
 package org.briarproject.briar.feed;
 
+import org.briarproject.bramble.api.identity.IdentityManager;
+import org.briarproject.bramble.api.identity.LocalAuthor;
 import org.briarproject.bramble.api.lifecycle.LifecycleManager;
 import org.briarproject.bramble.contact.ContactModule;
 import org.briarproject.bramble.crypto.CryptoExecutorModule;
@@ -25,6 +27,7 @@ import org.junit.Test;
 import java.io.File;
 import java.util.Collection;
 
+import static org.briarproject.bramble.test.TestUtils.getSecretKey;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
@@ -47,8 +50,12 @@ public class FeedManagerIntegrationTest extends BriarTestCase {
 		component.inject(this);
 		injectEagerSingletons(component);
 
+		IdentityManager identityManager = component.getIdentityManager();
+		LocalAuthor localAuthor = identityManager.createLocalAuthor("feedTest");
+		identityManager.registerLocalAuthor(localAuthor);
+
 		lifecycleManager = component.getLifecycleManager();
-		lifecycleManager.startServices("feedTest");
+		lifecycleManager.startServices(getSecretKey());
 		lifecycleManager.waitForStartup();
 
 		feedManager = component.getFeedManager();
diff --git a/briar-core/src/test/java/org/briarproject/briar/feed/FeedManagerIntegrationTestComponent.java b/briar-core/src/test/java/org/briarproject/briar/feed/FeedManagerIntegrationTestComponent.java
index 76445fe03d18bc67993a40f2aa2a2a9d3ccf6225..685eedacba48c4a6286667c47f0f053ad521cc45 100644
--- a/briar-core/src/test/java/org/briarproject/briar/feed/FeedManagerIntegrationTestComponent.java
+++ b/briar-core/src/test/java/org/briarproject/briar/feed/FeedManagerIntegrationTestComponent.java
@@ -1,5 +1,6 @@
 package org.briarproject.briar.feed;
 
+import org.briarproject.bramble.api.identity.IdentityManager;
 import org.briarproject.bramble.api.lifecycle.LifecycleManager;
 import org.briarproject.bramble.client.ClientModule;
 import org.briarproject.bramble.contact.ContactModule;
@@ -76,6 +77,8 @@ interface FeedManagerIntegrationTestComponent {
 
 	void inject(VersioningModule.EagerSingletons init);
 
+	IdentityManager getIdentityManager();
+
 	LifecycleManager getLifecycleManager();
 
 	FeedManager getFeedManager();
diff --git a/briar-core/src/test/java/org/briarproject/briar/messaging/SimplexMessagingIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/messaging/SimplexMessagingIntegrationTest.java
index 1007afe5e08866139119b5d3b3d129d5c1d8d434..c17f3a35d30aa1e5e61044c631dde3feb2b821e1 100644
--- a/briar-core/src/test/java/org/briarproject/briar/messaging/SimplexMessagingIntegrationTest.java
+++ b/briar-core/src/test/java/org/briarproject/briar/messaging/SimplexMessagingIntegrationTest.java
@@ -44,7 +44,6 @@ import java.io.InputStream;
 import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
 import static org.briarproject.bramble.test.TestPluginConfigModule.MAX_LATENCY;
 import static org.briarproject.bramble.test.TestPluginConfigModule.TRANSPORT_ID;
-import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
 import static org.briarproject.bramble.test.TestUtils.getSecretKey;
 import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
 import static org.junit.Assert.assertEquals;
@@ -58,8 +57,6 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
 	private final File bobDir = new File(testDir, "bob");
 	private final SecretKey master = getSecretKey();
 	private final long timestamp = System.currentTimeMillis();
-	private final LocalAuthor aliceAuthor = getLocalAuthor();
-	private final LocalAuthor bobAuthor = getLocalAuthor();
 
 	private SimplexMessagingIntegrationTestComponent alice, bob;
 
@@ -76,6 +73,11 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
 
 	@Test
 	public void testWriteAndRead() throws Exception {
+		// Create the identities
+		LocalAuthor aliceAuthor =
+				alice.getIdentityManager().createLocalAuthor("Alice");
+		LocalAuthor bobAuthor =
+				bob.getIdentityManager().createLocalAuthor("Bob");
 		// Set up the devices and get the contact IDs
 		ContactId bobId = setUp(alice, aliceAuthor, bobAuthor, true);
 		ContactId aliceId = setUp(bob, bobAuthor, aliceAuthor, false);
@@ -98,13 +100,13 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
 
 	private ContactId setUp(SimplexMessagingIntegrationTestComponent device,
 			LocalAuthor local, Author remote, boolean alice) throws Exception {
-		// Start the lifecycle manager
-		LifecycleManager lifecycleManager = device.getLifecycleManager();
-		lifecycleManager.startServices(null);
-		lifecycleManager.waitForStartup();
 		// Add an identity for the user
 		IdentityManager identityManager = device.getIdentityManager();
 		identityManager.registerLocalAuthor(local);
+		// Start the lifecycle manager
+		LifecycleManager lifecycleManager = device.getLifecycleManager();
+		lifecycleManager.startServices(getSecretKey());
+		lifecycleManager.waitForStartup();
 		// Add the other user as a contact
 		ContactManager contactManager = device.getContactManager();
 		return contactManager.addContact(remote, local.getId(), master,
diff --git a/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTest.java
index 481a574869a56d0f455a7f6673b7bf1f56fb5f32..f10d9418304607ca097ca1dd142c298cb5c4b2c6 100644
--- a/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTest.java
+++ b/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTest.java
@@ -160,8 +160,8 @@ public abstract class BriarIntegrationTest<C extends BriarIntegrationTestCompone
 		validationWaiter = new Waiter();
 		deliveryWaiter = new Waiter();
 
+		createAndRegisterIdentities();
 		startLifecycles();
-		getDefaultIdentities();
 		listenToEvents();
 		addDefaultContacts();
 	}
@@ -193,9 +193,9 @@ public abstract class BriarIntegrationTest<C extends BriarIntegrationTestCompone
 		lifecycleManager0 = c0.getLifecycleManager();
 		lifecycleManager1 = c1.getLifecycleManager();
 		lifecycleManager2 = c2.getLifecycleManager();
-		lifecycleManager0.startServices(AUTHOR0);
-		lifecycleManager1.startServices(AUTHOR1);
-		lifecycleManager2.startServices(AUTHOR2);
+		lifecycleManager0.startServices(getSecretKey());
+		lifecycleManager1.startServices(getSecretKey());
+		lifecycleManager2.startServices(getSecretKey());
 		lifecycleManager0.waitForStartup();
 		lifecycleManager1.waitForStartup();
 		lifecycleManager2.waitForStartup();
@@ -230,10 +230,13 @@ public abstract class BriarIntegrationTest<C extends BriarIntegrationTestCompone
 		}
 	}
 
-	private void getDefaultIdentities() throws DbException {
-		author0 = identityManager0.getLocalAuthor();
-		author1 = identityManager1.getLocalAuthor();
-		author2 = identityManager2.getLocalAuthor();
+	private void createAndRegisterIdentities() {
+		author0 = identityManager0.createLocalAuthor(AUTHOR0);
+		identityManager0.registerLocalAuthor(author0);
+		author1 = identityManager1.createLocalAuthor(AUTHOR1);
+		identityManager1.registerLocalAuthor(author1);
+		author2 = identityManager2.createLocalAuthor(AUTHOR2);
+		identityManager2.registerLocalAuthor(author2);
 	}
 
 	protected void addDefaultContacts() throws Exception {