Use the Fortuna generator instead of the JVM's SecureRandom. Bug #4.

Note that this is only the generator part of Fortuna, not the
accumulator. The generator requires a seed, which is provided by a
platform-specific implementation of SeedProvider. On Linux the
implementation reads the seed from /dev/urandom.
This commit is contained in:
akwizgran
2014-01-13 19:16:33 +00:00
parent a565e0c749
commit c9928348ef
15 changed files with 420 additions and 18 deletions

View File

@@ -19,6 +19,8 @@ import java.util.Collections;
import java.util.List;
import java.util.logging.Logger;
import javax.inject.Inject;
import org.briarproject.api.crypto.AuthenticatedCipher;
import org.briarproject.api.crypto.CryptoComponent;
import org.briarproject.api.crypto.KeyPair;
@@ -28,9 +30,9 @@ import org.briarproject.api.crypto.PrivateKey;
import org.briarproject.api.crypto.PseudoRandom;
import org.briarproject.api.crypto.PublicKey;
import org.briarproject.api.crypto.SecretKey;
import org.briarproject.api.crypto.SeedProvider;
import org.briarproject.api.crypto.Signature;
import org.briarproject.util.ByteUtils;
import org.spongycastle.crypto.AsymmetricCipherKeyPair;
import org.spongycastle.crypto.BlockCipher;
import org.spongycastle.crypto.CipherParameters;
@@ -86,23 +88,25 @@ class CryptoComponentImpl implements CryptoComponent {
// Blank secret for argument validation
private static final byte[] BLANK_SECRET = new byte[CIPHER_KEY_BYTES];
private final KeyParser agreementKeyParser, signatureKeyParser;
private final SecureRandom secureRandom;
private final ECKeyPairGenerator agreementKeyPairGenerator;
private final ECKeyPairGenerator signatureKeyPairGenerator;
private final KeyParser agreementKeyParser, signatureKeyParser;
CryptoComponentImpl() {
agreementKeyParser = new Sec1KeyParser(PARAMETERS, P,
AGREEMENT_KEY_PAIR_BITS);
signatureKeyParser = new Sec1KeyParser(PARAMETERS, P,
SIGNATURE_KEY_PAIR_BITS);
secureRandom = new SecureRandom();
@Inject
CryptoComponentImpl(SeedProvider r) {
if(!FortunaSecureRandom.selfTest()) throw new RuntimeException();
secureRandom = new FortunaSecureRandom(r.getSeed());
ECKeyGenerationParameters params = new ECKeyGenerationParameters(
PARAMETERS, secureRandom);
agreementKeyPairGenerator = new ECKeyPairGenerator();
agreementKeyPairGenerator.init(params);
signatureKeyPairGenerator = new ECKeyPairGenerator();
signatureKeyPairGenerator.init(params);
agreementKeyParser = new Sec1KeyParser(PARAMETERS, P,
AGREEMENT_KEY_PAIR_BITS);
signatureKeyParser = new Sec1KeyParser(PARAMETERS, P,
SIGNATURE_KEY_PAIR_BITS);
}
public SecretKey generateSecretKey() {

View File

@@ -14,7 +14,9 @@ import javax.inject.Singleton;
import org.briarproject.api.crypto.CryptoComponent;
import org.briarproject.api.crypto.CryptoExecutor;
import org.briarproject.api.crypto.PasswordStrengthEstimator;
import org.briarproject.api.crypto.SeedProvider;
import org.briarproject.api.lifecycle.LifecycleManager;
import org.briarproject.util.OsUtils;
import com.google.inject.AbstractModule;
import com.google.inject.Provides;
@@ -39,6 +41,9 @@ public class CryptoModule extends AbstractModule {
}
protected void configure() {
if(OsUtils.isAndroid() || OsUtils.isLinux()) {
bind(SeedProvider.class).to(LinuxSeedProvider.class);
}
bind(CryptoComponent.class).to(
CryptoComponentImpl.class).in(Singleton.class);
bind(PasswordStrengthEstimator.class).to(

View File

@@ -0,0 +1,83 @@
package org.briarproject.crypto;
import org.briarproject.api.crypto.MessageDigest;
import org.spongycastle.crypto.BlockCipher;
import org.spongycastle.crypto.digests.SHA256Digest;
import org.spongycastle.crypto.engines.AESLightEngine;
import org.spongycastle.crypto.params.KeyParameter;
/**
* Implements the Fortuna pseudo-random number generator, as described in
* Ferguson and Schneier, <i>Practical Cryptography</i>, chapter 9.
*/
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;
// All of the following are locking: this
private final MessageDigest 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);
}
synchronized void reseed(byte[] seed) {
digest.update(key);
digest.update(seed);
digest.digest(key, 0, KEY_BYTES);
incrementCounter();
}
// Package access for testing
synchronized void incrementCounter() {
counter[0]++;
for(int i = 0; counter[i] == 0; i++) {
if(i + 1 == BLOCK_BYTES)
throw new RuntimeException("Counter exhausted");
counter[i + 1]++;
}
}
// Package access for testing
synchronized byte[] getCounter() {
return counter;
}
synchronized int nextBytes(byte[] dest, int off, int len) {
// 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;
assert remaining < BLOCK_BYTES;
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;
}
}

View File

@@ -0,0 +1,87 @@
package org.briarproject.crypto;
import java.security.Provider;
import java.security.SecureRandom;
import java.security.SecureRandomSpi;
import java.util.Arrays;
import org.briarproject.util.StringUtils;
/**
* 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 long serialVersionUID = -417332227850184134L;
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);
if(!Arrays.equals(SELF_TEST_VECTOR_3, output)) return false;
return true;
}
private static class FortunaSecureRandomSpi extends SecureRandomSpi {
private static final long serialVersionUID = -1677799887497202351L;
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 static final long serialVersionUID = -833121797778381769L;
private FortunaProvider() {
super("Fortuna", 1.0, "");
}
}
}

View File

@@ -0,0 +1,23 @@
package org.briarproject.crypto;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import org.briarproject.api.crypto.SeedProvider;
class LinuxSeedProvider implements SeedProvider {
public byte[] getSeed() {
byte[] seed = new byte[SEED_BYTES];
try {
DataInputStream in = new DataInputStream(
new FileInputStream("/dev/urandom"));
in.readFully(seed);
in.close();
} catch(IOException e) {
throw new RuntimeException(e);
}
return seed;
}
}