From 374bff2fb660c50b34f4310a4fe432dde080f9f8 Mon Sep 17 00:00:00 2001
From: akwizgran <michael@briarproject.org>
Date: Tue, 16 Apr 2013 14:49:14 +0100
Subject: [PATCH] UI for setting and entering the password that encrypts the
 database key.

---
 briar-android/AndroidManifest.xml             |   2 +-
 briar-android/res/values/strings.xml          |  12 +-
 .../sf/briar/android/HomeScreenActivity.java  | 151 +++++++++++++++++-
 .../net/sf/briar/android/SetupActivity.java   | 137 ++++++++++++----
 .../briar/android/SplashScreenActivity.java   |   4 +-
 .../android/blogs/CreateBlogActivity.java     |   3 +-
 .../android/groups/CreateGroupActivity.java   |   3 +-
 .../android/helloworld/HelloWorldModule.java  |  10 +-
 .../identity/CreateIdentityActivity.java      |   5 +-
 .../messages/WritePrivateMessageActivity.java |   1 -
 .../net/sf/briar/api/db/DatabaseConfig.java   |   4 +-
 .../sf/briar/crypto/CryptoComponentImpl.java  |   2 +-
 .../src/net/sf/briar/db/H2Database.java       |  42 +++--
 .../src/net/sf/briar/db/JdbcDatabase.java     |  11 +-
 .../src/net/sf/briar/util/StringUtils.java    |  19 ++-
 .../src/net/sf/briar/TestDatabaseConfig.java  |   9 +-
 16 files changed, 328 insertions(+), 87 deletions(-)

diff --git a/briar-android/AndroidManifest.xml b/briar-android/AndroidManifest.xml
index 6020c390ae..c2bedce077 100644
--- a/briar-android/AndroidManifest.xml
+++ b/briar-android/AndroidManifest.xml
@@ -30,7 +30,7 @@
 		</activity>
 		<activity
 			android:name=".android.SetupActivity"
-			android:label="@string/app_name" >
+			android:label="@string/setup_title" >
 		</activity>
 		<activity
 			android:name=".android.SplashScreenActivity"
diff --git a/briar-android/res/values/strings.xml b/briar-android/res/values/strings.xml
index 3fda7ccda5..e76728dc08 100644
--- a/briar-android/res/values/strings.xml
+++ b/briar-android/res/values/strings.xml
@@ -3,6 +3,13 @@
 	<string name="app_name">Briar</string>
 	<string name="notification_title">Briar is running</string>
 	<string name="notification_text">Touch to quit.</string>
+	<string name="setup_title">Briar Setup</string>
+	<string name="choose_nickname">Choose your nickname:</string>
+	<string name="choose_password">Choose your password:</string>
+	<string name="confirm_password">Confirm your password:</string>
+	<string name="format_min_password">Password must be at least %1$d characters long.</string>
+	<string name="enter_password">Enter your password:</string>
+	<string name="try_again">Wrong password, try again</string>
 	<string name="contact_list_button">Contacts</string>
 	<string name="messages_button">Messages</string>
 	<string name="groups_button">Groups</string>
@@ -29,14 +36,14 @@
 	<string name="format_connecting_wifi">Connecting via %1$s\u2026</string>
 	<string name="connecting_bluetooth">Connecting via Bluetooth\u2026</string>
 	<string name="connection_failed">Connection failed</string>
-	<string name="check_same_network">Please check that you are both using the same network.</string>
+	<string name="check_same_network">Please check that you are both using the same network</string>
 	<string name="try_again_button">Try again</string>
 	<string name="connected_to_contact">Connected to contact</string>
 	<string name="your_confirmation_code">Your confirmation code is</string>
 	<string name="enter_confirmation_code">Please enter your contact\'s confirmation code:</string>
 	<string name="waiting_for_contact">Waiting for contact\u2026</string>
 	<string name="codes_do_not_match">Codes do not match</string>
-	<string name="interfering">This could mean that someone is trying to interfere with your connection.</string>
+	<string name="interfering">This could mean that someone is trying to interfere with your connection</string>
 	<string name="contact_added">Contact added</string>
 	<string name="done_button">Done</string>
 	<string name="messages_title">Messages</string>
@@ -67,7 +74,6 @@
 	<string name="new_blog_item">New blog\u2026</string>
 	<string name="create_nickname_item">New nickname\u2026</string>
 	<string name="create_identity_title">Create an Identity</string>
-	<string name="choose_nickname">Choose your nickname:</string>
 	<string name="create_button">Create</string>
 	<string name="no_contacts">You don\'t have any contacts. Add a contact now?</string>
 	<string name="add_button">Add</string>
diff --git a/briar-android/src/net/sf/briar/android/HomeScreenActivity.java b/briar-android/src/net/sf/briar/android/HomeScreenActivity.java
index 4f13b65fc2..d71d29f337 100644
--- a/briar-android/src/net/sf/briar/android/HomeScreenActivity.java
+++ b/briar-android/src/net/sf/briar/android/HomeScreenActivity.java
@@ -1,10 +1,18 @@
 package net.sf.briar.android;
 
+import static android.text.InputType.TYPE_CLASS_TEXT;
+import static android.text.InputType.TYPE_TEXT_VARIATION_PASSWORD;
 import static android.view.Gravity.CENTER;
+import static android.view.Gravity.CENTER_HORIZONTAL;
+import static android.view.View.GONE;
+import static android.view.View.VISIBLE;
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.inputmethod.InputMethodManager.HIDE_IMPLICIT_ONLY;
+import static android.widget.LinearLayout.VERTICAL;
 import static java.util.logging.Level.INFO;
 import static java.util.logging.Level.WARNING;
 import static net.sf.briar.android.widgets.CommonLayoutParams.MATCH_MATCH;
+import static net.sf.briar.android.widgets.CommonLayoutParams.WRAP_WRAP;
 import static net.sf.briar.api.messaging.Rating.GOOD;
 
 import java.util.ArrayList;
@@ -22,20 +30,31 @@ import net.sf.briar.android.messages.ConversationListActivity;
 import net.sf.briar.api.LocalAuthor;
 import net.sf.briar.api.android.DatabaseUiExecutor;
 import net.sf.briar.api.android.ReferenceManager;
+import net.sf.briar.api.crypto.CryptoComponent;
+import net.sf.briar.api.crypto.CryptoExecutor;
 import net.sf.briar.api.db.DatabaseComponent;
+import net.sf.briar.api.db.DatabaseConfig;
 import net.sf.briar.api.db.DbException;
+import net.sf.briar.util.StringUtils;
 import android.content.Intent;
+import android.content.SharedPreferences;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.text.Editable;
+import android.view.KeyEvent;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.view.ViewGroup;
+import android.view.inputmethod.InputMethodManager;
 import android.widget.BaseAdapter;
 import android.widget.Button;
+import android.widget.EditText;
 import android.widget.GridView;
 import android.widget.LinearLayout;
 import android.widget.ListView;
 import android.widget.ProgressBar;
+import android.widget.TextView;
+import android.widget.TextView.OnEditorActionListener;
 
 import com.google.inject.Inject;
 
@@ -48,10 +67,17 @@ public class HomeScreenActivity extends BriarActivity {
 			new BriarServiceConnection();
 
 	@Inject private ReferenceManager referenceManager = null;
+	@Inject private DatabaseConfig databaseConfig = null;
 	@Inject @DatabaseUiExecutor private Executor dbUiExecutor = null;
+	@Inject @CryptoExecutor private Executor cryptoExecutor = null;
+	private boolean bound = false;
+	private TextView tryAgain = null;
+	private Button continueButton = null;
+	private ProgressBar progress = null;
 
 	// Fields that are accessed from background threads must be volatile
 	@Inject private volatile DatabaseComponent db = null;
+	@Inject private volatile CryptoComponent crypto = null;
 
 	@Override
 	public void onCreate(Bundle state) {
@@ -62,20 +88,23 @@ public class HomeScreenActivity extends BriarActivity {
 		if(quit) {
 			// The activity was launched from the notification bar
 			showSpinner();
+			bindService();
 			quit();
 		} else if(handle != -1) {
 			// The activity was launched from the setup wizard
 			showSpinner();
+			startService(new Intent(BriarService.class.getName()));
+			bindService();
 			storeLocalAuthor(referenceManager.removeReference(handle,
 					LocalAuthor.class));
-		} else {
+		} else if(databaseConfig.getEncryptionKey() == null) {
 			// The activity was launched from the splash screen
+			showPasswordPrompt();
+		} else {
+			// The activity has been launched before
 			showButtons();
+			bindService();
 		}
-		// Start the service and bind to it
-		startService(new Intent(BriarService.class.getName()));
-		bindService(new Intent(BriarService.class.getName()),
-				serviceConnection, 0);
 	}
 
 	private void showSpinner() {
@@ -88,6 +117,11 @@ public class HomeScreenActivity extends BriarActivity {
 		setContentView(layout);
 	}
 
+	private void bindService() {
+		bound = bindService(new Intent(BriarService.class.getName()),
+				serviceConnection, 0);
+	}
+
 	private void quit() {
 		new Thread() {
 			@Override
@@ -145,6 +179,110 @@ public class HomeScreenActivity extends BriarActivity {
 		});
 	}
 
+	private void showPasswordPrompt() {
+		SharedPreferences prefs = getSharedPreferences("db", MODE_PRIVATE);
+		String hex = prefs.getString("key", null);
+		if(hex == null) throw new IllegalStateException();
+		final byte[] encrypted = StringUtils.fromHexString(hex);
+
+		LinearLayout layout = new LinearLayout(this);
+		layout.setLayoutParams(MATCH_MATCH);
+		layout.setOrientation(VERTICAL);
+		layout.setGravity(CENTER_HORIZONTAL);
+
+		TextView enterPassword = new TextView(this);
+		enterPassword.setGravity(CENTER);
+		enterPassword.setTextSize(18);
+		enterPassword.setPadding(10, 10, 10, 10);
+		enterPassword.setText(R.string.enter_password);
+		layout.addView(enterPassword);
+
+		final EditText passwordEntry = new EditText(this);
+		passwordEntry.setMaxLines(1);
+		passwordEntry.setPadding(10, 0, 10, 10);
+		int inputType = TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_PASSWORD;
+		passwordEntry.setInputType(inputType);
+		passwordEntry.setOnEditorActionListener(new OnEditorActionListener() {
+			public boolean onEditorAction(TextView v, int action, KeyEvent e) {
+				validatePassword(encrypted, passwordEntry.getText());
+				return true;
+			}
+		});
+		layout.addView(passwordEntry);
+
+		tryAgain = new TextView(this);
+		tryAgain.setGravity(CENTER);
+		tryAgain.setTextSize(14);
+		tryAgain.setPadding(10, 10, 10, 10);
+		tryAgain.setText(R.string.try_again);
+		tryAgain.setVisibility(GONE);
+		layout.addView(tryAgain);
+
+		continueButton = new Button(this);
+		continueButton.setLayoutParams(WRAP_WRAP);
+		continueButton.setText(R.string.continue_button);
+		continueButton.setOnClickListener(new OnClickListener() {
+			public void onClick(View v) {
+				validatePassword(encrypted, passwordEntry.getText());
+			}
+		});
+		layout.addView(continueButton);
+
+		progress = new ProgressBar(this);
+		progress.setLayoutParams(WRAP_WRAP);
+		progress.setIndeterminate(true);
+		progress.setVisibility(GONE);
+		layout.addView(progress);
+		setContentView(layout);
+	}
+
+	private void validatePassword(final byte[] encrypted, Editable e) {
+		if(tryAgain == null || continueButton == null || progress == null)
+			return;
+		// Hide the soft keyboard
+		Object o = getSystemService(INPUT_METHOD_SERVICE);
+		((InputMethodManager) o).toggleSoftInput(HIDE_IMPLICIT_ONLY, 0);
+		// Replace the button with a progress bar
+		continueButton.setVisibility(GONE);
+		progress.setVisibility(VISIBLE);
+		// Decrypt the database key in a background thread
+		int length = e.length();
+		final char[] password = new char[length];
+		e.getChars(0, length, password, 0);
+		e.delete(0, length);
+		cryptoExecutor.execute(new Runnable() {
+			public void run() {
+				byte[] key = crypto.decryptWithPassword(encrypted, password);
+				if(key == null) {
+					tryAgain();
+				} else {
+					databaseConfig.setEncryptionKey(key);
+					showButtonsAndStartService();
+				}
+			}
+		});
+	}
+
+	private void tryAgain() {
+		runOnUiThread(new Runnable() {
+			public void run() {
+				tryAgain.setVisibility(VISIBLE);
+				continueButton.setVisibility(VISIBLE);
+				progress.setVisibility(GONE);
+			}
+		});
+	}
+
+	private void showButtonsAndStartService() {
+		runOnUiThread(new Runnable() {
+			public void run() {
+				showButtons();
+				startService(new Intent(BriarService.class.getName()));
+				bindService();
+			}
+		});
+	}
+
 	private void showButtons() {
 		ListView.LayoutParams matchMatch =
 				new ListView.LayoutParams(MATCH_PARENT, MATCH_PARENT);
@@ -227,6 +365,7 @@ public class HomeScreenActivity extends BriarActivity {
 		quitButton.setText(R.string.quit_button);
 		quitButton.setOnClickListener(new OnClickListener() {
 			public void onClick(View view) {
+				showSpinner();
 				quit();
 			}
 		});
@@ -264,6 +403,6 @@ public class HomeScreenActivity extends BriarActivity {
 	@Override
 	public void onDestroy() {
 		super.onDestroy();
-		unbindService(serviceConnection);
+		if(bound) unbindService(serviceConnection);
 	}
 }
diff --git a/briar-android/src/net/sf/briar/android/SetupActivity.java b/briar-android/src/net/sf/briar/android/SetupActivity.java
index a612cc7b28..a761e7b7f5 100644
--- a/briar-android/src/net/sf/briar/android/SetupActivity.java
+++ b/briar-android/src/net/sf/briar/android/SetupActivity.java
@@ -3,17 +3,18 @@ package net.sf.briar.android;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.text.InputType.TYPE_CLASS_TEXT;
 import static android.text.InputType.TYPE_TEXT_FLAG_CAP_WORDS;
+import static android.text.InputType.TYPE_TEXT_VARIATION_PASSWORD;
 import static android.view.Gravity.CENTER;
 import static android.view.Gravity.CENTER_HORIZONTAL;
 import static android.view.View.GONE;
 import static android.view.View.VISIBLE;
-import static android.view.inputmethod.InputMethodManager.HIDE_IMPLICIT_ONLY;
 import static android.widget.LinearLayout.VERTICAL;
 import static net.sf.briar.android.widgets.CommonLayoutParams.MATCH_MATCH;
 import static net.sf.briar.android.widgets.CommonLayoutParams.WRAP_WRAP;
 
 import java.io.IOException;
 import java.security.KeyPair;
+import java.util.Arrays;
 import java.util.concurrent.Executor;
 
 import net.sf.briar.R;
@@ -22,31 +23,36 @@ import net.sf.briar.api.LocalAuthor;
 import net.sf.briar.api.android.ReferenceManager;
 import net.sf.briar.api.crypto.CryptoComponent;
 import net.sf.briar.api.crypto.CryptoExecutor;
+import net.sf.briar.api.db.DatabaseConfig;
+import net.sf.briar.util.StringUtils;
 import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.Editor;
 import android.os.Bundle;
-import android.view.KeyEvent;
+import android.text.Editable;
 import android.view.View;
 import android.view.View.OnClickListener;
-import android.view.inputmethod.InputMethodManager;
 import android.widget.Button;
 import android.widget.EditText;
 import android.widget.LinearLayout;
 import android.widget.ProgressBar;
 import android.widget.TextView;
-import android.widget.TextView.OnEditorActionListener;
 
 import com.google.inject.Inject;
 
-public class SetupActivity extends BriarActivity
-implements OnEditorActionListener, OnClickListener {
+public class SetupActivity extends BriarActivity implements OnClickListener {
+
+	private static final int MIN_PASSWORD_LENGTH = 8;
 
 	@Inject @CryptoExecutor private Executor cryptoExecutor;
 	private EditText nicknameEntry = null;
-	private Button createButton = null;
+	private EditText passwordEntry = null, passwordConfirmation = null;
+	private Button continueButton = null;
 	private ProgressBar progress = null;
 
 	// Fields that are accessed from background threads must be volatile
 	@Inject private volatile CryptoComponent crypto;
+	@Inject private volatile DatabaseConfig databaseConfig;
 	@Inject private volatile AuthorFactory authorFactory;
 	@Inject private volatile ReferenceManager referenceManager;
 
@@ -69,24 +75,66 @@ implements OnEditorActionListener, OnClickListener {
 			@Override
 			protected void onTextChanged(CharSequence text, int start,
 					int lengthBefore, int lengthAfter) {
-				if(createButton != null)
-					createButton.setEnabled(lengthAfter > 0);
+				enableOrDisableContinueButton();
 			}
 		};
-		nicknameEntry.setTextSize(18);
 		nicknameEntry.setMaxLines(1);
-		nicknameEntry.setPadding(10, 10, 10, 10);
 		int inputType = TYPE_CLASS_TEXT | TYPE_TEXT_FLAG_CAP_WORDS;
 		nicknameEntry.setInputType(inputType);
-		nicknameEntry.setOnEditorActionListener(this);
 		layout.addView(nicknameEntry);
 
-		createButton = new Button(this);
-		createButton.setLayoutParams(WRAP_WRAP);
-		createButton.setText(R.string.create_button);
-		createButton.setEnabled(false);
-		createButton.setOnClickListener(this);
-		layout.addView(createButton);
+		TextView choosePassword = new TextView(this);
+		choosePassword.setGravity(CENTER);
+		choosePassword.setTextSize(18);
+		choosePassword.setPadding(10, 10, 10, 10);
+		choosePassword.setText(R.string.choose_password);
+		layout.addView(choosePassword);
+
+		passwordEntry = new EditText(this) {
+			@Override
+			protected void onTextChanged(CharSequence text, int start,
+					int lengthBefore, int lengthAfter) {
+				enableOrDisableContinueButton();
+			}
+		};
+		passwordEntry.setMaxLines(1);
+		inputType = TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_PASSWORD;
+		passwordEntry.setInputType(inputType);
+		layout.addView(passwordEntry);
+
+		TextView confirmPassword = new TextView(this);
+		confirmPassword.setGravity(CENTER);
+		confirmPassword.setTextSize(18);
+		confirmPassword.setPadding(10, 10, 10, 10);
+		confirmPassword.setText(R.string.confirm_password);
+		layout.addView(confirmPassword);
+
+		passwordConfirmation = new EditText(this) {
+			@Override
+			protected void onTextChanged(CharSequence text, int start,
+					int lengthBefore, int lengthAfter) {
+				enableOrDisableContinueButton();
+			}
+		};
+		passwordConfirmation.setMaxLines(1);
+		inputType = TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_PASSWORD;
+		passwordConfirmation.setInputType(inputType);
+		layout.addView(passwordConfirmation);
+
+		TextView minPasswordLength = new TextView(this);
+		minPasswordLength.setGravity(CENTER);
+		minPasswordLength.setTextSize(14);
+		minPasswordLength.setPadding(10, 10, 10, 10);
+		String format = getResources().getString(R.string.format_min_password);
+		minPasswordLength.setText(String.format(format, MIN_PASSWORD_LENGTH));
+		layout.addView(minPasswordLength);
+
+		continueButton = new Button(this);
+		continueButton.setLayoutParams(WRAP_WRAP);
+		continueButton.setText(R.string.continue_button);
+		continueButton.setEnabled(false);
+		continueButton.setOnClickListener(this);
+		layout.addView(continueButton);
 
 		progress = new ProgressBar(this);
 		progress.setLayoutParams(WRAP_WRAP);
@@ -97,20 +145,42 @@ implements OnEditorActionListener, OnClickListener {
 		setContentView(layout);
 	}
 
-	public boolean onEditorAction(TextView textView, int actionId, KeyEvent e) {
-		validateNickname();
-		return true;
+	private void enableOrDisableContinueButton() {
+		if(nicknameEntry == null || passwordEntry == null ||
+				passwordConfirmation == null || continueButton == null) return;
+		boolean nicknameNotEmpty = nicknameEntry.getText().length() > 0;
+		char[] firstPassword = getChars(passwordEntry.getText());
+		char[] secondPassword = getChars(passwordConfirmation.getText());
+		boolean passwordLength = firstPassword.length >= MIN_PASSWORD_LENGTH;
+		boolean passwordsMatch = Arrays.equals(firstPassword, secondPassword);
+		for(int i = 0; i < firstPassword.length; i++) firstPassword[i] = 0;
+		for(int i = 0; i < secondPassword.length; i++) secondPassword[i] = 0;
+		boolean valid = nicknameNotEmpty && passwordLength && passwordsMatch;
+		continueButton.setEnabled(valid);
+	}
+
+	private char[] getChars(Editable e) {
+		int length = e.length();
+		char[] c = new char[length];
+		e.getChars(0, length, c, 0);
+		return c;
 	}
 
 	public void onClick(View view) {
-		if(!validateNickname()) return;
 		final String nickname = nicknameEntry.getText().toString();
+		final char[] password = getChars(passwordEntry.getText());
+		delete(passwordEntry.getText());
+		delete(passwordConfirmation.getText());
 		// Replace the button with a progress bar
-		createButton.setVisibility(GONE);
+		continueButton.setVisibility(GONE);
 		progress.setVisibility(VISIBLE);
-		// Create the identity in a background thread
+		// Store the DB key and create the identity in a background thread
 		cryptoExecutor.execute(new Runnable() {
 			public void run() {
+				byte[] key = crypto.generateSecretKey().getEncoded();
+				byte[] encrypted = crypto.encryptWithPassword(key, password);
+				storeEncryptedDatabaseKey(encrypted);
+				databaseConfig.setEncryptionKey(key);
 				KeyPair keyPair = crypto.generateSignatureKeyPair();
 				final byte[] publicKey = keyPair.getPublic().getEncoded();
 				final byte[] privateKey = keyPair.getPrivate().getEncoded();
@@ -127,6 +197,17 @@ implements OnEditorActionListener, OnClickListener {
 		});
 	}
 
+	private void delete(Editable e) {
+		e.delete(0, e.length());
+	}
+
+	private void storeEncryptedDatabaseKey(byte[] encrypted) {
+		SharedPreferences prefs = getSharedPreferences("db", MODE_PRIVATE);
+		Editor editor = prefs.edit();
+		editor.putString("key", StringUtils.toHexString(encrypted));
+		editor.commit();
+	}
+
 	private void showHomeScreen(final long handle) {
 		runOnUiThread(new Runnable() {
 			public void run() {
@@ -139,12 +220,4 @@ implements OnEditorActionListener, OnClickListener {
 			}
 		});
 	}
-
-	private boolean validateNickname() {
-		if(nicknameEntry.getText().toString().equals("")) return false;
-		// Hide the soft keyboard
-		Object o = getSystemService(INPUT_METHOD_SERVICE);
-		((InputMethodManager) o).toggleSoftInput(HIDE_IMPLICIT_ONLY, 0);
-		return true;
-	}
 }
diff --git a/briar-android/src/net/sf/briar/android/SplashScreenActivity.java b/briar-android/src/net/sf/briar/android/SplashScreenActivity.java
index 54d529c8c9..2c1e600056 100644
--- a/briar-android/src/net/sf/briar/android/SplashScreenActivity.java
+++ b/briar-android/src/net/sf/briar/android/SplashScreenActivity.java
@@ -2,7 +2,7 @@ package net.sf.briar.android;
 
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.view.Gravity.CENTER;
-import net.sf.briar.android.widgets.CommonLayoutParams;
+import static net.sf.briar.android.widgets.CommonLayoutParams.MATCH_MATCH;
 import net.sf.briar.api.db.DatabaseConfig;
 import roboguice.RoboGuice;
 import roboguice.activity.RoboSplashActivity;
@@ -24,7 +24,7 @@ public class SplashScreenActivity extends RoboSplashActivity {
 	public void onCreate(Bundle state) {
 		super.onCreate(null);
 		LinearLayout layout = new LinearLayout(this);
-		layout.setLayoutParams(CommonLayoutParams.MATCH_MATCH);
+		layout.setLayoutParams(MATCH_MATCH);
 		layout.setGravity(CENTER);
 		ProgressBar spinner = new ProgressBar(this);
 		spinner.setIndeterminate(true);
diff --git a/briar-android/src/net/sf/briar/android/blogs/CreateBlogActivity.java b/briar-android/src/net/sf/briar/android/blogs/CreateBlogActivity.java
index e62dff274a..c3025a62d4 100644
--- a/briar-android/src/net/sf/briar/android/blogs/CreateBlogActivity.java
+++ b/briar-android/src/net/sf/briar/android/blogs/CreateBlogActivity.java
@@ -99,9 +99,8 @@ SelectContactsDialog.Listener {
 				enableOrDisableCreateButton();
 			}
 		};
-		nameEntry.setTextSize(18);
 		nameEntry.setMaxLines(1);
-		nameEntry.setPadding(10, 10, 10, 10);
+		nameEntry.setPadding(10, 0, 10, 10);
 		nameEntry.setInputType(TYPE_CLASS_TEXT | TYPE_TEXT_FLAG_CAP_SENTENCES);
 		nameEntry.setOnEditorActionListener(this);
 		layout.addView(nameEntry);
diff --git a/briar-android/src/net/sf/briar/android/groups/CreateGroupActivity.java b/briar-android/src/net/sf/briar/android/groups/CreateGroupActivity.java
index 028d0cccd9..e6dab7ed02 100644
--- a/briar-android/src/net/sf/briar/android/groups/CreateGroupActivity.java
+++ b/briar-android/src/net/sf/briar/android/groups/CreateGroupActivity.java
@@ -94,9 +94,8 @@ SelectContactsDialog.Listener {
 				enableOrDisableCreateButton();
 			}
 		};
-		nameEntry.setTextSize(18);
 		nameEntry.setMaxLines(1);
-		nameEntry.setPadding(10, 10, 10, 10);
+		nameEntry.setPadding(10, 0, 10, 10);
 		nameEntry.setInputType(TYPE_CLASS_TEXT | TYPE_TEXT_FLAG_CAP_SENTENCES);
 		nameEntry.setOnEditorActionListener(this);
 		layout.addView(nameEntry);
diff --git a/briar-android/src/net/sf/briar/android/helloworld/HelloWorldModule.java b/briar-android/src/net/sf/briar/android/helloworld/HelloWorldModule.java
index b95de675f1..1880ece374 100644
--- a/briar-android/src/net/sf/briar/android/helloworld/HelloWorldModule.java
+++ b/briar-android/src/net/sf/briar/android/helloworld/HelloWorldModule.java
@@ -35,6 +35,8 @@ public class HelloWorldModule extends AbstractModule {
 		final File dir = app.getApplicationContext().getDir("db", MODE_PRIVATE);
 		return new DatabaseConfig() {
 
+			private volatile byte[] key = null;
+
 			public boolean databaseExists() {
 				return dir.isDirectory() && dir.listFiles().length > 0;
 			}
@@ -43,8 +45,12 @@ public class HelloWorldModule extends AbstractModule {
 				return dir;
 			}
 
-			public char[] getPassword() {
-				return "foo bar".toCharArray();
+			public void setEncryptionKey(byte[] key) {
+				this.key = key;
+			}
+
+			public byte[] getEncryptionKey() {
+				return key;
 			}
 
 			public long getMaxSize() {
diff --git a/briar-android/src/net/sf/briar/android/identity/CreateIdentityActivity.java b/briar-android/src/net/sf/briar/android/identity/CreateIdentityActivity.java
index d91a2f9524..e0d9493ece 100644
--- a/briar-android/src/net/sf/briar/android/identity/CreateIdentityActivity.java
+++ b/briar-android/src/net/sf/briar/android/identity/CreateIdentityActivity.java
@@ -85,12 +85,11 @@ implements OnEditorActionListener, OnClickListener {
 			protected void onTextChanged(CharSequence text, int start,
 					int lengthBefore, int lengthAfter) {
 				if(createButton != null)
-					createButton.setEnabled(lengthAfter > 0);
+					createButton.setEnabled(getText().length() > 0);
 			}
 		};
-		nicknameEntry.setTextSize(18);
 		nicknameEntry.setMaxLines(1);
-		nicknameEntry.setPadding(10, 10, 10, 10);
+		nicknameEntry.setPadding(10, 0, 10, 10);
 		int inputType = TYPE_CLASS_TEXT | TYPE_TEXT_FLAG_CAP_WORDS;
 		nicknameEntry.setInputType(inputType);
 		nicknameEntry.setOnEditorActionListener(this);
diff --git a/briar-android/src/net/sf/briar/android/messages/WritePrivateMessageActivity.java b/briar-android/src/net/sf/briar/android/messages/WritePrivateMessageActivity.java
index 728a28e5f4..866c4bdaa5 100644
--- a/briar-android/src/net/sf/briar/android/messages/WritePrivateMessageActivity.java
+++ b/briar-android/src/net/sf/briar/android/messages/WritePrivateMessageActivity.java
@@ -96,7 +96,6 @@ implements OnItemSelectedListener, OnClickListener {
 
 		from = new TextView(this);
 		from.setTextSize(18);
-		from.setMaxLines(1);
 		from.setPadding(10, 10, 10, 10);
 		from.setText(R.string.from);
 		header.addView(from);
diff --git a/briar-api/src/net/sf/briar/api/db/DatabaseConfig.java b/briar-api/src/net/sf/briar/api/db/DatabaseConfig.java
index 15f27b645e..26f42f99d9 100644
--- a/briar-api/src/net/sf/briar/api/db/DatabaseConfig.java
+++ b/briar-api/src/net/sf/briar/api/db/DatabaseConfig.java
@@ -8,7 +8,9 @@ public interface DatabaseConfig {
 
 	File getDatabaseDirectory();
 
-	char[] getPassword();
+	void setEncryptionKey(byte[] key);
+
+	byte[] getEncryptionKey();
 
 	long getMaxSize();
 }
diff --git a/briar-core/src/net/sf/briar/crypto/CryptoComponentImpl.java b/briar-core/src/net/sf/briar/crypto/CryptoComponentImpl.java
index 7526c3708d..c846af33a5 100644
--- a/briar-core/src/net/sf/briar/crypto/CryptoComponentImpl.java
+++ b/briar-core/src/net/sf/briar/crypto/CryptoComponentImpl.java
@@ -65,7 +65,7 @@ class CryptoComponentImpl implements CryptoComponent {
 	private static final String STORAGE_CIPHER_ALGO = "AES/GCM/NoPadding";
 	private static final int STORAGE_IV_BYTES = 16; // 128 bits
 	private static final int PBKDF_SALT_BYTES = 16; // 128 bits
-	private static final int PBKDF_ITERATIONS = 10 * 1000; // FIXME: How many?
+	private static final int PBKDF_ITERATIONS = 1000;
 	private static final String KEY_DERIVATION_ALGO = "AES/CTR/NoPadding";
 	private static final int KEY_DERIVATION_IV_BYTES = 16; // 128 bits
 
diff --git a/briar-core/src/net/sf/briar/db/H2Database.java b/briar-core/src/net/sf/briar/db/H2Database.java
index 18fe2bdd76..5203f64830 100644
--- a/briar-core/src/net/sf/briar/db/H2Database.java
+++ b/briar-core/src/net/sf/briar/db/H2Database.java
@@ -5,13 +5,13 @@ import java.io.IOException;
 import java.sql.Connection;
 import java.sql.DriverManager;
 import java.sql.SQLException;
-import java.util.Arrays;
 import java.util.Properties;
 
 import net.sf.briar.api.clock.Clock;
 import net.sf.briar.api.db.DatabaseConfig;
 import net.sf.briar.api.db.DbException;
 import net.sf.briar.util.FileUtils;
+import net.sf.briar.util.StringUtils;
 
 import com.google.inject.Inject;
 
@@ -23,23 +23,23 @@ class H2Database extends JdbcDatabase {
 	private static final String COUNTER_TYPE = "INT NOT NULL AUTO_INCREMENT";
 	private static final String SECRET_TYPE = "BINARY(32)";
 
-	private final File dir;
+	private final DatabaseConfig config;
 	private final String url;
-	private final char[] password;
-	private final long maxSize;
 
 	@Inject
 	H2Database(DatabaseConfig config, Clock clock) {
-		super(HASH_TYPE, BINARY_TYPE, COUNTER_TYPE, SECRET_TYPE, config, clock);
-		dir = config.getDatabaseDirectory();
-		url = "jdbc:h2:split:" + new File(dir, "db").getPath()
+		super(HASH_TYPE, BINARY_TYPE, COUNTER_TYPE, SECRET_TYPE, clock);
+		this.config = config;
+		String path = new File(config.getDatabaseDirectory(), "db").getPath();
+		url = "jdbc:h2:split:" + path
 				+ ";CIPHER=AES;MULTI_THREADED=1;DB_CLOSE_ON_EXIT=false";
-		password = config.getPassword();
-		maxSize = config.getMaxSize();
 	}
 
 	public boolean open() throws DbException, IOException {
-		return super.open("org.h2.Driver");
+		boolean reopen = config.databaseExists();
+		if(!reopen) config.getDatabaseDirectory().mkdirs();
+		super.open("org.h2.Driver", reopen);
+		return reopen;
 	}
 
 	public void close() throws DbException {
@@ -52,6 +52,8 @@ class H2Database extends JdbcDatabase {
 	}
 
 	public long getFreeSpace() throws DbException {
+		File dir = config.getDatabaseDirectory();
+		long maxSize = config.getMaxSize();
 		try {
 			long free = FileUtils.getFreeSpace(dir);
 			long used = getDiskSpace(dir);
@@ -73,14 +75,28 @@ class H2Database extends JdbcDatabase {
 
 	@Override
 	protected Connection createConnection() throws SQLException {
-		char[] passwordCopy = password.clone();
+		char[] password = encodePassword(config.getEncryptionKey());
 		Properties props = new Properties();
 		props.setProperty("user", "user");
-		props.put("password", passwordCopy);
+		props.put("password", password);
 		try {
 			return DriverManager.getConnection(url, props);
 		} finally {
-			Arrays.fill(passwordCopy, (char) 0);
+			for(int i = 0; i < password.length; i++) password[i] = 0;
 		}
 	}
+
+	private char[] encodePassword(byte[] key) {
+		// The database password is the hex-encoded key
+		char[] hex = StringUtils.toHexChars(key);
+		// Separate the database password from the user password with a space
+		char[] user = "password".toCharArray();
+		char[] combined = new char[hex.length + 1 + user.length];
+		System.arraycopy(hex, 0, combined, 0, hex.length);
+		combined[hex.length] = ' ';
+		System.arraycopy(user, 0, combined, hex.length + 1, user.length);
+		// Erase the hex-encoded key
+		for(int i = 0; i < hex.length; i++) hex[i] = 0;
+		return combined;
+	}
 }
diff --git a/briar-core/src/net/sf/briar/db/JdbcDatabase.java b/briar-core/src/net/sf/briar/db/JdbcDatabase.java
index 742cb6cdb1..d33c66950e 100644
--- a/briar-core/src/net/sf/briar/db/JdbcDatabase.java
+++ b/briar-core/src/net/sf/briar/db/JdbcDatabase.java
@@ -34,7 +34,6 @@ import net.sf.briar.api.TransportConfig;
 import net.sf.briar.api.TransportId;
 import net.sf.briar.api.TransportProperties;
 import net.sf.briar.api.clock.Clock;
-import net.sf.briar.api.db.DatabaseConfig;
 import net.sf.briar.api.db.DbClosedException;
 import net.sf.briar.api.db.DbException;
 import net.sf.briar.api.db.GroupMessageHeader;
@@ -360,7 +359,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 
 	// Different database libraries use different names for certain types
 	private final String hashType, binaryType, counterType, secretType;
-	private final DatabaseConfig config;
 	private final Clock clock;
 
 	private final LinkedList<Connection> connections =
@@ -372,18 +370,16 @@ abstract class JdbcDatabase implements Database<Connection> {
 	protected abstract Connection createConnection() throws SQLException;
 
 	JdbcDatabase(String hashType, String binaryType, String counterType,
-			String secretType, DatabaseConfig config, Clock clock) {
+			String secretType, Clock clock) {
 		this.hashType = hashType;
 		this.binaryType = binaryType;
 		this.counterType = counterType;
 		this.secretType = secretType;
-		this.config = config;
 		this.clock = clock;
 	}
 
-	protected boolean open(String driverClass) throws DbException, IOException {
-		boolean reopen = config.databaseExists();
-		if(!reopen) config.getDatabaseDirectory().mkdirs();
+	protected void open(String driverClass, boolean reopen) throws DbException,
+	IOException {
 		// Load the JDBC driver
 		try {
 			Class.forName(driverClass);
@@ -399,7 +395,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 			abortTransaction(txn);
 			throw e;
 		}
-		return reopen;
 	}
 
 	private void createTables(Connection txn) throws DbException {
diff --git a/briar-core/src/net/sf/briar/util/StringUtils.java b/briar-core/src/net/sf/briar/util/StringUtils.java
index 0b3e674fd8..a7d71ea8ac 100644
--- a/briar-core/src/net/sf/briar/util/StringUtils.java
+++ b/briar-core/src/net/sf/briar/util/StringUtils.java
@@ -29,16 +29,19 @@ public class StringUtils {
 		else return s;
 	}
 
+	/** Converts the given byte array to a hex character array. */
+	public static char[] toHexChars(byte[] bytes) {
+		char[] hex = new char[bytes.length * 2];
+		for(int i = 0, j = 0; i < bytes.length; i++) {
+			hex[j++] = HEX[(bytes[i] >> 4) & 0xF];
+			hex[j++] = HEX[bytes[i] & 0xF];
+		}
+		return hex;
+	}
+
 	/** Converts the given byte array to a hex string. */
 	public static String toHexString(byte[] bytes) {
-		StringBuilder s = new StringBuilder(bytes.length * 2);
-		for(byte b : bytes) {
-			int high = (b >> 4) & 0xF;
-			s.append(HEX[high]);
-			int low = b & 0xF;
-			s.append(HEX[low]);
-		}
-		return s.toString();
+		return new String(toHexChars(bytes));
 	}
 
 	/** Converts the given hex string to a byte array. */
diff --git a/briar-tests/src/net/sf/briar/TestDatabaseConfig.java b/briar-tests/src/net/sf/briar/TestDatabaseConfig.java
index e21336822f..1d01590cf4 100644
--- a/briar-tests/src/net/sf/briar/TestDatabaseConfig.java
+++ b/briar-tests/src/net/sf/briar/TestDatabaseConfig.java
@@ -8,6 +8,7 @@ public class TestDatabaseConfig implements DatabaseConfig {
 
 	private final File dir;
 	private final long maxSize;
+	private volatile byte[] key = new byte[] { 'f', 'o', 'o' };
 
 	public TestDatabaseConfig(File dir, long maxSize) {
 		this.dir = dir;
@@ -22,8 +23,12 @@ public class TestDatabaseConfig implements DatabaseConfig {
 		return dir;
 	}
 
-	public char[] getPassword() {
-		return "foo bar".toCharArray();
+	public void setEncryptionKey(byte[] key) {
+		this.key = key;
+	}
+
+	public byte[] getEncryptionKey() {
+		return key;
 	}
 
 	public long getMaxSize() {
-- 
GitLab