From d9c63bbcfeb66197d257ff04bcd492b6f16e899b Mon Sep 17 00:00:00 2001
From: akwizgran <akwizgran@users.sourceforge.net>
Date: Wed, 29 Mar 2017 15:01:13 +0100
Subject: [PATCH] Remove Fortuna generator, fix Android SecureRandom bug.

---
 .../system/AndroidSecureRandomProvider.java   |  89 ++++++++++++
 .../bramble/system/AndroidSeedProvider.java   |  42 ------
 .../bramble/system/AndroidSystemModule.java   |   6 +-
 .../api/system/SecureRandomProvider.java      |  23 ++++
 .../bramble/api/system/SeedProvider.java      |  18 ---
 .../bramble/crypto/CombinedSecureRandom.java  |  62 ---------
 .../bramble/crypto/CryptoComponentImpl.java   |  65 +++++++--
 .../bramble/crypto/CryptoModule.java          |   7 +-
 .../bramble/crypto/DoubleDigest.java          |  76 -----------
 .../bramble/crypto/FortunaGenerator.java      | 114 ----------------
 .../bramble/crypto/FortunaSecureRandom.java   |  81 -----------
 .../bramble/crypto/PseudoRandomImpl.java      |  30 ++--
 .../system/AbstractSecureRandomProvider.java  |  42 ++++++
 .../system/LinuxSecureRandomProvider.java     |  69 ++++++++++
 .../bramble/system/LinuxSecureRandomSpi.java  |  64 +++++++++
 .../bramble/system/LinuxSeedProvider.java     |  75 ----------
 .../EllipticCurveMultiplicationTest.java      |   4 +-
 .../bramble/crypto/FortunaGeneratorTest.java  |  99 --------------
 .../crypto/FortunaSecureRandomTest.java       |  67 ---------
 .../briarproject/bramble/crypto/HashTest.java |   4 +-
 .../bramble/crypto/KeyAgreementTest.java      |  14 +-
 .../bramble/crypto/KeyDerivationTest.java     |  16 +--
 .../crypto/KeyEncodingAndParsingTest.java     |   4 +-
 .../briarproject/bramble/crypto/MacTest.java  |   4 +-
 .../bramble/crypto/PasswordBasedKdfTest.java  |   4 +-
 .../bramble/crypto/PseudoSecureRandom.java    |  48 +++++++
 .../bramble/crypto/SignatureTest.java         |   4 +-
 .../system/LinuxSecureRandomProviderTest.java |  57 ++++++++
 .../system/LinuxSecureRandomSpiTest.java      | 128 ++++++++++++++++++
 .../bramble/system/LinuxSeedProviderTest.java |  90 ------------
 .../test/TestSecureRandomProvider.java        |  16 +++
 .../bramble/test/TestSeedProvider.java        |  13 --
 .../bramble/test/TestSeedProviderModule.java  |   6 +-
 .../system/DesktopSecureRandomModule.java     |  19 +++
 .../system/DesktopSeedProviderModule.java     |  19 ---
 35 files changed, 663 insertions(+), 816 deletions(-)
 create mode 100644 bramble-android/src/main/java/org/briarproject/bramble/system/AndroidSecureRandomProvider.java
 delete mode 100644 bramble-android/src/main/java/org/briarproject/bramble/system/AndroidSeedProvider.java
 create mode 100644 bramble-api/src/main/java/org/briarproject/bramble/api/system/SecureRandomProvider.java
 delete mode 100644 bramble-api/src/main/java/org/briarproject/bramble/api/system/SeedProvider.java
 delete mode 100644 bramble-core/src/main/java/org/briarproject/bramble/crypto/CombinedSecureRandom.java
 delete mode 100644 bramble-core/src/main/java/org/briarproject/bramble/crypto/DoubleDigest.java
 delete mode 100644 bramble-core/src/main/java/org/briarproject/bramble/crypto/FortunaGenerator.java
 delete mode 100644 bramble-core/src/main/java/org/briarproject/bramble/crypto/FortunaSecureRandom.java
 create mode 100644 bramble-core/src/main/java/org/briarproject/bramble/system/AbstractSecureRandomProvider.java
 create mode 100644 bramble-core/src/main/java/org/briarproject/bramble/system/LinuxSecureRandomProvider.java
 create mode 100644 bramble-core/src/main/java/org/briarproject/bramble/system/LinuxSecureRandomSpi.java
 delete mode 100644 bramble-core/src/main/java/org/briarproject/bramble/system/LinuxSeedProvider.java
 delete mode 100644 bramble-core/src/test/java/org/briarproject/bramble/crypto/FortunaGeneratorTest.java
 delete mode 100644 bramble-core/src/test/java/org/briarproject/bramble/crypto/FortunaSecureRandomTest.java
 create mode 100644 bramble-core/src/test/java/org/briarproject/bramble/crypto/PseudoSecureRandom.java
 create mode 100644 bramble-core/src/test/java/org/briarproject/bramble/system/LinuxSecureRandomProviderTest.java
 create mode 100644 bramble-core/src/test/java/org/briarproject/bramble/system/LinuxSecureRandomSpiTest.java
 delete mode 100644 bramble-core/src/test/java/org/briarproject/bramble/system/LinuxSeedProviderTest.java
 create mode 100644 bramble-core/src/test/java/org/briarproject/bramble/test/TestSecureRandomProvider.java
 delete mode 100644 bramble-core/src/test/java/org/briarproject/bramble/test/TestSeedProvider.java
 create mode 100644 bramble-j2se/src/main/java/org/briarproject/bramble/system/DesktopSecureRandomModule.java
 delete mode 100644 bramble-j2se/src/main/java/org/briarproject/bramble/system/DesktopSeedProviderModule.java

diff --git a/bramble-android/src/main/java/org/briarproject/bramble/system/AndroidSecureRandomProvider.java b/bramble-android/src/main/java/org/briarproject/bramble/system/AndroidSecureRandomProvider.java
new file mode 100644
index 0000000000..502e7b9821
--- /dev/null
+++ b/bramble-android/src/main/java/org/briarproject/bramble/system/AndroidSecureRandomProvider.java
@@ -0,0 +1,89 @@
+package org.briarproject.bramble.system;
+
+import android.app.Application;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiManager;
+import android.os.Build;
+import android.os.Parcel;
+import android.provider.Settings;
+
+import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
+
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+import javax.annotation.concurrent.Immutable;
+import javax.inject.Inject;
+
+import static android.content.Context.WIFI_SERVICE;
+import static android.provider.Settings.Secure.ANDROID_ID;
+
+@Immutable
+@NotNullByDefault
+class AndroidSecureRandomProvider extends LinuxSecureRandomProvider {
+
+	private static final int SEED_LENGTH = 32;
+
+	private final Context appContext;
+
+	@Inject
+	AndroidSecureRandomProvider(Application app) {
+		appContext = app.getApplicationContext();
+	}
+
+	@Override
+	protected void writeToEntropyPool(DataOutputStream out) throws IOException {
+		super.writeToEntropyPool(out);
+		out.writeInt(android.os.Process.myPid());
+		out.writeInt(android.os.Process.myTid());
+		out.writeInt(android.os.Process.myUid());
+		if (Build.FINGERPRINT != null) out.writeUTF(Build.FINGERPRINT);
+		if (Build.SERIAL != null) out.writeUTF(Build.SERIAL);
+		ContentResolver contentResolver = appContext.getContentResolver();
+		String id = Settings.Secure.getString(contentResolver, ANDROID_ID);
+		if (id != null) out.writeUTF(id);
+		Parcel parcel = Parcel.obtain();
+		WifiManager wm =
+				(WifiManager) appContext.getSystemService(WIFI_SERVICE);
+		for (WifiConfiguration config : wm.getConfiguredNetworks())
+			parcel.writeParcelable(config, 0);
+		BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter();
+		if (bt != null) {
+			for (BluetoothDevice device : bt.getBondedDevices())
+				parcel.writeParcelable(device, 0);
+		}
+		out.write(parcel.marshall());
+		parcel.recycle();
+	}
+
+	@Override
+	protected void writeSeed() {
+		super.writeSeed();
+		if (Build.VERSION.SDK_INT >= 16 && Build.VERSION.SDK_INT <= 18)
+			applyOpenSslFix();
+	}
+
+	// Based on https://android-developers.googleblog.com/2013/08/some-securerandom-thoughts.html
+	private void applyOpenSslFix() {
+		byte[] seed = new LinuxSecureRandomSpi().engineGenerateSeed(
+				SEED_LENGTH);
+		try {
+			// Seed the OpenSSL PRNG
+			Class.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto")
+					.getMethod("RAND_seed", byte[].class)
+					.invoke(null, seed);
+			// Mix the output of the Linux PRNG into the OpenSSL PRNG
+			int bytesRead = (Integer) Class.forName(
+					"org.apache.harmony.xnet.provider.jsse.NativeCrypto")
+					.getMethod("RAND_load_file", String.class, long.class)
+					.invoke(null, "/dev/urandom", 1024);
+			if (bytesRead != 1024) throw new IOException();
+		} catch (Exception e) {
+			throw new SecurityException(e);
+		}
+	}
+}
diff --git a/bramble-android/src/main/java/org/briarproject/bramble/system/AndroidSeedProvider.java b/bramble-android/src/main/java/org/briarproject/bramble/system/AndroidSeedProvider.java
deleted file mode 100644
index 52d15db5e3..0000000000
--- a/bramble-android/src/main/java/org/briarproject/bramble/system/AndroidSeedProvider.java
+++ /dev/null
@@ -1,42 +0,0 @@
-package org.briarproject.bramble.system;
-
-import android.app.Application;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.os.Build;
-import android.provider.Settings;
-
-import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-
-import java.io.DataOutputStream;
-import java.io.IOException;
-
-import javax.annotation.concurrent.Immutable;
-import javax.inject.Inject;
-
-import static android.provider.Settings.Secure.ANDROID_ID;
-
-@Immutable
-@NotNullByDefault
-class AndroidSeedProvider extends LinuxSeedProvider {
-
-	private final Context appContext;
-
-	@Inject
-	AndroidSeedProvider(Application app) {
-		appContext = app.getApplicationContext();
-	}
-
-	@Override
-	void writeToEntropyPool(DataOutputStream out) throws IOException {
-		out.writeInt(android.os.Process.myPid());
-		out.writeInt(android.os.Process.myTid());
-		out.writeInt(android.os.Process.myUid());
-		if (Build.FINGERPRINT != null) out.writeUTF(Build.FINGERPRINT);
-		if (Build.SERIAL != null) out.writeUTF(Build.SERIAL);
-		ContentResolver contentResolver = appContext.getContentResolver();
-		String id = Settings.Secure.getString(contentResolver, ANDROID_ID);
-		if (id != null) out.writeUTF(id);
-		super.writeToEntropyPool(out);
-	}
-}
diff --git a/bramble-android/src/main/java/org/briarproject/bramble/system/AndroidSystemModule.java b/bramble-android/src/main/java/org/briarproject/bramble/system/AndroidSystemModule.java
index e5976ff995..79fbf44b54 100644
--- a/bramble-android/src/main/java/org/briarproject/bramble/system/AndroidSystemModule.java
+++ b/bramble-android/src/main/java/org/briarproject/bramble/system/AndroidSystemModule.java
@@ -4,7 +4,7 @@ import android.app.Application;
 
 import org.briarproject.bramble.api.system.AndroidExecutor;
 import org.briarproject.bramble.api.system.LocationUtils;
-import org.briarproject.bramble.api.system.SeedProvider;
+import org.briarproject.bramble.api.system.SecureRandomProvider;
 
 import javax.inject.Singleton;
 
@@ -16,8 +16,8 @@ public class AndroidSystemModule {
 
 	@Provides
 	@Singleton
-	SeedProvider provideSeedProvider(Application app) {
-		return new AndroidSeedProvider(app);
+	SecureRandomProvider provideSecureRandomProvider(Application app) {
+		return new AndroidSecureRandomProvider(app);
 	}
 
 	@Provides
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/system/SecureRandomProvider.java b/bramble-api/src/main/java/org/briarproject/bramble/api/system/SecureRandomProvider.java
new file mode 100644
index 0000000000..7d821ba2cd
--- /dev/null
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/system/SecureRandomProvider.java
@@ -0,0 +1,23 @@
+package org.briarproject.bramble.api.system;
+
+import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
+
+import java.security.Provider;
+import java.security.SecureRandom;
+
+import javax.annotation.Nullable;
+
+/**
+ * Wrapper for a platform-specific secure random number generator.
+ */
+@NotNullByDefault
+public interface SecureRandomProvider {
+
+	/**
+	 * Returns a {@link Provider} that provides a strong {@link SecureRandom}
+	 * implementation, or null if the platform's default implementation should
+	 * be used.
+	 */
+	@Nullable
+	Provider getProvider();
+}
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/system/SeedProvider.java b/bramble-api/src/main/java/org/briarproject/bramble/api/system/SeedProvider.java
deleted file mode 100644
index 2d5c147f16..0000000000
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/system/SeedProvider.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package org.briarproject.bramble.api.system;
-
-import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-
-/**
- * Uses a platform-specific source to provide a seed for a pseudo-random
- * number generator.
- */
-@NotNullByDefault
-public interface SeedProvider {
-
-	/**
-	 * The length of the seed in bytes.
-	 */
-	int SEED_BYTES = 32;
-
-	byte[] getSeed();
-}
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/crypto/CombinedSecureRandom.java b/bramble-core/src/main/java/org/briarproject/bramble/crypto/CombinedSecureRandom.java
deleted file mode 100644
index 82c9d50d60..0000000000
--- a/bramble-core/src/main/java/org/briarproject/bramble/crypto/CombinedSecureRandom.java
+++ /dev/null
@@ -1,62 +0,0 @@
-package org.briarproject.bramble.crypto;
-
-import java.security.Provider;
-import java.security.SecureRandom;
-import java.security.SecureRandomSpi;
-
-/**
- * A {@link SecureRandom} implementation that combines the outputs of two or
- * more other implementations using XOR.
- */
-class CombinedSecureRandom extends SecureRandom {
-
-	private static final Provider PROVIDER = new CombinedProvider();
-
-	CombinedSecureRandom(SecureRandom... randoms) {
-		super(new CombinedSecureRandomSpi(randoms), PROVIDER);
-	}
-
-	private static class CombinedSecureRandomSpi extends SecureRandomSpi {
-
-		private final SecureRandom[] randoms;
-
-		private CombinedSecureRandomSpi(SecureRandom... randoms) {
-			if (randoms.length < 2) throw new IllegalArgumentException();
-			this.randoms = randoms;
-		}
-
-		@Override
-		protected byte[] engineGenerateSeed(int numBytes) {
-			byte[] combined = new byte[numBytes];
-			for (SecureRandom random : randoms) {
-				byte[] b = random.generateSeed(numBytes);
-				int length = Math.min(numBytes, b.length);
-				for (int i = 0; i < length; i++)
-					combined[i] = (byte) (combined[i] ^ b[i]);
-			}
-			return combined;
-		}
-
-		@Override
-		protected void engineNextBytes(byte[] b) {
-			byte[] temp = new byte[b.length];
-			for (SecureRandom random : randoms) {
-				random.nextBytes(temp);
-				for (int i = 0; i < b.length; i++)
-					b[i] = (byte) (b[i] ^ temp[i]);
-			}
-		}
-
-		@Override
-		protected void engineSetSeed(byte[] seed) {
-			for (SecureRandom random : randoms) random.setSeed(seed);
-		}
-	}
-
-	private static class CombinedProvider extends Provider {
-
-		private CombinedProvider() {
-			super("Combined", 1.0, "");
-		}
-	}
-}
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 ae25da4e18..197ef79738 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
@@ -8,7 +8,7 @@ import org.briarproject.bramble.api.crypto.PseudoRandom;
 import org.briarproject.bramble.api.crypto.PublicKey;
 import org.briarproject.bramble.api.crypto.SecretKey;
 import org.briarproject.bramble.api.plugin.TransportId;
-import org.briarproject.bramble.api.system.SeedProvider;
+import org.briarproject.bramble.api.system.SecureRandomProvider;
 import org.briarproject.bramble.api.transport.IncomingKeys;
 import org.briarproject.bramble.api.transport.OutgoingKeys;
 import org.briarproject.bramble.api.transport.TransportKeys;
@@ -29,7 +29,10 @@ import org.spongycastle.crypto.params.KeyParameter;
 
 import java.nio.charset.Charset;
 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;
@@ -101,16 +104,26 @@ class CryptoComponentImpl implements CryptoComponent {
 	private final MessageEncrypter messageEncrypter;
 
 	@Inject
-	CryptoComponentImpl(SeedProvider seedProvider) {
-		if (!FortunaSecureRandom.selfTest()) throw new RuntimeException();
-		SecureRandom platformSecureRandom = new SecureRandom();
+	CryptoComponentImpl(SecureRandomProvider secureRandomProvider) {
 		if (LOG.isLoggable(INFO)) {
-			String provider = platformSecureRandom.getProvider().getName();
-			String algorithm = platformSecureRandom.getAlgorithm();
-			LOG.info("Default SecureRandom: " + provider + " " + algorithm);
+			SecureRandom defaultSecureRandom = new SecureRandom();
+			String name = defaultSecureRandom.getProvider().getName();
+			String algorithm = defaultSecureRandom.getAlgorithm();
+			LOG.info("Default SecureRandom: " + name + " " + algorithm);
 		}
-		SecureRandom fortuna = new FortunaSecureRandom(seedProvider.getSeed());
-		secureRandom = new CombinedSecureRandom(platformSecureRandom, fortuna);
+		Provider provider = secureRandomProvider.getProvider();
+		if (provider == null) {
+			LOG.info("Using default");
+		} else {
+			installSecureRandomProvider(provider);
+			if (LOG.isLoggable(INFO)) {
+				SecureRandom installedSecureRandom = new SecureRandom();
+				String name = installedSecureRandom.getProvider().getName();
+				String algorithm = installedSecureRandom.getAlgorithm();
+				LOG.info("Installed SecureRandom: " + name + " " + algorithm);
+			}
+		}
+		secureRandom = new SecureRandom();
 		ECKeyGenerationParameters params = new ECKeyGenerationParameters(
 				PARAMETERS, secureRandom);
 		agreementKeyPairGenerator = new ECKeyPairGenerator();
@@ -124,6 +137,31 @@ class CryptoComponentImpl implements CryptoComponent {
 		messageEncrypter = new MessageEncrypter(secureRandom);
 	}
 
+	// Based on https://android-developers.googleblog.com/2013/08/some-securerandom-thoughts.html
+	private void installSecureRandomProvider(Provider provider) {
+		Provider[] providers = Security.getProviders("SecureRandom.SHA1PRNG");
+		if (providers == null || providers.length == 0
+				|| !provider.getClass().equals(providers[0].getClass())) {
+			Security.insertProviderAt(provider, 1);
+		}
+		// Check the new provider is the default when no algorithm is specified
+		SecureRandom random = new SecureRandom();
+		if (!provider.getClass().equals(random.getProvider().getClass())) {
+			throw new SecurityException("Wrong SecureRandom provider: "
+					+ random.getProvider().getClass());
+		}
+		// Check the new provider is the default when SHA1PRNG is specified
+		try {
+			random = SecureRandom.getInstance("SHA1PRNG");
+		} catch (NoSuchAlgorithmException e) {
+			throw new SecurityException(e);
+		}
+		if (!provider.getClass().equals(random.getProvider().getClass())) {
+			throw new SecurityException("Wrong SHA1PRNG provider: "
+					+ random.getProvider().getClass());
+		}
+	}
+
 	@Override
 	public SecretKey generateSecretKey() {
 		byte[] b = new byte[SecretKey.LENGTH];
@@ -133,7 +171,10 @@ class CryptoComponentImpl implements CryptoComponent {
 
 	@Override
 	public PseudoRandom getPseudoRandom(int seed1, int seed2) {
-		return new PseudoRandomImpl(seed1, seed2);
+		byte[] seed = new byte[INT_32_BYTES * 2];
+		ByteUtils.writeUint32(seed1, seed, 0);
+		ByteUtils.writeUint32(seed2, seed, INT_32_BYTES);
+		return new PseudoRandomImpl(seed);
 	}
 
 	@Override
@@ -296,7 +337,7 @@ class CryptoComponentImpl implements CryptoComponent {
 	public SecretKey deriveMasterSecret(byte[] theirPublicKey,
 			KeyPair ourKeyPair, boolean alice) throws GeneralSecurityException {
 		return deriveMasterSecret(deriveSharedSecret(
-				theirPublicKey,ourKeyPair, alice));
+				theirPublicKey, ourKeyPair, alice));
 	}
 
 	@Override
@@ -607,7 +648,7 @@ class CryptoComponentImpl implements CryptoComponent {
 	}
 
 	private long sampleRunningTime(int iterations) {
-		byte[] password = { 'p', 'a', 's', 's', 'w', 'o', 'r', 'd' };
+		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();
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 accfaf5279..6b3ea5295b 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
@@ -6,7 +6,7 @@ import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator;
 import org.briarproject.bramble.api.crypto.StreamDecrypterFactory;
 import org.briarproject.bramble.api.crypto.StreamEncrypterFactory;
 import org.briarproject.bramble.api.lifecycle.LifecycleManager;
-import org.briarproject.bramble.api.system.SeedProvider;
+import org.briarproject.bramble.api.system.SecureRandomProvider;
 
 import java.security.SecureRandom;
 import java.util.concurrent.BlockingQueue;
@@ -60,8 +60,9 @@ public class CryptoModule {
 
 	@Provides
 	@Singleton
-	CryptoComponent provideCryptoComponent(SeedProvider seedProvider) {
-		return new CryptoComponentImpl(seedProvider);
+	CryptoComponent provideCryptoComponent(
+			SecureRandomProvider secureRandomProvider) {
+		return new CryptoComponentImpl(secureRandomProvider);
 	}
 
 	@Provides
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/crypto/DoubleDigest.java b/bramble-core/src/main/java/org/briarproject/bramble/crypto/DoubleDigest.java
deleted file mode 100644
index 2bd71fa78a..0000000000
--- a/bramble-core/src/main/java/org/briarproject/bramble/crypto/DoubleDigest.java
+++ /dev/null
@@ -1,76 +0,0 @@
-package org.briarproject.bramble.crypto;
-
-import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-import org.spongycastle.crypto.Digest;
-
-import javax.annotation.concurrent.NotThreadSafe;
-
-/**
- * A message digest that prevents length extension attacks - see Ferguson and
- * Schneier, <i>Practical Cryptography</i>, chapter 6.
- * <p>
- * "Let h be an interative hash function. The hash function h<sub>d</sub> is
- * defined by h<sub>d</sub> := h(h(m)), and has a claimed security level of
- * min(k, n/2) where k is the security level of h and n is the size of the hash
- * result."
- */
-@NotThreadSafe
-@NotNullByDefault
-class DoubleDigest implements Digest {
-
-	private final Digest delegate;
-
-	DoubleDigest(Digest delegate) {
-		this.delegate = delegate;
-	}
-
-	private byte[] digest() {
-		byte[] digest = new byte[delegate.getDigestSize()];
-		delegate.doFinal(digest, 0); // h(m)
-		delegate.update(digest, 0, digest.length);
-		delegate.doFinal(digest, 0); // h(h(m))
-		return digest;
-	}
-
-	public int digest(byte[] buf, int offset, int len) {
-		byte[] digest = digest();
-		len = Math.min(len, digest.length);
-		System.arraycopy(digest, 0, buf, offset, len);
-		return len;
-	}
-
-	@Override
-	public int getDigestSize() {
-		return delegate.getDigestSize();
-	}
-
-	@Override
-	public String getAlgorithmName() {
-		return "Double " + delegate.getAlgorithmName();
-	}
-
-	@Override
-	public void reset() {
-		delegate.reset();
-	}
-
-	@Override
-	public void update(byte input) {
-		delegate.update(input);
-	}
-
-	public void update(byte[] input) {
-		delegate.update(input, 0, input.length);
-	}
-
-	@Override
-	public void update(byte[] input, int offset, int len) {
-		delegate.update(input, offset, len);
-	}
-
-	@Override
-	public int doFinal(byte[] out, int outOff) {
-		return digest(out, outOff, delegate.getDigestSize());
-	}
-
-}
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/crypto/FortunaGenerator.java b/bramble-core/src/main/java/org/briarproject/bramble/crypto/FortunaGenerator.java
deleted file mode 100644
index 5c36a931fd..0000000000
--- a/bramble-core/src/main/java/org/briarproject/bramble/crypto/FortunaGenerator.java
+++ /dev/null
@@ -1,114 +0,0 @@
-package org.briarproject.bramble.crypto;
-
-import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-import org.spongycastle.crypto.BlockCipher;
-import org.spongycastle.crypto.digests.SHA256Digest;
-import org.spongycastle.crypto.engines.AESLightEngine;
-import org.spongycastle.crypto.params.KeyParameter;
-
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-
-import javax.annotation.concurrent.ThreadSafe;
-
-/**
- * Implements the Fortuna pseudo-random number generator, as described in
- * Ferguson and Schneier, <i>Practical Cryptography</i>, chapter 9.
- */
-@ThreadSafe
-@NotNullByDefault
-class FortunaGenerator {
-
-	private static final int MAX_BYTES_PER_REQUEST = 1024 * 1024;
-	private static final int KEY_BYTES = 32;
-	private static final int BLOCK_BYTES = 16;
-
-	private final Lock lock = new ReentrantLock();
-
-	// The following are locking: lock
-	private final DoubleDigest digest = new DoubleDigest(new SHA256Digest());
-	private final BlockCipher cipher = new AESLightEngine();
-	private final byte[] key = new byte[KEY_BYTES];
-	private final byte[] counter = new byte[BLOCK_BYTES];
-	private final byte[] buffer = new byte[BLOCK_BYTES];
-	private final byte[] newKey = new byte[KEY_BYTES];
-
-	FortunaGenerator(byte[] seed) {
-		reseed(seed);
-	}
-
-	void reseed(byte[] seed) {
-		lock.lock();
-		try {
-			digest.update(key);
-			digest.update(seed);
-			digest.digest(key, 0, KEY_BYTES);
-			incrementCounter();
-		} finally {
-			lock.unlock();
-		}
-
-	}
-
-	// Package access for testing
-	void incrementCounter() {
-		lock.lock();
-		try {
-			counter[0]++;
-			for (int i = 0; counter[i] == 0; i++) {
-				if (i + 1 == BLOCK_BYTES)
-					throw new RuntimeException("Counter exhausted");
-				counter[i + 1]++;
-			}
-		} finally {
-			lock.unlock();
-		}
-	}
-
-	// Package access for testing
-	byte[] getCounter() {
-		lock.lock();
-		try {
-			return counter;
-		} finally {
-			lock.unlock();
-		}
-
-	}
-
-	int nextBytes(byte[] dest, int off, int len) {
-		lock.lock();
-		try {
-			// Don't write more than the maximum number of bytes in one request
-			if (len > MAX_BYTES_PER_REQUEST) len = MAX_BYTES_PER_REQUEST;
-			cipher.init(true, new KeyParameter(key));
-			// Generate full blocks directly into the output buffer
-			int fullBlocks = len / BLOCK_BYTES;
-			for (int i = 0; i < fullBlocks; i++) {
-				cipher.processBlock(counter, 0, dest, off + i * BLOCK_BYTES);
-				incrementCounter();
-			}
-			// Generate a partial block if needed
-			int done = fullBlocks * BLOCK_BYTES, remaining = len - done;
-			if (remaining >= BLOCK_BYTES) throw new AssertionError();
-			if (remaining > 0) {
-				cipher.processBlock(counter, 0, buffer, 0);
-				incrementCounter();
-				// Copy the partial block to the output buffer and erase our copy
-				System.arraycopy(buffer, 0, dest, off + done, remaining);
-				for (int i = 0; i < BLOCK_BYTES; i++) buffer[i] = 0;
-			}
-			// Generate a new key
-			for (int i = 0; i < KEY_BYTES / BLOCK_BYTES; i++) {
-				cipher.processBlock(counter, 0, newKey, i * BLOCK_BYTES);
-				incrementCounter();
-			}
-			System.arraycopy(newKey, 0, key, 0, KEY_BYTES);
-			for (int i = 0; i < KEY_BYTES; i++) newKey[i] = 0;
-			// Return the number of bytes written
-			return len;
-		} finally {
-			lock.unlock();
-		}
-	}
-}
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/crypto/FortunaSecureRandom.java b/bramble-core/src/main/java/org/briarproject/bramble/crypto/FortunaSecureRandom.java
deleted file mode 100644
index 62e609c96b..0000000000
--- a/bramble-core/src/main/java/org/briarproject/bramble/crypto/FortunaSecureRandom.java
+++ /dev/null
@@ -1,81 +0,0 @@
-package org.briarproject.bramble.crypto;
-
-import org.briarproject.bramble.util.StringUtils;
-
-import java.security.Provider;
-import java.security.SecureRandom;
-import java.security.SecureRandomSpi;
-import java.util.Arrays;
-
-/**
- * A {@link java.security.SecureRandom SecureRandom} implementation based on a
- * {@link FortunaGenerator}.
- */
-class FortunaSecureRandom extends SecureRandom {
-
-	// Package access for testing
-	static final byte[] SELF_TEST_VECTOR_1 =
-			StringUtils.fromHexString("4BD6EA599D47E3EE9DD911833C29CA22");
-	static final byte[] SELF_TEST_VECTOR_2 =
-			StringUtils.fromHexString("10984D576E6850E505CA9F42A9BFD88A");
-	static final byte[] SELF_TEST_VECTOR_3 =
-			StringUtils.fromHexString("1E12DA166BD86DCECDE50A8296018DE2");
-
-	private static final Provider PROVIDER = new FortunaProvider();
-
-	FortunaSecureRandom(byte[] seed) {
-		super(new FortunaSecureRandomSpi(seed), PROVIDER);
-	}
-
-	/**
-	 * Tests that the {@link #nextBytes(byte[])} and {@link #setSeed(byte[])}
-	 * methods are passed through to the generator in the expected way.
-	 */
-	static boolean selfTest() {
-		byte[] seed = new byte[32];
-		SecureRandom r = new FortunaSecureRandom(seed);
-		byte[] output = new byte[16];
-		r.nextBytes(output);
-		if (!Arrays.equals(SELF_TEST_VECTOR_1, output)) return false;
-		r.nextBytes(output);
-		if (!Arrays.equals(SELF_TEST_VECTOR_2, output)) return false;
-		r.setSeed(seed);
-		r.nextBytes(output);
-		return Arrays.equals(SELF_TEST_VECTOR_3, output);
-	}
-
-	private static class FortunaSecureRandomSpi extends SecureRandomSpi {
-
-		private final FortunaGenerator generator;
-
-		private FortunaSecureRandomSpi(byte[] seed) {
-			generator = new FortunaGenerator(seed);
-		}
-
-		@Override
-		protected byte[] engineGenerateSeed(int numBytes) {
-			byte[] b = new byte[numBytes];
-			engineNextBytes(b);
-			return b;
-		}
-
-		@Override
-		protected void engineNextBytes(byte[] b) {
-			int offset = 0;
-			while (offset < b.length)
-				offset += generator.nextBytes(b, offset, b.length - offset);
-		}
-
-		@Override
-		protected void engineSetSeed(byte[] seed) {
-			generator.reseed(seed);
-		}
-	}
-
-	private static class FortunaProvider extends Provider {
-
-		private FortunaProvider() {
-			super("Fortuna", 1.0, "");
-		}
-	}
-}
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/crypto/PseudoRandomImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/crypto/PseudoRandomImpl.java
index fb02df0471..1eabb29df3 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/crypto/PseudoRandomImpl.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/crypto/PseudoRandomImpl.java
@@ -2,30 +2,34 @@ package org.briarproject.bramble.crypto;
 
 import org.briarproject.bramble.api.crypto.PseudoRandom;
 import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-import org.briarproject.bramble.util.ByteUtils;
+import org.spongycastle.crypto.Digest;
+import org.spongycastle.crypto.engines.Salsa20Engine;
+import org.spongycastle.crypto.params.KeyParameter;
+import org.spongycastle.crypto.params.ParametersWithIV;
 
 import javax.annotation.concurrent.NotThreadSafe;
 
-import static org.briarproject.bramble.util.ByteUtils.INT_32_BYTES;
-
 @NotThreadSafe
 @NotNullByDefault
 class PseudoRandomImpl implements PseudoRandom {
 
-	private final FortunaGenerator generator;
+	private final Salsa20Engine cipher = new Salsa20Engine();
 
-	PseudoRandomImpl(int seed1, int seed2) {
-		byte[] seed = new byte[INT_32_BYTES * 2];
-		ByteUtils.writeUint32(seed1, seed, 0);
-		ByteUtils.writeUint32(seed2, seed, INT_32_BYTES);
-		generator = new FortunaGenerator(seed);
+	PseudoRandomImpl(byte[] seed) {
+		// Hash the seed to produce a 32-byte key
+		byte[] key = new byte[32];
+		Digest digest = new Blake2sDigest();
+		digest.update(seed, 0, seed.length);
+		digest.doFinal(key, 0);
+		// Initialise the stream cipher with an all-zero nonce
+		byte[] nonce = new byte[8];
+		cipher.init(true, new ParametersWithIV(new KeyParameter(key), nonce));
 	}
 
 	@Override
 	public byte[] nextBytes(int length) {
-		byte[] b = new byte[length];
-		int offset = 0;
-		while (offset < length) offset += generator.nextBytes(b, offset, length);
-		return b;
+		byte[] in = new byte[length], out = new byte[length];
+		cipher.processBytes(in, 0, length, out, 0);
+		return out;
 	}
 }
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/system/AbstractSecureRandomProvider.java b/bramble-core/src/main/java/org/briarproject/bramble/system/AbstractSecureRandomProvider.java
new file mode 100644
index 0000000000..c442895a23
--- /dev/null
+++ b/bramble-core/src/main/java/org/briarproject/bramble/system/AbstractSecureRandomProvider.java
@@ -0,0 +1,42 @@
+package org.briarproject.bramble.system;
+
+import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
+import org.briarproject.bramble.api.system.SecureRandomProvider;
+
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.Properties;
+
+import javax.annotation.concurrent.Immutable;
+
+@Immutable
+@NotNullByDefault
+abstract class AbstractSecureRandomProvider implements SecureRandomProvider {
+
+	// Contribute whatever slightly unpredictable info we have to the pool
+	protected void writeToEntropyPool(DataOutputStream out) throws IOException {
+		out.writeLong(System.currentTimeMillis());
+		out.writeLong(System.nanoTime());
+		out.writeLong(Runtime.getRuntime().freeMemory());
+		List<NetworkInterface> ifaces =
+				Collections.list(NetworkInterface.getNetworkInterfaces());
+		for (NetworkInterface i : ifaces) {
+			List<InetAddress> addrs = Collections.list(i.getInetAddresses());
+			for (InetAddress a : addrs) out.write(a.getAddress());
+			byte[] hardware = i.getHardwareAddress();
+			if (hardware != null) out.write(hardware);
+		}
+		for (Entry<String, String> e : System.getenv().entrySet()) {
+			out.writeUTF(e.getKey());
+			out.writeUTF(e.getValue());
+		}
+		Properties properties = System.getProperties();
+		for (String key : properties.stringPropertyNames())
+			out.writeUTF(properties.getProperty(key));
+	}
+}
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/system/LinuxSecureRandomProvider.java b/bramble-core/src/main/java/org/briarproject/bramble/system/LinuxSecureRandomProvider.java
new file mode 100644
index 0000000000..c63c02b053
--- /dev/null
+++ b/bramble-core/src/main/java/org/briarproject/bramble/system/LinuxSecureRandomProvider.java
@@ -0,0 +1,69 @@
+package org.briarproject.bramble.system;
+
+import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
+
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.security.Provider;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.logging.Logger;
+
+import javax.annotation.concurrent.Immutable;
+
+import static java.util.logging.Level.WARNING;
+
+@Immutable
+@NotNullByDefault
+class LinuxSecureRandomProvider extends AbstractSecureRandomProvider {
+
+	private static final Logger LOG =
+			Logger.getLogger(LinuxSecureRandomProvider.class.getName());
+
+	private static final File RANDOM_DEVICE = new File("/dev/urandom");
+
+	private final AtomicBoolean seeded = new AtomicBoolean(false);
+	private final File outputDevice;
+
+	LinuxSecureRandomProvider() {
+		this(RANDOM_DEVICE);
+	}
+
+	LinuxSecureRandomProvider(File outputDevice) {
+		this.outputDevice = outputDevice;
+	}
+
+	@Override
+	public Provider getProvider() {
+		if (!seeded.getAndSet(true)) writeSeed();
+		return new LinuxProvider();
+	}
+
+	protected void writeSeed() {
+		try {
+			DataOutputStream out = new DataOutputStream(
+					new FileOutputStream(outputDevice));
+			writeToEntropyPool(out);
+			out.flush();
+			out.close();
+		} catch (IOException e) {
+			// On some devices /dev/urandom isn't writable - this isn't fatal
+			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
+		}
+	}
+
+	// Based on https://android-developers.googleblog.com/2013/08/some-securerandom-thoughts.html
+	private static class LinuxProvider extends Provider {
+
+		private LinuxProvider() {
+			super("LinuxPRNG", 1.1, "A Linux-specific PRNG using /dev/urandom");
+			// Although /dev/urandom is not a SHA-1 PRNG, some callers
+			// explicitly request a SHA1PRNG SecureRandom and we need to
+			// prevent them from getting the default implementation whose
+			// output may have low entropy.
+			put("SecureRandom.SHA1PRNG", LinuxSecureRandomSpi.class.getName());
+			put("SecureRandom.SHA1PRNG ImplementedIn", "Software");
+		}
+	}
+}
\ No newline at end of file
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/system/LinuxSecureRandomSpi.java b/bramble-core/src/main/java/org/briarproject/bramble/system/LinuxSecureRandomSpi.java
new file mode 100644
index 0000000000..6b4790ada1
--- /dev/null
+++ b/bramble-core/src/main/java/org/briarproject/bramble/system/LinuxSecureRandomSpi.java
@@ -0,0 +1,64 @@
+package org.briarproject.bramble.system;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.security.SecureRandomSpi;
+import java.util.logging.Logger;
+
+import static java.util.logging.Level.WARNING;
+
+public class LinuxSecureRandomSpi extends SecureRandomSpi {
+
+	private static final Logger LOG =
+			Logger.getLogger(LinuxSecureRandomSpi.class.getName());
+
+	private static final File RANDOM_DEVICE = new File("/dev/urandom");
+
+	private final File inputDevice, outputDevice;
+
+	public LinuxSecureRandomSpi() {
+		this(RANDOM_DEVICE, RANDOM_DEVICE);
+	}
+
+	LinuxSecureRandomSpi(File inputDevice, File outputDevice) {
+		this.inputDevice = inputDevice;
+		this.outputDevice = outputDevice;
+	}
+
+	@Override
+	protected void engineSetSeed(byte[] seed) {
+		try {
+			DataOutputStream out = new DataOutputStream(
+					new FileOutputStream(outputDevice));
+			out.write(seed);
+			out.flush();
+			out.close();
+		} catch (IOException e) {
+			// On some devices /dev/urandom isn't writable - this isn't fatal
+			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
+		}
+	}
+
+	@Override
+	protected void engineNextBytes(byte[] bytes) {
+		try {
+			DataInputStream in = new DataInputStream(
+					new FileInputStream(inputDevice));
+			in.readFully(bytes);
+			in.close();
+		} catch (IOException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	@Override
+	protected byte[] engineGenerateSeed(int len) {
+		byte[] seed = new byte[len];
+		engineNextBytes(seed);
+		return seed;
+	}
+}
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/system/LinuxSeedProvider.java b/bramble-core/src/main/java/org/briarproject/bramble/system/LinuxSeedProvider.java
deleted file mode 100644
index e4141a4179..0000000000
--- a/bramble-core/src/main/java/org/briarproject/bramble/system/LinuxSeedProvider.java
+++ /dev/null
@@ -1,75 +0,0 @@
-package org.briarproject.bramble.system;
-
-import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-import org.briarproject.bramble.api.system.SeedProvider;
-
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.net.InetAddress;
-import java.net.NetworkInterface;
-import java.util.Collections;
-import java.util.List;
-import java.util.logging.Logger;
-
-import javax.annotation.concurrent.Immutable;
-
-import static java.util.logging.Level.WARNING;
-
-@Immutable
-@NotNullByDefault
-class LinuxSeedProvider implements SeedProvider {
-
-	private static final Logger LOG =
-			Logger.getLogger(LinuxSeedProvider.class.getName());
-
-	private final String outputFile, inputFile;
-
-	LinuxSeedProvider() {
-		this("/dev/urandom", "/dev/urandom");
-	}
-
-	LinuxSeedProvider(String outputFile, String inputFile) {
-		this.outputFile = outputFile;
-		this.inputFile = inputFile;
-	}
-
-	@Override
-	public byte[] getSeed() {
-		byte[] seed = new byte[SEED_BYTES];
-		// Contribute whatever slightly unpredictable info we have to the pool
-		try {
-			DataOutputStream out = new DataOutputStream(
-					new FileOutputStream(outputFile));
-			writeToEntropyPool(out);
-			out.flush();
-			out.close();
-		} catch (IOException e) {
-			// On some devices /dev/urandom isn't writable - this isn't fatal
-			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
-		}
-		// Read the seed from the pool
-		try {
-			DataInputStream in = new DataInputStream(
-					new FileInputStream(inputFile));
-			in.readFully(seed);
-			in.close();
-		} catch (IOException e) {
-			throw new RuntimeException(e);
-		}
-		return seed;
-	}
-
-	void writeToEntropyPool(DataOutputStream out) throws IOException {
-		out.writeLong(System.currentTimeMillis());
-		out.writeLong(System.nanoTime());
-		List<NetworkInterface> ifaces =
-				Collections.list(NetworkInterface.getNetworkInterfaces());
-		for (NetworkInterface i : ifaces) {
-			List<InetAddress> addrs = Collections.list(i.getInetAddresses());
-			for (InetAddress a : addrs) out.write(a.getAddress());
-		}
-	}
-}
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/crypto/EllipticCurveMultiplicationTest.java b/bramble-core/src/test/java/org/briarproject/bramble/crypto/EllipticCurveMultiplicationTest.java
index ce3d03e443..9be3dc0ddc 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/crypto/EllipticCurveMultiplicationTest.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/crypto/EllipticCurveMultiplicationTest.java
@@ -45,7 +45,7 @@ public class EllipticCurveMultiplicationTest extends BrambleTestCase {
 		byte[] seed = new byte[32];
 		new SecureRandom().nextBytes(seed);
 		// Montgomery ladder multiplier
-		SecureRandom random = new FortunaSecureRandom(seed);
+		SecureRandom random = new PseudoSecureRandom(seed);
 		ECKeyGenerationParameters montgomeryGeneratorParams =
 				new ECKeyGenerationParameters(PARAMETERS, random);
 		ECKeyPairGenerator montgomeryGenerator = new ECKeyPairGenerator();
@@ -63,7 +63,7 @@ public class EllipticCurveMultiplicationTest extends BrambleTestCase {
 		ECPublicKeyParameters montgomeryPublic2 =
 				(ECPublicKeyParameters) montgomeryKeyPair2.getPublic();
 		// Default multiplier
-		random = new FortunaSecureRandom(seed);
+		random = new PseudoSecureRandom(seed);
 		ECKeyGenerationParameters defaultGeneratorParams =
 				new ECKeyGenerationParameters(defaultParameters, random);
 		ECKeyPairGenerator defaultGenerator = new ECKeyPairGenerator();
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/crypto/FortunaGeneratorTest.java b/bramble-core/src/test/java/org/briarproject/bramble/crypto/FortunaGeneratorTest.java
deleted file mode 100644
index c1573f610f..0000000000
--- a/bramble-core/src/test/java/org/briarproject/bramble/crypto/FortunaGeneratorTest.java
+++ /dev/null
@@ -1,99 +0,0 @@
-package org.briarproject.bramble.crypto;
-
-import org.briarproject.bramble.test.BrambleTestCase;
-import org.junit.Test;
-import org.spongycastle.crypto.BlockCipher;
-import org.spongycastle.crypto.engines.AESLightEngine;
-import org.spongycastle.crypto.params.KeyParameter;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-
-public class FortunaGeneratorTest extends BrambleTestCase {
-
-	@Test
-	public void testCounterInitialisedToOne() {
-		FortunaGenerator f = new FortunaGenerator(new byte[32]);
-		// The counter is little-endian
-		byte[] expected = new byte[16];
-		expected[0] = 1;
-		assertArrayEquals(expected, f.getCounter());
-	}
-
-	@Test
-	public void testIncrementCounter() {
-		FortunaGenerator f = new FortunaGenerator(new byte[32]);
-		// Increment the counter until it reaches 255
-		for (int i = 1; i < 255; i++) f.incrementCounter();
-		byte[] expected = new byte[16];
-		expected[0] = (byte) 255;
-		assertArrayEquals(expected, f.getCounter());
-		// Increment the counter again - it should carry into the next byte
-		f.incrementCounter();
-		expected[0] = 0;
-		expected[1] = 1;
-		assertArrayEquals(expected, f.getCounter());
-		// Increment the counter until it carries into the next byte
-		for (int i = 256; i < 65536; i++) f.incrementCounter();
-		expected[0] = 0;
-		expected[1] = 0;
-		expected[2] = 1;
-		assertArrayEquals(expected, f.getCounter());
-	}
-
-	@Test
-	public void testNextBytes() {
-		// Generate several outputs with the same seed - they should all match
-		byte[] seed = new byte[32];
-		byte[] out1 = new byte[48];
-		new FortunaGenerator(seed).nextBytes(out1, 0, 48);
-		// One byte longer than a block, with an offset of one
-		byte[] out2 = new byte[49];
-		new FortunaGenerator(seed).nextBytes(out2, 1, 48);
-		for (int i = 0; i < 48; i++) assertEquals(out1[i], out2[i + 1]);
-		// One byte shorter than a block
-		byte[] out3 = new byte[47];
-		new FortunaGenerator(seed).nextBytes(out3, 0, 47);
-		for (int i = 0; i < 47; i++) assertEquals(out1[i], out3[i]);
-		// Less than a block, with an offset greater than a block
-		byte[] out4 = new byte[32];
-		new FortunaGenerator(seed).nextBytes(out4, 17, 15);
-		for (int i = 0; i < 15; i++) assertEquals(out1[i], out4[i + 17]);
-	}
-
-	@Test
-	public void testRekeying() {
-		byte[] seed = new byte[32];
-		FortunaGenerator f = new FortunaGenerator(seed);
-		// Generate three blocks of output
-		byte[] out1 = new byte[48];
-		f.nextBytes(out1, 0, 48);
-		// Create another generator with the same seed and generate one block
-		f = new FortunaGenerator(seed);
-		byte[] out2 = new byte[16];
-		f.nextBytes(out2, 0, 16);
-		// The generator should have rekeyed with the 2nd and 3rd blocks
-		byte[] expectedKey = new byte[32];
-		System.arraycopy(out1, 16, expectedKey, 0, 32);
-		// The generator's counter should have been incremented 3 times
-		byte[] expectedCounter = new byte[16];
-		expectedCounter[0] = 4;
-		// The next expected output block is the counter encrypted with the key
-		byte[] expectedOutput = new byte[16];
-		BlockCipher c = new AESLightEngine();
-		c.init(true, new KeyParameter(expectedKey));
-		c.processBlock(expectedCounter, 0, expectedOutput, 0);
-		// Check that the generator produces the expected output block
-		byte[] out3 = new byte[16];
-		f.nextBytes(out3, 0, 16);
-		assertArrayEquals(expectedOutput, out3);
-	}
-
-	@Test
-	public void testMaximumRequestLength() {
-		int expectedMax = 1024 * 1024;
-		byte[] output = new byte[expectedMax + 123];
-		FortunaGenerator f = new FortunaGenerator(new byte[32]);
-		assertEquals(expectedMax, f.nextBytes(output, 0, output.length));
-	}
-}
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/crypto/FortunaSecureRandomTest.java b/bramble-core/src/test/java/org/briarproject/bramble/crypto/FortunaSecureRandomTest.java
deleted file mode 100644
index d685ce11fb..0000000000
--- a/bramble-core/src/test/java/org/briarproject/bramble/crypto/FortunaSecureRandomTest.java
+++ /dev/null
@@ -1,67 +0,0 @@
-package org.briarproject.bramble.crypto;
-
-import org.briarproject.bramble.test.BrambleTestCase;
-import org.junit.Test;
-import org.spongycastle.crypto.BlockCipher;
-import org.spongycastle.crypto.digests.SHA256Digest;
-import org.spongycastle.crypto.engines.AESLightEngine;
-import org.spongycastle.crypto.params.KeyParameter;
-
-import static org.briarproject.bramble.crypto.FortunaSecureRandom.SELF_TEST_VECTOR_1;
-import static org.briarproject.bramble.crypto.FortunaSecureRandom.SELF_TEST_VECTOR_2;
-import static org.briarproject.bramble.crypto.FortunaSecureRandom.SELF_TEST_VECTOR_3;
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertTrue;
-
-public class FortunaSecureRandomTest extends BrambleTestCase {
-
-	@Test
-	public void testClassPassesSelfTest() {
-		assertTrue(FortunaSecureRandom.selfTest());
-	}
-
-	@Test
-	public void testSelfTestVectorsAreReproducible() {
-		byte[] key = new byte[32], seed = new byte[32];
-		byte[] counter = new byte[16], output = new byte[16];
-		byte[] newKey = new byte[32];
-		// Calculate the initial key
-		DoubleDigest digest = new DoubleDigest(new SHA256Digest());
-		digest.update(key);
-		digest.update(seed);
-		digest.digest(key, 0, 32);
-		// Calculate the first output block and the new key
-		BlockCipher c = new AESLightEngine();
-		c.init(true, new KeyParameter(key));
-		counter[0] = 1;
-		c.processBlock(counter, 0, output, 0);
-		counter[0] = 2;
-		c.processBlock(counter, 0, newKey, 0);
-		counter[0] = 3;
-		c.processBlock(counter, 0, newKey, 16);
-		System.arraycopy(newKey, 0, key, 0, 32);
-		// The first self-test vector should match the first output block
-		assertArrayEquals(SELF_TEST_VECTOR_1, output);
-		// Calculate the second output block and the new key before reseeding
-		c.init(true, new KeyParameter(key));
-		counter[0] = 4;
-		c.processBlock(counter, 0, output, 0);
-		counter[0] = 5;
-		c.processBlock(counter, 0, newKey, 0);
-		counter[0] = 6;
-		c.processBlock(counter, 0, newKey, 16);
-		System.arraycopy(newKey, 0, key, 0, 32);
-		// The second self-test vector should match the second output block
-		assertArrayEquals(SELF_TEST_VECTOR_2, output);
-		// Calculate the new key after reseeding
-		digest.update(key);
-		digest.update(seed);
-		digest.digest(key, 0, 32);
-		// Calculate the third output block
-		c.init(true, new KeyParameter(key));
-		counter[0] = 8;
-		c.processBlock(counter, 0, output, 0);
-		// The third self-test vector should match the third output block
-		assertArrayEquals(SELF_TEST_VECTOR_3, output);
-	}
-}
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 482afc0451..bce8b720ea 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
@@ -2,7 +2,7 @@ package org.briarproject.bramble.crypto;
 
 import org.briarproject.bramble.api.crypto.CryptoComponent;
 import org.briarproject.bramble.test.BrambleTestCase;
-import org.briarproject.bramble.test.TestSeedProvider;
+import org.briarproject.bramble.test.TestSecureRandomProvider;
 import org.briarproject.bramble.test.TestUtils;
 import org.junit.Test;
 
@@ -21,7 +21,7 @@ public class HashTest extends BrambleTestCase {
 	private final byte[] inputBytes2 = new byte[0];
 
 	public HashTest() {
-		crypto = new CryptoComponentImpl(new TestSeedProvider());
+		crypto = new CryptoComponentImpl(new TestSecureRandomProvider());
 	}
 
 	@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 0a0e826dc9..ad3b0dad6b 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
@@ -3,9 +3,9 @@ package org.briarproject.bramble.crypto;
 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.system.SeedProvider;
+import org.briarproject.bramble.api.system.SecureRandomProvider;
 import org.briarproject.bramble.test.BrambleTestCase;
-import org.briarproject.bramble.test.TestSeedProvider;
+import org.briarproject.bramble.test.TestSecureRandomProvider;
 import org.junit.Test;
 
 import static org.junit.Assert.assertArrayEquals;
@@ -14,8 +14,9 @@ public class KeyAgreementTest extends BrambleTestCase {
 
 	@Test
 	public void testDeriveMasterSecret() throws Exception {
-		SeedProvider seedProvider = new TestSeedProvider();
-		CryptoComponent crypto = new CryptoComponentImpl(seedProvider);
+		SecureRandomProvider
+				secureRandomProvider = new TestSecureRandomProvider();
+		CryptoComponent crypto = new CryptoComponentImpl(secureRandomProvider);
 		KeyPair aPair = crypto.generateAgreementKeyPair();
 		byte[] aPub = aPair.getPublic().getEncoded();
 		KeyPair bPair = crypto.generateAgreementKeyPair();
@@ -27,8 +28,9 @@ public class KeyAgreementTest extends BrambleTestCase {
 
 	@Test
 	public void testDeriveSharedSecret() throws Exception {
-		SeedProvider seedProvider = new TestSeedProvider();
-		CryptoComponent crypto = new CryptoComponentImpl(seedProvider);
+		SecureRandomProvider
+				secureRandomProvider = new TestSecureRandomProvider();
+		CryptoComponent crypto = new CryptoComponentImpl(secureRandomProvider);
 		KeyPair aPair = crypto.generateAgreementKeyPair();
 		byte[] aPub = aPair.getPublic().getEncoded();
 		KeyPair bPair = crypto.generateAgreementKeyPair();
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 616833c470..0e0e7a69f7 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
@@ -1,20 +1,24 @@
 package org.briarproject.bramble.crypto;
 
+import org.briarproject.bramble.api.Bytes;
 import org.briarproject.bramble.api.crypto.CryptoComponent;
 import org.briarproject.bramble.api.crypto.SecretKey;
 import org.briarproject.bramble.api.plugin.TransportId;
 import org.briarproject.bramble.api.transport.TransportKeys;
 import org.briarproject.bramble.test.BrambleTestCase;
-import org.briarproject.bramble.test.TestSeedProvider;
+import org.briarproject.bramble.test.TestSecureRandomProvider;
 import org.briarproject.bramble.test.TestUtils;
 import org.junit.Test;
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 
 public class KeyDerivationTest extends BrambleTestCase {
 
@@ -23,7 +27,7 @@ public class KeyDerivationTest extends BrambleTestCase {
 	private final SecretKey master;
 
 	public KeyDerivationTest() {
-		crypto = new CryptoComponentImpl(new TestSeedProvider());
+		crypto = new CryptoComponentImpl(new TestSecureRandomProvider());
 		master = TestUtils.getSecretKey();
 	}
 
@@ -156,11 +160,7 @@ public class KeyDerivationTest extends BrambleTestCase {
 	}
 
 	private void assertAllDifferent(List<SecretKey> keys) {
-		for (SecretKey ki : keys) {
-			for (SecretKey kj : keys) {
-				if (ki == kj) assertArrayEquals(ki.getBytes(), kj.getBytes());
-				else assertFalse(Arrays.equals(ki.getBytes(), kj.getBytes()));
-			}
-		}
+		Set<Bytes> set = new HashSet<Bytes>();
+		for (SecretKey k : keys) assertTrue(set.add(new Bytes(k.getBytes())));
 	}
 }
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 3a6f011f87..4eb7516407 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
@@ -5,7 +5,7 @@ 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.test.BrambleTestCase;
-import org.briarproject.bramble.test.TestSeedProvider;
+import org.briarproject.bramble.test.TestSecureRandomProvider;
 import org.briarproject.bramble.test.TestUtils;
 import org.junit.Test;
 
@@ -19,7 +19,7 @@ import static org.junit.Assert.assertTrue;
 public class KeyEncodingAndParsingTest extends BrambleTestCase {
 
 	private final CryptoComponentImpl crypto =
-			new CryptoComponentImpl(new TestSeedProvider());
+			new CryptoComponentImpl(new TestSecureRandomProvider());
 
 	@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 3699e601e8..f6760bf3e6 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
@@ -3,7 +3,7 @@ package org.briarproject.bramble.crypto;
 import org.briarproject.bramble.api.crypto.CryptoComponent;
 import org.briarproject.bramble.api.crypto.SecretKey;
 import org.briarproject.bramble.test.BrambleTestCase;
-import org.briarproject.bramble.test.TestSeedProvider;
+import org.briarproject.bramble.test.TestSecureRandomProvider;
 import org.briarproject.bramble.test.TestUtils;
 import org.junit.Test;
 
@@ -22,7 +22,7 @@ public class MacTest extends BrambleTestCase {
 	private final byte[] inputBytes2 = new byte[0];
 
 	public MacTest() {
-		crypto = new CryptoComponentImpl(new TestSeedProvider());
+		crypto = new CryptoComponentImpl(new TestSecureRandomProvider());
 	}
 
 	@Test
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/crypto/PasswordBasedKdfTest.java b/bramble-core/src/test/java/org/briarproject/bramble/crypto/PasswordBasedKdfTest.java
index f92b0fef69..efdd90dc65 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/crypto/PasswordBasedKdfTest.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/crypto/PasswordBasedKdfTest.java
@@ -1,7 +1,7 @@
 package org.briarproject.bramble.crypto;
 
 import org.briarproject.bramble.test.BrambleTestCase;
-import org.briarproject.bramble.test.TestSeedProvider;
+import org.briarproject.bramble.test.TestSecureRandomProvider;
 import org.briarproject.bramble.test.TestUtils;
 import org.junit.Test;
 
@@ -15,7 +15,7 @@ import static org.junit.Assert.assertTrue;
 public class PasswordBasedKdfTest extends BrambleTestCase {
 
 	private final CryptoComponentImpl crypto =
-			new CryptoComponentImpl(new TestSeedProvider());
+			new CryptoComponentImpl(new TestSecureRandomProvider());
 
 	@Test
 	public void testEncryptionAndDecryption() {
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/crypto/PseudoSecureRandom.java b/bramble-core/src/test/java/org/briarproject/bramble/crypto/PseudoSecureRandom.java
new file mode 100644
index 0000000000..92702ca90a
--- /dev/null
+++ b/bramble-core/src/test/java/org/briarproject/bramble/crypto/PseudoSecureRandom.java
@@ -0,0 +1,48 @@
+package org.briarproject.bramble.crypto;
+
+import org.briarproject.bramble.api.crypto.PseudoRandom;
+
+import java.security.Provider;
+import java.security.SecureRandom;
+import java.security.SecureRandomSpi;
+
+class PseudoSecureRandom extends SecureRandom {
+
+	private static final Provider PROVIDER = new PseudoSecureRandomProvider();
+
+	PseudoSecureRandom(byte[] seed) {
+		super(new PseudoSecureRandomSpi(seed), PROVIDER);
+	}
+
+	private static class PseudoSecureRandomSpi extends SecureRandomSpi {
+
+		private final PseudoRandom pseudoRandom;
+
+		private PseudoSecureRandomSpi(byte[] seed) {
+			pseudoRandom = new PseudoRandomImpl(seed);
+		}
+
+		@Override
+		protected byte[] engineGenerateSeed(int length) {
+			return pseudoRandom.nextBytes(length);
+		}
+
+		@Override
+		protected void engineNextBytes(byte[] b) {
+			byte[] random = pseudoRandom.nextBytes(b.length);
+			System.arraycopy(random, 0, b, 0, b.length);
+		}
+
+		@Override
+		protected void engineSetSeed(byte[] seed) {
+			// Thank you for your input
+		}
+	}
+
+	private static class PseudoSecureRandomProvider extends Provider {
+
+		private PseudoSecureRandomProvider() {
+			super("PseudoSecureRandom", 1.0, "Only for testing");
+		}
+	}
+}
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 017ba3a3ba..c11444c634 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
@@ -3,7 +3,7 @@ package org.briarproject.bramble.crypto;
 import org.briarproject.bramble.api.crypto.CryptoComponent;
 import org.briarproject.bramble.api.crypto.KeyPair;
 import org.briarproject.bramble.test.BrambleTestCase;
-import org.briarproject.bramble.test.TestSeedProvider;
+import org.briarproject.bramble.test.TestSecureRandomProvider;
 import org.briarproject.bramble.test.TestUtils;
 import org.junit.Test;
 
@@ -22,7 +22,7 @@ public class SignatureTest extends BrambleTestCase {
 	private final byte[] inputBytes = TestUtils.getRandomBytes(123);
 
 	public SignatureTest() {
-		crypto = new CryptoComponentImpl(new TestSeedProvider());
+		crypto = new CryptoComponentImpl(new TestSecureRandomProvider());
 		KeyPair k = crypto.generateSignatureKeyPair();
 		publicKey = k.getPublic().getEncoded();
 		privateKey = k.getPrivate().getEncoded();
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/system/LinuxSecureRandomProviderTest.java b/bramble-core/src/test/java/org/briarproject/bramble/system/LinuxSecureRandomProviderTest.java
new file mode 100644
index 0000000000..8c4190ac98
--- /dev/null
+++ b/bramble-core/src/test/java/org/briarproject/bramble/system/LinuxSecureRandomProviderTest.java
@@ -0,0 +1,57 @@
+package org.briarproject.bramble.system;
+
+import org.briarproject.bramble.test.BrambleTestCase;
+import org.briarproject.bramble.test.TestUtils;
+import org.briarproject.bramble.util.OsUtils;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+import java.security.Provider;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+public class LinuxSecureRandomProviderTest extends BrambleTestCase {
+
+	private final File testDir = TestUtils.getTestDirectory();
+
+	@Before
+	public void setUp() {
+		testDir.mkdirs();
+	}
+
+	@Test
+	public void testGetProviderWritesToRandomDeviceOnFirstCall()
+			throws Exception {
+		if (!(OsUtils.isLinux())) {
+			System.err.println("WARNING: Skipping test, can't run on this OS");
+			return;
+		}
+		// Redirect the provider's output to a file
+		File urandom = new File(testDir, "urandom");
+		urandom.delete();
+		assertTrue(urandom.createNewFile());
+		assertEquals(0, urandom.length());
+		LinuxSecureRandomProvider p = new LinuxSecureRandomProvider(urandom);
+		// Getting a provider should write entropy to the file
+		Provider provider = p.getProvider();
+		assertNotNull(provider);
+		assertEquals("LinuxPRNG", provider.getName());
+		// There should be at least 16 bytes from the clock, 8 from the runtime
+		long length = urandom.length();
+		assertTrue(length >= 24);
+		// Getting another provider should not write to the file again
+		provider = p.getProvider();
+		assertNotNull(provider);
+		assertEquals("LinuxPRNG", provider.getName());
+		assertEquals(length, urandom.length());
+	}
+
+	@After
+	public void tearDown() {
+		TestUtils.deleteTestDirectory(testDir);
+	}
+}
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/system/LinuxSecureRandomSpiTest.java b/bramble-core/src/test/java/org/briarproject/bramble/system/LinuxSecureRandomSpiTest.java
new file mode 100644
index 0000000000..4a3ac7313d
--- /dev/null
+++ b/bramble-core/src/test/java/org/briarproject/bramble/system/LinuxSecureRandomSpiTest.java
@@ -0,0 +1,128 @@
+package org.briarproject.bramble.system;
+
+import org.briarproject.bramble.api.Bytes;
+import org.briarproject.bramble.test.BrambleTestCase;
+import org.briarproject.bramble.test.TestUtils;
+import org.briarproject.bramble.util.IoUtils;
+import org.briarproject.bramble.util.OsUtils;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.util.HashSet;
+import java.util.Set;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+
+public class LinuxSecureRandomSpiTest extends BrambleTestCase {
+
+	private static final File RANDOM_DEVICE = new File("/dev/urandom");
+	private static final int SEED_BYTES = 32;
+
+	private final File testDir = TestUtils.getTestDirectory();
+
+	@Before
+	public void setUp() {
+		testDir.mkdirs();
+	}
+
+	@Test
+	public void testSeedsAreDistinct() {
+		if (!(OsUtils.isLinux())) {
+			System.err.println("WARNING: Skipping test, can't run on this OS");
+			return;
+		}
+		Set<Bytes> seeds = new HashSet<Bytes>();
+		LinuxSecureRandomSpi engine = new LinuxSecureRandomSpi();
+		for (int i = 0; i < 1000; i++) {
+			byte[] seed = engine.engineGenerateSeed(SEED_BYTES);
+			assertEquals(SEED_BYTES, seed.length);
+			assertTrue(seeds.add(new Bytes(seed)));
+		}
+	}
+
+	@Test
+	public void testEngineSetSeedWritesToRandomDevice() throws Exception {
+		if (!(OsUtils.isLinux())) {
+			System.err.println("WARNING: Skipping test, can't run on this OS");
+			return;
+		}
+		// Redirect the engine's output to a file
+		File urandom = new File(testDir, "urandom");
+		urandom.delete();
+		assertTrue(urandom.createNewFile());
+		assertEquals(0, urandom.length());
+		// Generate a seed
+		byte[] seed = TestUtils.getRandomBytes(SEED_BYTES);
+		// Check that the engine writes the seed to the file
+		LinuxSecureRandomSpi engine = new LinuxSecureRandomSpi(RANDOM_DEVICE,
+				urandom);
+		engine.engineSetSeed(seed);
+		assertEquals(SEED_BYTES, urandom.length());
+		byte[] written = new byte[SEED_BYTES];
+		FileInputStream in = new FileInputStream(urandom);
+		IoUtils.read(in, written);
+		in.close();
+		assertArrayEquals(seed, written);
+	}
+
+	@Test
+	public void testEngineNextBytesReadsFromRandomDevice() throws Exception {
+		if (!(OsUtils.isLinux())) {
+			System.err.println("WARNING: Skipping test, can't run on this OS");
+			return;
+		}
+		// Generate some entropy
+		byte[] entropy = TestUtils.getRandomBytes(SEED_BYTES);
+		// Write the entropy to a file
+		File urandom = new File(testDir, "urandom");
+		urandom.delete();
+		FileOutputStream out = new FileOutputStream(urandom);
+		out.write(entropy);
+		out.flush();
+		out.close();
+		assertTrue(urandom.exists());
+		assertEquals(SEED_BYTES, urandom.length());
+		// Check that the engine reads from the file
+		LinuxSecureRandomSpi engine = new LinuxSecureRandomSpi(urandom,
+				RANDOM_DEVICE);
+		byte[] b = new byte[SEED_BYTES];
+		engine.engineNextBytes(b);
+		assertArrayEquals(entropy, b);
+	}
+
+	@Test
+	public void testEngineGenerateSeedReadsFromRandomDevice() throws Exception {
+		if (!(OsUtils.isLinux())) {
+			System.err.println("WARNING: Skipping test, can't run on this OS");
+			return;
+		}
+		// Generate some entropy
+		byte[] entropy = TestUtils.getRandomBytes(SEED_BYTES);
+		// Write the entropy to a file
+		File urandom = new File(testDir, "urandom");
+		urandom.delete();
+		FileOutputStream out = new FileOutputStream(urandom);
+		out.write(entropy);
+		out.flush();
+		out.close();
+		assertTrue(urandom.exists());
+		assertEquals(SEED_BYTES, urandom.length());
+		// Check that the engine reads from the file
+		LinuxSecureRandomSpi engine = new LinuxSecureRandomSpi(urandom,
+				RANDOM_DEVICE);
+		byte[] b = engine.engineGenerateSeed(SEED_BYTES);
+		assertArrayEquals(entropy, b);
+	}
+
+	@After
+	public void tearDown() {
+		TestUtils.deleteTestDirectory(testDir);
+	}
+}
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/system/LinuxSeedProviderTest.java b/bramble-core/src/test/java/org/briarproject/bramble/system/LinuxSeedProviderTest.java
deleted file mode 100644
index 0e0fe119c9..0000000000
--- a/bramble-core/src/test/java/org/briarproject/bramble/system/LinuxSeedProviderTest.java
+++ /dev/null
@@ -1,90 +0,0 @@
-package org.briarproject.bramble.system;
-
-import org.briarproject.bramble.api.Bytes;
-import org.briarproject.bramble.test.BrambleTestCase;
-import org.briarproject.bramble.test.TestUtils;
-import org.briarproject.bramble.util.OsUtils;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.util.HashSet;
-import java.util.Set;
-
-import static org.briarproject.bramble.api.system.SeedProvider.SEED_BYTES;
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-public class LinuxSeedProviderTest extends BrambleTestCase {
-
-	private final File testDir = TestUtils.getTestDirectory();
-
-	@Before
-	public void setUp() {
-		testDir.mkdirs();
-	}
-
-	@Test
-	public void testSeedAppearsSane() {
-		if (!(OsUtils.isLinux())) {
-			System.err.println("WARNING: Skipping test, can't run on this OS");
-			return;
-		}
-		Set<Bytes> seeds = new HashSet<Bytes>();
-		LinuxSeedProvider p = new LinuxSeedProvider();
-		for (int i = 0; i < 1000; i++) {
-			byte[] seed = p.getSeed();
-			assertEquals(SEED_BYTES, seed.length);
-			assertTrue(seeds.add(new Bytes(seed)));
-		}
-	}
-
-	@Test
-	public void testEntropyIsWrittenToPool() throws Exception {
-		if (!(OsUtils.isLinux())) {
-			System.err.println("WARNING: Skipping test, can't run on this OS");
-			return;
-		}
-		// Redirect the provider's entropy to a file
-		File urandom = new File(testDir, "urandom");
-		urandom.delete();
-		assertTrue(urandom.createNewFile());
-		assertEquals(0, urandom.length());
-		String path = urandom.getAbsolutePath();
-		LinuxSeedProvider p = new LinuxSeedProvider(path, "/dev/urandom");
-		p.getSeed();
-		// There should be 16 bytes from the clock, plus network interfaces
-		assertTrue(urandom.length() > 20);
-	}
-
-	@Test
-	public void testSeedIsReadFromPool() throws Exception {
-		if (!(OsUtils.isLinux())) {
-			System.err.println("WARNING: Skipping test, can't run on this OS");
-			return;
-		}
-		// Generate a seed
-		byte[] seed = TestUtils.getRandomBytes(SEED_BYTES);
-		// Write the seed to a file
-		File urandom = new File(testDir, "urandom");
-		urandom.delete();
-		FileOutputStream out = new FileOutputStream(urandom);
-		out.write(seed);
-		out.flush();
-		out.close();
-		assertTrue(urandom.exists());
-		assertEquals(SEED_BYTES, urandom.length());
-		// Check that the provider reads the seed from the file
-		String path = urandom.getAbsolutePath();
-		LinuxSeedProvider p = new LinuxSeedProvider("/dev/urandom", path);
-		assertArrayEquals(seed, p.getSeed());
-	}
-
-	@After
-	public void tearDown() {
-		TestUtils.deleteTestDirectory(testDir);
-	}
-}
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/test/TestSecureRandomProvider.java b/bramble-core/src/test/java/org/briarproject/bramble/test/TestSecureRandomProvider.java
new file mode 100644
index 0000000000..c34c227d16
--- /dev/null
+++ b/bramble-core/src/test/java/org/briarproject/bramble/test/TestSecureRandomProvider.java
@@ -0,0 +1,16 @@
+package org.briarproject.bramble.test;
+
+import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
+import org.briarproject.bramble.api.system.SecureRandomProvider;
+
+import java.security.Provider;
+
+@NotNullByDefault
+public class TestSecureRandomProvider implements SecureRandomProvider {
+
+	@Override
+	public Provider getProvider() {
+		// Use the default provider
+		return null;
+	}
+}
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/test/TestSeedProvider.java b/bramble-core/src/test/java/org/briarproject/bramble/test/TestSeedProvider.java
deleted file mode 100644
index a149bec8b6..0000000000
--- a/bramble-core/src/test/java/org/briarproject/bramble/test/TestSeedProvider.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package org.briarproject.bramble.test;
-
-import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-import org.briarproject.bramble.api.system.SeedProvider;
-
-@NotNullByDefault
-public class TestSeedProvider implements SeedProvider {
-
-	@Override
-	public byte[] getSeed() {
-		return TestUtils.getRandomBytes(32);
-	}
-}
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/test/TestSeedProviderModule.java b/bramble-core/src/test/java/org/briarproject/bramble/test/TestSeedProviderModule.java
index ce8650d264..b2e4db7d9c 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/test/TestSeedProviderModule.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/test/TestSeedProviderModule.java
@@ -1,6 +1,6 @@
 package org.briarproject.bramble.test;
 
-import org.briarproject.bramble.api.system.SeedProvider;
+import org.briarproject.bramble.api.system.SecureRandomProvider;
 
 import javax.inject.Singleton;
 
@@ -12,7 +12,7 @@ public class TestSeedProviderModule {
 
 	@Provides
 	@Singleton
-	SeedProvider provideSeedProvider() {
-		return new TestSeedProvider();
+	SecureRandomProvider provideSeedProvider() {
+		return new TestSecureRandomProvider();
 	}
 }
diff --git a/bramble-j2se/src/main/java/org/briarproject/bramble/system/DesktopSecureRandomModule.java b/bramble-j2se/src/main/java/org/briarproject/bramble/system/DesktopSecureRandomModule.java
new file mode 100644
index 0000000000..65246870c5
--- /dev/null
+++ b/bramble-j2se/src/main/java/org/briarproject/bramble/system/DesktopSecureRandomModule.java
@@ -0,0 +1,19 @@
+package org.briarproject.bramble.system;
+
+import org.briarproject.bramble.api.system.SecureRandomProvider;
+import org.briarproject.bramble.util.OsUtils;
+
+import javax.inject.Singleton;
+
+import dagger.Module;
+import dagger.Provides;
+
+@Module
+public class DesktopSecureRandomModule {
+
+	@Provides
+	@Singleton
+	SecureRandomProvider provideSecureRandomProvider() {
+		return OsUtils.isLinux() ? new LinuxSecureRandomProvider() : null;
+	}
+}
diff --git a/bramble-j2se/src/main/java/org/briarproject/bramble/system/DesktopSeedProviderModule.java b/bramble-j2se/src/main/java/org/briarproject/bramble/system/DesktopSeedProviderModule.java
deleted file mode 100644
index d603f7a134..0000000000
--- a/bramble-j2se/src/main/java/org/briarproject/bramble/system/DesktopSeedProviderModule.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package org.briarproject.bramble.system;
-
-import org.briarproject.bramble.api.system.SeedProvider;
-import org.briarproject.bramble.util.OsUtils;
-
-import javax.inject.Singleton;
-
-import dagger.Module;
-import dagger.Provides;
-
-@Module
-public class DesktopSeedProviderModule {
-
-	@Provides
-	@Singleton
-	SeedProvider provideSeedProvider() {
-		return OsUtils.isLinux() ? new LinuxSeedProvider() : null;
-	}
-}
-- 
GitLab