diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/CryptoComponent.java b/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/CryptoComponent.java
index c104f61d1310111389eb053b81dcaf6257a93e08..f4f065c97393dea173a93d788b9f757700405fcb 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/CryptoComponent.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/CryptoComponent.java
@@ -1,8 +1,13 @@
 package org.briarproject.bramble.api.crypto;
 
+import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
+
 import java.security.GeneralSecurityException;
 import java.security.SecureRandom;
 
+import javax.annotation.Nullable;
+
+@NotNullByDefault
 public interface CryptoComponent {
 
 	SecretKey generateSecretKey();
@@ -124,6 +129,7 @@ public interface CryptoComponent {
 	 * given password. Returns null if the ciphertext cannot be decrypted and
 	 * authenticated (for example, if the password is wrong).
 	 */
+	@Nullable
 	byte[] decryptWithPassword(byte[] ciphertext, String password);
 
 	/**
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/crypto/CryptoComponentImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/crypto/CryptoComponentImpl.java
index 435b453ca1b04ec2a089b5a212093f31502f76c9..25969d0eb36dc9a68a26bcf85de9db7bdc9214db 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/crypto/CryptoComponentImpl.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/crypto/CryptoComponentImpl.java
@@ -10,31 +10,25 @@ import org.briarproject.bramble.api.crypto.KeyParser;
 import org.briarproject.bramble.api.crypto.PrivateKey;
 import org.briarproject.bramble.api.crypto.PublicKey;
 import org.briarproject.bramble.api.crypto.SecretKey;
+import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
 import org.briarproject.bramble.api.system.SecureRandomProvider;
 import org.briarproject.bramble.util.ByteUtils;
 import org.briarproject.bramble.util.StringUtils;
 import org.spongycastle.crypto.AsymmetricCipherKeyPair;
-import org.spongycastle.crypto.CipherParameters;
 import org.spongycastle.crypto.CryptoException;
 import org.spongycastle.crypto.Digest;
 import org.spongycastle.crypto.agreement.ECDHCBasicAgreement;
 import org.spongycastle.crypto.digests.Blake2bDigest;
-import org.spongycastle.crypto.digests.SHA256Digest;
 import org.spongycastle.crypto.generators.ECKeyPairGenerator;
-import org.spongycastle.crypto.generators.PKCS5S2ParametersGenerator;
 import org.spongycastle.crypto.params.ECKeyGenerationParameters;
 import org.spongycastle.crypto.params.ECPrivateKeyParameters;
 import org.spongycastle.crypto.params.ECPublicKeyParameters;
-import org.spongycastle.crypto.params.KeyParameter;
 
 import java.security.GeneralSecurityException;
 import java.security.NoSuchAlgorithmException;
 import java.security.Provider;
 import java.security.SecureRandom;
 import java.security.Security;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
 import java.util.logging.Logger;
 
 import javax.inject.Inject;
@@ -43,6 +37,7 @@ import static java.util.logging.Level.INFO;
 import static org.briarproject.bramble.crypto.EllipticCurveConstants.PARAMETERS;
 import static org.briarproject.bramble.util.ByteUtils.INT_32_BYTES;
 
+@NotNullByDefault
 class CryptoComponentImpl implements CryptoComponent {
 
 	private static final Logger LOG =
@@ -53,10 +48,9 @@ class CryptoComponentImpl implements CryptoComponent {
 	private static final int ED_KEY_PAIR_BITS = 256;
 	private static final int STORAGE_IV_BYTES = 24; // 196 bits
 	private static final int PBKDF_SALT_BYTES = 32; // 256 bits
-	private static final int PBKDF_TARGET_MILLIS = 500;
-	private static final int PBKDF_SAMPLES = 30;
 
 	private final SecureRandom secureRandom;
+	private final PasswordBasedKdf passwordBasedKdf;
 	private final ECKeyPairGenerator agreementKeyPairGenerator;
 	private final ECKeyPairGenerator signatureKeyPairGenerator;
 	private final KeyParser agreementKeyParser, signatureKeyParser;
@@ -65,7 +59,8 @@ class CryptoComponentImpl implements CryptoComponent {
 	private final KeyParser edKeyParser;
 
 	@Inject
-	CryptoComponentImpl(SecureRandomProvider secureRandomProvider) {
+	CryptoComponentImpl(SecureRandomProvider secureRandomProvider,
+			PasswordBasedKdf passwordBasedKdf) {
 		if (LOG.isLoggable(INFO)) {
 			SecureRandom defaultSecureRandom = new SecureRandom();
 			String name = defaultSecureRandom.getProvider().getName();
@@ -85,6 +80,7 @@ class CryptoComponentImpl implements CryptoComponent {
 			}
 		}
 		secureRandom = new SecureRandom();
+		this.passwordBasedKdf = passwordBasedKdf;
 		ECKeyGenerationParameters params = new ECKeyGenerationParameters(
 				PARAMETERS, secureRandom);
 		agreementKeyPairGenerator = new ECKeyPairGenerator();
@@ -339,18 +335,18 @@ class CryptoComponentImpl implements CryptoComponent {
 		byte[] salt = new byte[PBKDF_SALT_BYTES];
 		secureRandom.nextBytes(salt);
 		// Calibrate the KDF
-		int iterations = chooseIterationCount(PBKDF_TARGET_MILLIS);
+		int cost = passwordBasedKdf.chooseCostParameter();
 		// Derive the key from the password
-		SecretKey key = new SecretKey(pbkdf2(password, salt, iterations));
+		SecretKey key = passwordBasedKdf.deriveKey(password, salt, cost);
 		// Generate a random IV
 		byte[] iv = new byte[STORAGE_IV_BYTES];
 		secureRandom.nextBytes(iv);
-		// The output contains the salt, iterations, IV, ciphertext and MAC
+		// The output contains the salt, cost parameter, IV, ciphertext and MAC
 		int outputLen = salt.length + INT_32_BYTES + iv.length + input.length
 				+ macBytes;
 		byte[] output = new byte[outputLen];
 		System.arraycopy(salt, 0, output, 0, salt.length);
-		ByteUtils.writeUint32(iterations, output, salt.length);
+		ByteUtils.writeUint32(cost, output, salt.length);
 		System.arraycopy(iv, 0, output, salt.length + INT_32_BYTES, iv.length);
 		// Initialise the cipher and encrypt the plaintext
 		try {
@@ -367,19 +363,19 @@ class CryptoComponentImpl implements CryptoComponent {
 	public byte[] decryptWithPassword(byte[] input, String password) {
 		AuthenticatedCipher cipher = new XSalsa20Poly1305AuthenticatedCipher();
 		int macBytes = cipher.getMacBytes();
-		// The input contains the salt, iterations, IV, ciphertext and MAC
+		// The input contains the salt, cost parameter, IV, ciphertext and MAC
 		if (input.length < PBKDF_SALT_BYTES + INT_32_BYTES + STORAGE_IV_BYTES
 				+ macBytes)
 			return null; // Invalid input
 		byte[] salt = new byte[PBKDF_SALT_BYTES];
 		System.arraycopy(input, 0, salt, 0, salt.length);
-		long iterations = ByteUtils.readUint32(input, salt.length);
-		if (iterations < 0 || iterations > Integer.MAX_VALUE)
-			return null; // Invalid iteration count
+		long cost = ByteUtils.readUint32(input, salt.length);
+		if (cost < 2 || cost > Integer.MAX_VALUE)
+			return null; // Invalid cost parameter
 		byte[] iv = new byte[STORAGE_IV_BYTES];
 		System.arraycopy(input, salt.length + INT_32_BYTES, iv, 0, iv.length);
 		// Derive the key from the password
-		SecretKey key = new SecretKey(pbkdf2(password, salt, (int) iterations));
+		SecretKey key = passwordBasedKdf.deriveKey(password, salt, (int) cost);
 		// Initialise the cipher
 		try {
 			cipher.init(false, key, iv);
@@ -411,64 +407,4 @@ class CryptoComponentImpl implements CryptoComponent {
 	public String asciiArmour(byte[] b, int lineLength) {
 		return AsciiArmour.wrap(b, lineLength);
 	}
-
-	// Password-based key derivation function - see PKCS#5 v2.1, section 5.2
-	private byte[] pbkdf2(String password, byte[] salt, int iterations) {
-		byte[] utf8 = StringUtils.toUtf8(password);
-		Digest digest = new SHA256Digest();
-		PKCS5S2ParametersGenerator gen = new PKCS5S2ParametersGenerator(digest);
-		gen.init(utf8, salt, iterations);
-		int keyLengthInBits = SecretKey.LENGTH * 8;
-		CipherParameters p = gen.generateDerivedParameters(keyLengthInBits);
-		return ((KeyParameter) p).getKey();
-	}
-
-	// Package access for testing
-	int chooseIterationCount(int targetMillis) {
-		List<Long> quickSamples = new ArrayList<>(PBKDF_SAMPLES);
-		List<Long> slowSamples = new ArrayList<>(PBKDF_SAMPLES);
-		long iterationNanos = 0, initNanos = 0;
-		while (iterationNanos <= 0 || initNanos <= 0) {
-			// Sample the running time with one iteration and two iterations
-			for (int i = 0; i < PBKDF_SAMPLES; i++) {
-				quickSamples.add(sampleRunningTime(1));
-				slowSamples.add(sampleRunningTime(2));
-			}
-			// Calculate the iteration time and the initialisation time
-			long quickMedian = median(quickSamples);
-			long slowMedian = median(slowSamples);
-			iterationNanos = slowMedian - quickMedian;
-			initNanos = quickMedian - iterationNanos;
-			if (LOG.isLoggable(INFO)) {
-				LOG.info("Init: " + initNanos + ", iteration: "
-						+ iterationNanos);
-			}
-		}
-		long targetNanos = targetMillis * 1000L * 1000L;
-		long iterations = (targetNanos - initNanos) / iterationNanos;
-		if (LOG.isLoggable(INFO)) LOG.info("Target iterations: " + iterations);
-		if (iterations < 1) return 1;
-		if (iterations > Integer.MAX_VALUE) return Integer.MAX_VALUE;
-		return (int) iterations;
-	}
-
-	private long sampleRunningTime(int iterations) {
-		byte[] password = {'p', 'a', 's', 's', 'w', 'o', 'r', 'd'};
-		byte[] salt = new byte[PBKDF_SALT_BYTES];
-		int keyLengthInBits = SecretKey.LENGTH * 8;
-		long start = System.nanoTime();
-		Digest digest = new SHA256Digest();
-		PKCS5S2ParametersGenerator gen = new PKCS5S2ParametersGenerator(digest);
-		gen.init(password, salt, iterations);
-		gen.generateDerivedParameters(keyLengthInBits);
-		return System.nanoTime() - start;
-	}
-
-	private long median(List<Long> list) {
-		int size = list.size();
-		if (size == 0) throw new IllegalArgumentException();
-		Collections.sort(list);
-		if (size % 2 == 1) return list.get(size / 2);
-		return list.get(size / 2 - 1) + list.get(size / 2) / 2;
-	}
 }
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/crypto/CryptoModule.java b/bramble-core/src/main/java/org/briarproject/bramble/crypto/CryptoModule.java
index a40b656282556a5660b23973a5b611f4793dec3e..25d24995d5ddf45b5a9aefea69fe1c5c101d0acd 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/crypto/CryptoModule.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/crypto/CryptoModule.java
@@ -67,8 +67,9 @@ public class CryptoModule {
 	@Provides
 	@Singleton
 	CryptoComponent provideCryptoComponent(
-			SecureRandomProvider secureRandomProvider) {
-		return new CryptoComponentImpl(secureRandomProvider);
+			SecureRandomProvider secureRandomProvider,
+			ScryptKdf passwordBasedKdf) {
+		return new CryptoComponentImpl(secureRandomProvider, passwordBasedKdf);
 	}
 
 	@Provides
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/crypto/PasswordBasedKdf.java b/bramble-core/src/main/java/org/briarproject/bramble/crypto/PasswordBasedKdf.java
new file mode 100644
index 0000000000000000000000000000000000000000..60604c6c2ef64eb62bc31d692aa3b81b98b480d7
--- /dev/null
+++ b/bramble-core/src/main/java/org/briarproject/bramble/crypto/PasswordBasedKdf.java
@@ -0,0 +1,10 @@
+package org.briarproject.bramble.crypto;
+
+import org.briarproject.bramble.api.crypto.SecretKey;
+
+interface PasswordBasedKdf {
+
+	int chooseCostParameter();
+
+	SecretKey deriveKey(String password, byte[] salt, int cost);
+}
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/crypto/ScryptKdf.java b/bramble-core/src/main/java/org/briarproject/bramble/crypto/ScryptKdf.java
new file mode 100644
index 0000000000000000000000000000000000000000..b5cec690619740e6aca789e28dd6339be99be6f5
--- /dev/null
+++ b/bramble-core/src/main/java/org/briarproject/bramble/crypto/ScryptKdf.java
@@ -0,0 +1,62 @@
+package org.briarproject.bramble.crypto;
+
+import org.briarproject.bramble.api.crypto.SecretKey;
+import org.briarproject.bramble.api.system.Clock;
+import org.briarproject.bramble.util.StringUtils;
+import org.spongycastle.crypto.generators.SCrypt;
+
+import java.util.logging.Logger;
+
+import javax.inject.Inject;
+
+import static java.util.logging.Level.INFO;
+
+class ScryptKdf implements PasswordBasedKdf {
+
+	private static final Logger LOG =
+			Logger.getLogger(ScryptKdf.class.getName());
+
+	private static final int MIN_COST = 512; // Min parameter N
+	private static final int MAX_COST = 1024 * 1024; // Max parameter N
+	private static final int BLOCK_SIZE = 8; // Parameter r
+	private static final int PARALLELIZATION = 1; // Parameter p
+	private static final int TARGET_MS = 1000;
+
+	private final Clock clock;
+
+	@Inject
+	ScryptKdf(Clock clock) {
+		this.clock = clock;
+	}
+
+	@Override
+	public int chooseCostParameter() {
+		// Increase the cost from min to max while measuring performance
+		int cost = MIN_COST;
+		while (cost * 2 <= MAX_COST && measureDuration(cost) * 2 <= TARGET_MS)
+			cost *= 2;
+		if (LOG.isLoggable(INFO))
+			LOG.info("KDF cost parameter " + cost);
+		return cost;
+	}
+
+	private long measureDuration(int cost) {
+		byte[] password = new byte[16], salt = new byte[32];
+		long start = clock.currentTimeMillis();
+		SCrypt.generate(password, salt, cost, BLOCK_SIZE, PARALLELIZATION,
+				SecretKey.LENGTH);
+		return clock.currentTimeMillis() - start;
+	}
+
+	@Override
+	public SecretKey deriveKey(String password, byte[] salt, int cost) {
+		long start = System.currentTimeMillis();
+		byte[] passwordBytes = StringUtils.toUtf8(password);
+		SecretKey k = new SecretKey(SCrypt.generate(passwordBytes, salt, cost,
+				BLOCK_SIZE, PARALLELIZATION, SecretKey.LENGTH));
+		long duration = System.currentTimeMillis() - start;
+		if (LOG.isLoggable(INFO))
+			LOG.info("Deriving key from password took " + duration + " ms");
+		return k;
+	}
+}
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/crypto/HashTest.java b/bramble-core/src/test/java/org/briarproject/bramble/crypto/HashTest.java
index dcd3177ba718ba875b062e664c2b8ae77900d5f5..56eda7c87587d266c8f4b1ee9cd8eebf57c4a7e3 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/crypto/HashTest.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/crypto/HashTest.java
@@ -22,7 +22,7 @@ public class HashTest extends BrambleTestCase {
 	private final byte[] inputBytes2 = new byte[0];
 
 	public HashTest() {
-		crypto = new CryptoComponentImpl(new TestSecureRandomProvider());
+		crypto = new CryptoComponentImpl(new TestSecureRandomProvider(), null);
 	}
 
 	@Test
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/crypto/KeyAgreementTest.java b/bramble-core/src/test/java/org/briarproject/bramble/crypto/KeyAgreementTest.java
index 5a3568631ca95d7846ae58df0ae5e3c1015d4285..d035bbe4b39f2b72e6c7e92c13b1c21bef05f046 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/crypto/KeyAgreementTest.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/crypto/KeyAgreementTest.java
@@ -18,7 +18,7 @@ public class KeyAgreementTest extends BrambleTestCase {
 	@Test
 	public void testDeriveSharedSecret() throws Exception {
 		CryptoComponent crypto =
-				new CryptoComponentImpl(new TestSecureRandomProvider());
+				new CryptoComponentImpl(new TestSecureRandomProvider(), null);
 		KeyPair aPair = crypto.generateAgreementKeyPair();
 		KeyPair bPair = crypto.generateAgreementKeyPair();
 		Random random = new Random();
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/crypto/KeyDerivationTest.java b/bramble-core/src/test/java/org/briarproject/bramble/crypto/KeyDerivationTest.java
index 805816e9233064e35064335069014e438d38670d..81f73f8d28f410a5c434d4d107815c416ff59d37 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/crypto/KeyDerivationTest.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/crypto/KeyDerivationTest.java
@@ -24,7 +24,7 @@ import static org.junit.Assert.assertTrue;
 public class KeyDerivationTest extends BrambleTestCase {
 
 	private final CryptoComponent crypto =
-			new CryptoComponentImpl(new TestSecureRandomProvider());
+			new CryptoComponentImpl(new TestSecureRandomProvider(), null);
 	private final TransportCrypto transportCrypto =
 			new TransportCryptoImpl(crypto);
 	private final TransportId transportId = new TransportId("id");
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/crypto/KeyEncodingAndParsingTest.java b/bramble-core/src/test/java/org/briarproject/bramble/crypto/KeyEncodingAndParsingTest.java
index 4eb7516407b0086f211ca03211766dba9ea1cbb6..a7136e5cd59714b530e78c6b344704ba1ea73ece 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/crypto/KeyEncodingAndParsingTest.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/crypto/KeyEncodingAndParsingTest.java
@@ -19,7 +19,7 @@ import static org.junit.Assert.assertTrue;
 public class KeyEncodingAndParsingTest extends BrambleTestCase {
 
 	private final CryptoComponentImpl crypto =
-			new CryptoComponentImpl(new TestSecureRandomProvider());
+			new CryptoComponentImpl(new TestSecureRandomProvider(), null);
 
 	@Test
 	public void testAgreementPublicKeyLength() throws Exception {
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/crypto/MacTest.java b/bramble-core/src/test/java/org/briarproject/bramble/crypto/MacTest.java
index 8541c4a1d3c5ed4193700698c41d5358bc08fd67..7fb0717b79746ff19c8f31893fdb23ed9f935675 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/crypto/MacTest.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/crypto/MacTest.java
@@ -17,7 +17,7 @@ import static org.junit.Assert.assertFalse;
 public class MacTest extends BrambleTestCase {
 
 	private final CryptoComponent crypto =
-			new CryptoComponentImpl(new TestSecureRandomProvider());
+			new CryptoComponentImpl(new TestSecureRandomProvider(), null);
 
 	private final SecretKey key1 = getSecretKey(), key2 = getSecretKey();
 	private final String label1 = getRandomString(123);
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/crypto/PasswordBasedKdfTest.java b/bramble-core/src/test/java/org/briarproject/bramble/crypto/PasswordBasedEncryptionTest.java
similarity index 60%
rename from bramble-core/src/test/java/org/briarproject/bramble/crypto/PasswordBasedKdfTest.java
rename to bramble-core/src/test/java/org/briarproject/bramble/crypto/PasswordBasedEncryptionTest.java
index efdd90dc655b053bcc713ce91d7bff3fc1a6912b..5efc3593ebd79ddd9485a79a272586f4d7c3ae76 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/crypto/PasswordBasedKdfTest.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/crypto/PasswordBasedEncryptionTest.java
@@ -1,5 +1,6 @@
 package org.briarproject.bramble.crypto;
 
+import org.briarproject.bramble.system.SystemClock;
 import org.briarproject.bramble.test.BrambleTestCase;
 import org.briarproject.bramble.test.TestSecureRandomProvider;
 import org.briarproject.bramble.test.TestUtils;
@@ -8,14 +9,13 @@ import org.junit.Test;
 import java.util.Random;
 
 import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
 
-public class PasswordBasedKdfTest extends BrambleTestCase {
+public class PasswordBasedEncryptionTest extends BrambleTestCase {
 
 	private final CryptoComponentImpl crypto =
-			new CryptoComponentImpl(new TestSecureRandomProvider());
+			new CryptoComponentImpl(new TestSecureRandomProvider(),
+					new ScryptKdf(new SystemClock()));
 
 	@Test
 	public void testEncryptionAndDecryption() {
@@ -37,17 +37,4 @@ public class PasswordBasedKdfTest extends BrambleTestCase {
 		byte[] output = crypto.decryptWithPassword(ciphertext, password);
 		assertNull(output);
 	}
-
-	@Test
-	public void testCalibration() {
-		// If the target time is unachievable, one iteration should be used
-		int iterations = crypto.chooseIterationCount(0);
-		assertEquals(1, iterations);
-		// If the target time is long, more than one iteration should be used
-		iterations = crypto.chooseIterationCount(10 * 1000);
-		assertTrue(iterations > 1);
-		// If the target time is very long, max iterations should be used
-		iterations = crypto.chooseIterationCount(Integer.MAX_VALUE);
-		assertEquals(Integer.MAX_VALUE, iterations);
-	}
 }
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/crypto/ScryptKdfTest.java b/bramble-core/src/test/java/org/briarproject/bramble/crypto/ScryptKdfTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..d53925245532048a202702c6a0ddad83baa964e0
--- /dev/null
+++ b/bramble-core/src/test/java/org/briarproject/bramble/crypto/ScryptKdfTest.java
@@ -0,0 +1,87 @@
+package org.briarproject.bramble.crypto;
+
+import org.briarproject.bramble.api.Bytes;
+import org.briarproject.bramble.api.crypto.SecretKey;
+import org.briarproject.bramble.api.system.Clock;
+import org.briarproject.bramble.system.SystemClock;
+import org.briarproject.bramble.test.BrambleTestCase;
+import org.junit.Test;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import static junit.framework.TestCase.assertTrue;
+import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
+import static org.briarproject.bramble.util.StringUtils.getRandomString;
+import static org.junit.Assert.assertEquals;
+
+public class ScryptKdfTest extends BrambleTestCase {
+
+	@Test
+	public void testPasswordAffectsKey() throws Exception {
+		PasswordBasedKdf kdf = new ScryptKdf(new SystemClock());
+		byte[] salt = getRandomBytes(32);
+		Set<Bytes> keys = new HashSet<>();
+		for (int i = 0; i < 100; i++) {
+			String password = getRandomString(16);
+			SecretKey key = kdf.deriveKey(password, salt, 256);
+			assertTrue(keys.add(new Bytes(key.getBytes())));
+		}
+	}
+
+	@Test
+	public void testSaltAffectsKey() throws Exception {
+		PasswordBasedKdf kdf = new ScryptKdf(new SystemClock());
+		String password = getRandomString(16);
+		Set<Bytes> keys = new HashSet<>();
+		for (int i = 0; i < 100; i++) {
+			byte[] salt = getRandomBytes(32);
+			SecretKey key = kdf.deriveKey(password, salt, 256);
+			assertTrue(keys.add(new Bytes(key.getBytes())));
+		}
+	}
+
+	@Test
+	public void testCostParameterAffectsKey() throws Exception {
+		PasswordBasedKdf kdf = new ScryptKdf(new SystemClock());
+		String password = getRandomString(16);
+		byte[] salt = getRandomBytes(32);
+		Set<Bytes> keys = new HashSet<>();
+		for (int cost = 2; cost <= 256; cost *= 2) {
+			SecretKey key = kdf.deriveKey(password, salt, cost);
+			assertTrue(keys.add(new Bytes(key.getBytes())));
+		}
+	}
+
+	@Test
+	public void testCalibration() throws Exception {
+		Clock clock = new ArrayClock(
+				0, 100, // Duration for cost 512
+				0, 200, // Duration for cost 1024
+				0, 400, // Duration for cost 2048
+				0, 800 // Duration for cost 4096
+		);
+		PasswordBasedKdf kdf = new ScryptKdf(clock);
+		assertEquals(4096, kdf.chooseCostParameter());
+	}
+
+	private static class ArrayClock implements Clock {
+
+		private final long[] times;
+		private int index = 0;
+
+		private ArrayClock(long... times) {
+			this.times = times;
+		}
+
+		@Override
+		public long currentTimeMillis() {
+			return times[index++];
+		}
+
+		@Override
+		public void sleep(long milliseconds) throws InterruptedException {
+			Thread.sleep(milliseconds);
+		}
+	}
+}
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/crypto/SignatureTest.java b/bramble-core/src/test/java/org/briarproject/bramble/crypto/SignatureTest.java
index 1abe4a71a56ad55252e994daa1a60154d7ace798..09561c8c69d1fa36aa36c2a5ad82564df37143e0 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/crypto/SignatureTest.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/crypto/SignatureTest.java
@@ -32,7 +32,7 @@ public abstract class SignatureTest extends BrambleTestCase {
 			byte[] publicKey, byte[] signature) throws GeneralSecurityException;
 
 	SignatureTest() {
-		crypto = new CryptoComponentImpl(new TestSecureRandomProvider());
+		crypto = new CryptoComponentImpl(new TestSecureRandomProvider(), null);
 		KeyPair k = generateKeyPair();
 		publicKey = k.getPublic().getEncoded();
 		privateKey = k.getPrivate().getEncoded();
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/sync/SyncIntegrationTestComponent.java b/bramble-core/src/test/java/org/briarproject/bramble/sync/SyncIntegrationTestComponent.java
index 6c0ffa19cb24562228d945305862a1718718c20c..a714520dd5df088f882a160d33b3d7c6a48be117 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/sync/SyncIntegrationTestComponent.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/sync/SyncIntegrationTestComponent.java
@@ -1,7 +1,8 @@
 package org.briarproject.bramble.sync;
 
 import org.briarproject.bramble.crypto.CryptoModule;
-import org.briarproject.bramble.test.TestSeedProviderModule;
+import org.briarproject.bramble.system.SystemModule;
+import org.briarproject.bramble.test.TestSecureRandomModule;
 import org.briarproject.bramble.transport.TransportModule;
 
 import javax.inject.Singleton;
@@ -10,9 +11,10 @@ import dagger.Component;
 
 @Singleton
 @Component(modules = {
-		TestSeedProviderModule.class,
+		TestSecureRandomModule.class,
 		CryptoModule.class,
 		SyncModule.class,
+		SystemModule.class,
 		TransportModule.class
 })
 interface SyncIntegrationTestComponent {
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/test/TestSeedProviderModule.java b/bramble-core/src/test/java/org/briarproject/bramble/test/TestSecureRandomModule.java
similarity index 63%
rename from bramble-core/src/test/java/org/briarproject/bramble/test/TestSeedProviderModule.java
rename to bramble-core/src/test/java/org/briarproject/bramble/test/TestSecureRandomModule.java
index b2e4db7d9c29f80b0fceba4191cf963523e92786..97cdd0cf31496dc93eb3da731ac82ab5815029ed 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/test/TestSeedProviderModule.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/test/TestSecureRandomModule.java
@@ -2,17 +2,14 @@ package org.briarproject.bramble.test;
 
 import org.briarproject.bramble.api.system.SecureRandomProvider;
 
-import javax.inject.Singleton;
-
 import dagger.Module;
 import dagger.Provides;
 
 @Module
-public class TestSeedProviderModule {
+public class TestSecureRandomModule {
 
 	@Provides
-	@Singleton
-	SecureRandomProvider provideSeedProvider() {
+	SecureRandomProvider provideSecureRandomProvider() {
 		return new TestSecureRandomProvider();
 	}
 }
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/login/PasswordFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/login/PasswordFragment.java
index 80b8e04e417e7cbeca12002bdedd26118754236e..3b5f788b746aaa4739e8fca9358baf6beab9e1e3 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/login/PasswordFragment.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/login/PasswordFragment.java
@@ -1,11 +1,13 @@
 package org.briarproject.briar.android.login;
 
 import android.os.Bundle;
+import android.os.IBinder;
 import android.support.design.widget.TextInputEditText;
 import android.support.design.widget.TextInputLayout;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.inputmethod.InputMethodManager;
 import android.widget.Button;
 import android.widget.ProgressBar;
 
@@ -13,6 +15,7 @@ import org.briarproject.briar.R;
 import org.briarproject.briar.android.activity.ActivityComponent;
 import org.briarproject.briar.android.util.UiUtils;
 
+import static android.content.Context.INPUT_METHOD_SERVICE;
 import static android.view.View.INVISIBLE;
 import static android.view.View.VISIBLE;
 import static org.briarproject.bramble.api.crypto.PasswordStrengthEstimator.QUITE_WEAK;
@@ -107,6 +110,9 @@ public class PasswordFragment extends SetupFragment {
 			progressBar.setVisibility(VISIBLE);
 		}
 		String password = passwordEntry.getText().toString();
+		IBinder token = passwordEntry.getWindowToken();
+		Object o = getContext().getSystemService(INPUT_METHOD_SERVICE);
+		((InputMethodManager) o).hideSoftInputFromWindow(token, 0);
 		setupController.setPassword(password);
 		setupController.showDozeOrCreateAccount();
 	}
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 ce5ec5a00dfecbc9d2b5c8b6f0af92abb5d776e1..0a8fbfbe62db6c661dcfbe89008aa432ede1b055 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
@@ -13,7 +13,7 @@ import org.briarproject.bramble.sync.SyncModule;
 import org.briarproject.bramble.system.SystemModule;
 import org.briarproject.bramble.test.TestDatabaseModule;
 import org.briarproject.bramble.test.TestPluginConfigModule;
-import org.briarproject.bramble.test.TestSeedProviderModule;
+import org.briarproject.bramble.test.TestSecureRandomModule;
 import org.briarproject.bramble.test.TestSocksModule;
 import org.briarproject.bramble.transport.TransportModule;
 import org.briarproject.briar.api.blog.BlogManager;
@@ -30,7 +30,7 @@ import dagger.Component;
 @Component(modules = {
 		TestDatabaseModule.class,
 		TestPluginConfigModule.class,
-		TestSeedProviderModule.class,
+		TestSecureRandomModule.class,
 		TestSocksModule.class,
 		TestDnsModule.class,
 		LifecycleModule.class,
diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTestComponent.java b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTestComponent.java
index 538d4def320ba2cbbbea1e9a1cd0150bf77f7a4d..bc0ea62410713a6ae9f4a3f5034c8e10c187111b 100644
--- a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTestComponent.java
+++ b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTestComponent.java
@@ -13,7 +13,7 @@ import org.briarproject.bramble.sync.SyncModule;
 import org.briarproject.bramble.system.SystemModule;
 import org.briarproject.bramble.test.TestDatabaseModule;
 import org.briarproject.bramble.test.TestPluginConfigModule;
-import org.briarproject.bramble.test.TestSeedProviderModule;
+import org.briarproject.bramble.test.TestSecureRandomModule;
 import org.briarproject.bramble.transport.TransportModule;
 import org.briarproject.briar.blog.BlogModule;
 import org.briarproject.briar.client.BriarClientModule;
@@ -32,7 +32,7 @@ import dagger.Component;
 @Component(modules = {
 		TestDatabaseModule.class,
 		TestPluginConfigModule.class,
-		TestSeedProviderModule.class,
+		TestSecureRandomModule.class,
 		BlogModule.class,
 		BriarClientModule.class,
 		ClientModule.class,
diff --git a/briar-core/src/test/java/org/briarproject/briar/messaging/MessageSizeIntegrationTestComponent.java b/briar-core/src/test/java/org/briarproject/briar/messaging/MessageSizeIntegrationTestComponent.java
index 2c91a0bd87bb4ac536309505b2620d22b8ff12e8..42b27da6f3c4265d021bb376316f65cc2f1944ab 100644
--- a/briar-core/src/test/java/org/briarproject/briar/messaging/MessageSizeIntegrationTestComponent.java
+++ b/briar-core/src/test/java/org/briarproject/briar/messaging/MessageSizeIntegrationTestComponent.java
@@ -10,7 +10,7 @@ import org.briarproject.bramble.sync.SyncModule;
 import org.briarproject.bramble.system.SystemModule;
 import org.briarproject.bramble.test.TestDatabaseModule;
 import org.briarproject.bramble.test.TestLifecycleModule;
-import org.briarproject.bramble.test.TestSeedProviderModule;
+import org.briarproject.bramble.test.TestSecureRandomModule;
 import org.briarproject.briar.client.BriarClientModule;
 import org.briarproject.briar.forum.ForumModule;
 
@@ -22,7 +22,7 @@ import dagger.Component;
 @Component(modules = {
 		TestDatabaseModule.class,
 		TestLifecycleModule.class,
-		TestSeedProviderModule.class,
+		TestSecureRandomModule.class,
 		BriarClientModule.class,
 		ClientModule.class,
 		CryptoModule.class,
diff --git a/briar-core/src/test/java/org/briarproject/briar/messaging/SimplexMessagingIntegrationTestComponent.java b/briar-core/src/test/java/org/briarproject/briar/messaging/SimplexMessagingIntegrationTestComponent.java
index 085d28ae1ceff45921da742e71bfc856d1295e5e..d740859aa130141e1c1fe5fee92fa3ad2e3ee8e0 100644
--- a/briar-core/src/test/java/org/briarproject/briar/messaging/SimplexMessagingIntegrationTestComponent.java
+++ b/briar-core/src/test/java/org/briarproject/briar/messaging/SimplexMessagingIntegrationTestComponent.java
@@ -20,7 +20,7 @@ import org.briarproject.bramble.sync.SyncModule;
 import org.briarproject.bramble.system.SystemModule;
 import org.briarproject.bramble.test.TestDatabaseModule;
 import org.briarproject.bramble.test.TestPluginConfigModule;
-import org.briarproject.bramble.test.TestSeedProviderModule;
+import org.briarproject.bramble.test.TestSecureRandomModule;
 import org.briarproject.bramble.transport.TransportModule;
 import org.briarproject.briar.api.messaging.MessagingManager;
 import org.briarproject.briar.api.messaging.PrivateMessageFactory;
@@ -34,7 +34,7 @@ import dagger.Component;
 @Component(modules = {
 		TestDatabaseModule.class,
 		TestPluginConfigModule.class,
-		TestSeedProviderModule.class,
+		TestSecureRandomModule.class,
 		BriarClientModule.class,
 		ClientModule.class,
 		ContactModule.class,
diff --git a/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTestComponent.java b/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTestComponent.java
index 679d7772aa7798c863aa4129dfbd27e347760b97..1b2344d3add2760d94dab0aae8f09babbc052a0d 100644
--- a/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTestComponent.java
+++ b/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTestComponent.java
@@ -22,7 +22,7 @@ import org.briarproject.bramble.sync.SyncModule;
 import org.briarproject.bramble.system.SystemModule;
 import org.briarproject.bramble.test.TestDatabaseModule;
 import org.briarproject.bramble.test.TestPluginConfigModule;
-import org.briarproject.bramble.test.TestSeedProviderModule;
+import org.briarproject.bramble.test.TestSecureRandomModule;
 import org.briarproject.bramble.transport.TransportModule;
 import org.briarproject.briar.api.blog.BlogFactory;
 import org.briarproject.briar.api.blog.BlogManager;
@@ -50,7 +50,7 @@ import dagger.Component;
 @Component(modules = {
 		TestDatabaseModule.class,
 		TestPluginConfigModule.class,
-		TestSeedProviderModule.class,
+		TestSecureRandomModule.class,
 		BlogModule.class,
 		BriarClientModule.class,
 		ClientModule.class,