Use FortunaGenerator to implement PseudoRandom.

This commit is contained in:
akwizgran
2015-01-14 20:46:03 +00:00
parent 1c7432cac4
commit 7fbad8dc26
9 changed files with 63 additions and 92 deletions

View File

@@ -9,6 +9,7 @@ import javax.inject.Singleton;
import org.briarproject.api.android.AndroidExecutor; import org.briarproject.api.android.AndroidExecutor;
import org.briarproject.api.android.AndroidNotificationManager; import org.briarproject.api.android.AndroidNotificationManager;
import org.briarproject.api.android.ReferenceManager; import org.briarproject.api.android.ReferenceManager;
import org.briarproject.api.crypto.SecretKey;
import org.briarproject.api.db.DatabaseConfig; import org.briarproject.api.db.DatabaseConfig;
import org.briarproject.api.lifecycle.LifecycleManager; import org.briarproject.api.lifecycle.LifecycleManager;
import org.briarproject.api.ui.UiCallback; import org.briarproject.api.ui.UiCallback;
@@ -54,7 +55,7 @@ public class AndroidModule extends AbstractModule {
final File dir = app.getApplicationContext().getDir("db", MODE_PRIVATE); final File dir = app.getApplicationContext().getDir("db", MODE_PRIVATE);
return new DatabaseConfig() { return new DatabaseConfig() {
private volatile byte[] key = null; private volatile SecretKey key = null;
public boolean databaseExists() { public boolean databaseExists() {
return dir.isDirectory() && dir.listFiles().length > 0; return dir.isDirectory() && dir.listFiles().length > 0;
@@ -64,11 +65,11 @@ public class AndroidModule extends AbstractModule {
return dir; return dir;
} }
public void setEncryptionKey(byte[] key) { public void setEncryptionKey(SecretKey key) {
this.key = key; this.key = key;
} }
public byte[] getEncryptionKey() { public SecretKey getEncryptionKey() {
return key; return key;
} }

View File

@@ -23,6 +23,7 @@ import org.briarproject.android.util.FixedVerticalSpace;
import org.briarproject.android.util.LayoutUtils; import org.briarproject.android.util.LayoutUtils;
import org.briarproject.api.crypto.CryptoComponent; import org.briarproject.api.crypto.CryptoComponent;
import org.briarproject.api.crypto.CryptoExecutor; import org.briarproject.api.crypto.CryptoExecutor;
import org.briarproject.api.crypto.SecretKey;
import org.briarproject.api.db.DatabaseConfig; import org.briarproject.api.db.DatabaseConfig;
import org.briarproject.util.StringUtils; import org.briarproject.util.StringUtils;
@@ -140,7 +141,7 @@ public class PasswordActivity extends RoboActivity {
if(key == null) { if(key == null) {
tryAgain(); tryAgain();
} else { } else {
databaseConfig.setEncryptionKey(key); databaseConfig.setEncryptionKey(new SecretKey(key));
setResultAndFinish(); setResultAndFinish();
} }
} }

View File

@@ -34,6 +34,7 @@ import org.briarproject.api.crypto.CryptoComponent;
import org.briarproject.api.crypto.CryptoExecutor; import org.briarproject.api.crypto.CryptoExecutor;
import org.briarproject.api.crypto.KeyPair; import org.briarproject.api.crypto.KeyPair;
import org.briarproject.api.crypto.PasswordStrengthEstimator; import org.briarproject.api.crypto.PasswordStrengthEstimator;
import org.briarproject.api.crypto.SecretKey;
import org.briarproject.api.db.DatabaseConfig; import org.briarproject.api.db.DatabaseConfig;
import org.briarproject.util.StringUtils; import org.briarproject.util.StringUtils;
@@ -225,7 +226,7 @@ OnEditorActionListener {
// Store the DB key and create the identity in a background thread // Store the DB key and create the identity in a background thread
cryptoExecutor.execute(new Runnable() { cryptoExecutor.execute(new Runnable() {
public void run() { public void run() {
byte[] key = crypto.generateSecretKey().getBytes(); SecretKey key = crypto.generateSecretKey();
databaseConfig.setEncryptionKey(key); databaseConfig.setEncryptionKey(key);
byte[] encrypted = encryptDatabaseKey(key, password); byte[] encrypted = encryptDatabaseKey(key, password);
storeEncryptedDatabaseKey(encrypted); storeEncryptedDatabaseKey(encrypted);
@@ -247,9 +248,9 @@ OnEditorActionListener {
LOG.info("Key storage took " + duration + " ms"); LOG.info("Key storage took " + duration + " ms");
} }
private byte[] encryptDatabaseKey(byte[] key, String password) { private byte[] encryptDatabaseKey(SecretKey key, String password) {
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
byte[] encrypted = crypto.encryptWithPassword(key, password); byte[] encrypted = crypto.encryptWithPassword(key.getBytes(), password);
long duration = System.currentTimeMillis() - now; long duration = System.currentTimeMillis() - now;
if(LOG.isLoggable(INFO)) if(LOG.isLoggable(INFO))
LOG.info("Key derivation took " + duration + " ms"); LOG.info("Key derivation took " + duration + " ms");

View File

@@ -3,9 +3,12 @@ package org.briarproject.api.crypto;
/** A secret key used for encryption and/or authentication. */ /** A secret key used for encryption and/or authentication. */
public class SecretKey { public class SecretKey {
public static final int LENGTH = 32; // Bytes
private final byte[] key; private final byte[] key;
public SecretKey(byte[] key) { public SecretKey(byte[] key) {
if(key.length != LENGTH) throw new IllegalArgumentException();
this.key = key; this.key = key;
} }

View File

@@ -2,15 +2,17 @@ package org.briarproject.api.db;
import java.io.File; import java.io.File;
import org.briarproject.api.crypto.SecretKey;
public interface DatabaseConfig { public interface DatabaseConfig {
boolean databaseExists(); boolean databaseExists();
File getDatabaseDirectory(); File getDatabaseDirectory();
void setEncryptionKey(byte[] key); void setEncryptionKey(SecretKey key);
byte[] getEncryptionKey(); SecretKey getEncryptionKey();
long getMaxSize(); long getMaxSize();
} }

View File

@@ -9,7 +9,6 @@ import static org.briarproject.util.ByteUtils.MAX_32_BIT_UNSIGNED;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.logging.Logger; import java.util.logging.Logger;
@@ -49,7 +48,6 @@ class CryptoComponentImpl implements CryptoComponent {
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(CryptoComponentImpl.class.getName()); Logger.getLogger(CryptoComponentImpl.class.getName());
private static final int CIPHER_KEY_BYTES = 32; // 256 bits
private static final int AGREEMENT_KEY_PAIR_BITS = 256; private static final int AGREEMENT_KEY_PAIR_BITS = 256;
private static final int SIGNATURE_KEY_PAIR_BITS = 256; private static final int SIGNATURE_KEY_PAIR_BITS = 256;
private static final int STORAGE_IV_BYTES = 16; // 128 bits private static final int STORAGE_IV_BYTES = 16; // 128 bits
@@ -73,8 +71,6 @@ class CryptoComponentImpl implements CryptoComponent {
{ 'A', '_', 'F', 'R', 'A', 'M', 'E', '\0' }; { 'A', '_', 'F', 'R', 'A', 'M', 'E', '\0' };
private static final byte[] B_FRAME = private static final byte[] B_FRAME =
{ 'B', '_', 'F', 'R', 'A', 'M', 'E', '\0' }; { 'B', '_', 'F', 'R', 'A', 'M', 'E', '\0' };
// Blank secret for argument validation
private static final byte[] BLANK_SECRET = new byte[CIPHER_KEY_BYTES];
private final SecureRandom secureRandom; private final SecureRandom secureRandom;
private final ECKeyPairGenerator agreementKeyPairGenerator; private final ECKeyPairGenerator agreementKeyPairGenerator;
@@ -105,7 +101,7 @@ class CryptoComponentImpl implements CryptoComponent {
} }
public SecretKey generateSecretKey() { public SecretKey generateSecretKey() {
byte[] b = new byte[CIPHER_KEY_BYTES]; byte[] b = new byte[SecretKey.LENGTH];
secureRandom.nextBytes(b); secureRandom.nextBytes(b);
return new SecretKey(b); return new SecretKey(b);
} }
@@ -115,7 +111,7 @@ class CryptoComponentImpl implements CryptoComponent {
} }
public PseudoRandom getPseudoRandom(int seed1, int seed2) { public PseudoRandom getPseudoRandom(int seed1, int seed2) {
return new PseudoRandomImpl(getMessageDigest(), seed1, seed2); return new PseudoRandomImpl(seed1, seed2);
} }
public SecureRandom getSecureRandom() { public SecureRandom getSecureRandom() {
@@ -172,9 +168,7 @@ class CryptoComponentImpl implements CryptoComponent {
} }
public int[] deriveConfirmationCodes(byte[] secret) { public int[] deriveConfirmationCodes(byte[] secret) {
if(secret.length != CIPHER_KEY_BYTES) if(secret.length != SecretKey.LENGTH)
throw new IllegalArgumentException();
if(Arrays.equals(secret, BLANK_SECRET))
throw new IllegalArgumentException(); throw new IllegalArgumentException();
byte[] alice = counterModeKdf(secret, CODE, 0); byte[] alice = counterModeKdf(secret, CODE, 0);
byte[] bob = counterModeKdf(secret, CODE, 1); byte[] bob = counterModeKdf(secret, CODE, 1);
@@ -185,9 +179,7 @@ class CryptoComponentImpl implements CryptoComponent {
} }
public byte[][] deriveInvitationNonces(byte[] secret) { public byte[][] deriveInvitationNonces(byte[] secret) {
if(secret.length != CIPHER_KEY_BYTES) if(secret.length != SecretKey.LENGTH)
throw new IllegalArgumentException();
if(Arrays.equals(secret, BLANK_SECRET))
throw new IllegalArgumentException(); throw new IllegalArgumentException();
byte[] alice = counterModeKdf(secret, NONCE, 0); byte[] alice = counterModeKdf(secret, NONCE, 0);
byte[] bob = counterModeKdf(secret, NONCE, 1); byte[] bob = counterModeKdf(secret, NONCE, 1);
@@ -237,26 +229,20 @@ class CryptoComponentImpl implements CryptoComponent {
} }
public byte[] deriveGroupSalt(byte[] secret) { public byte[] deriveGroupSalt(byte[] secret) {
if(secret.length != CIPHER_KEY_BYTES) if(secret.length != SecretKey.LENGTH)
throw new IllegalArgumentException();
if(Arrays.equals(secret, BLANK_SECRET))
throw new IllegalArgumentException(); throw new IllegalArgumentException();
return counterModeKdf(secret, SALT, 0); return counterModeKdf(secret, SALT, 0);
} }
public byte[] deriveInitialSecret(byte[] secret, int transportIndex) { public byte[] deriveInitialSecret(byte[] secret, int transportIndex) {
if(secret.length != CIPHER_KEY_BYTES) if(secret.length != SecretKey.LENGTH)
throw new IllegalArgumentException();
if(Arrays.equals(secret, BLANK_SECRET))
throw new IllegalArgumentException(); throw new IllegalArgumentException();
if(transportIndex < 0) throw new IllegalArgumentException(); if(transportIndex < 0) throw new IllegalArgumentException();
return counterModeKdf(secret, FIRST, transportIndex); return counterModeKdf(secret, FIRST, transportIndex);
} }
public byte[] deriveNextSecret(byte[] secret, long period) { public byte[] deriveNextSecret(byte[] secret, long period) {
if(secret.length != CIPHER_KEY_BYTES) if(secret.length != SecretKey.LENGTH)
throw new IllegalArgumentException();
if(Arrays.equals(secret, BLANK_SECRET))
throw new IllegalArgumentException(); throw new IllegalArgumentException();
if(period < 0 || period > MAX_32_BIT_UNSIGNED) if(period < 0 || period > MAX_32_BIT_UNSIGNED)
throw new IllegalArgumentException(); throw new IllegalArgumentException();
@@ -264,9 +250,7 @@ class CryptoComponentImpl implements CryptoComponent {
} }
public SecretKey deriveTagKey(byte[] secret, boolean alice) { public SecretKey deriveTagKey(byte[] secret, boolean alice) {
if(secret.length != CIPHER_KEY_BYTES) if(secret.length != SecretKey.LENGTH)
throw new IllegalArgumentException();
if(Arrays.equals(secret, BLANK_SECRET))
throw new IllegalArgumentException(); throw new IllegalArgumentException();
if(alice) return deriveKey(secret, A_TAG, 0); if(alice) return deriveKey(secret, A_TAG, 0);
else return deriveKey(secret, B_TAG, 0); else return deriveKey(secret, B_TAG, 0);
@@ -274,9 +258,7 @@ class CryptoComponentImpl implements CryptoComponent {
public SecretKey deriveFrameKey(byte[] secret, long streamNumber, public SecretKey deriveFrameKey(byte[] secret, long streamNumber,
boolean alice) { boolean alice) {
if(secret.length != CIPHER_KEY_BYTES) if(secret.length != SecretKey.LENGTH)
throw new IllegalArgumentException();
if(Arrays.equals(secret, BLANK_SECRET))
throw new IllegalArgumentException(); throw new IllegalArgumentException();
if(streamNumber < 0 || streamNumber > MAX_32_BIT_UNSIGNED) if(streamNumber < 0 || streamNumber > MAX_32_BIT_UNSIGNED)
throw new IllegalArgumentException(); throw new IllegalArgumentException();
@@ -366,32 +348,30 @@ class CryptoComponentImpl implements CryptoComponent {
// Key derivation function based on a hash function - see NIST SP 800-56A, // Key derivation function based on a hash function - see NIST SP 800-56A,
// section 5.8 // section 5.8
private byte[] concatenationKdf(byte[]... args) { private byte[] concatenationKdf(byte[]... inputs) {
// The output of the hash function must be long enough to use as a key // The output of the hash function must be long enough to use as a key
MessageDigest messageDigest = getMessageDigest(); MessageDigest messageDigest = getMessageDigest();
if(messageDigest.getDigestLength() < CIPHER_KEY_BYTES) if(messageDigest.getDigestLength() < SecretKey.LENGTH)
throw new RuntimeException(); throw new RuntimeException();
// Each argument is length-prefixed - the length must fit in an // Each input is length-prefixed - the length must fit in an
// unsigned 8-bit integer // unsigned 8-bit integer
for(byte[] arg : args) { for(byte[] input : inputs) {
if(arg.length > 255) throw new IllegalArgumentException(); if(input.length > 255) throw new IllegalArgumentException();
messageDigest.update((byte) arg.length); messageDigest.update((byte) input.length);
messageDigest.update(arg); messageDigest.update(input);
} }
byte[] hash = messageDigest.digest(); byte[] hash = messageDigest.digest();
// The output is the first CIPHER_KEY_BYTES bytes of the hash // The output is the first SecretKey.LENGTH bytes of the hash
if(hash.length == CIPHER_KEY_BYTES) return hash; if(hash.length == SecretKey.LENGTH) return hash;
byte[] output = new byte[CIPHER_KEY_BYTES]; byte[] truncated = new byte[SecretKey.LENGTH];
System.arraycopy(hash, 0, output, 0, output.length); System.arraycopy(hash, 0, truncated, 0, truncated.length);
return output; return truncated;
} }
// Key derivation function based on a PRF in counter mode - see // Key derivation function based on a PRF in counter mode - see
// NIST SP 800-108, section 5.1 // NIST SP 800-108, section 5.1
private byte[] counterModeKdf(byte[] secret, byte[] label, long context) { private byte[] counterModeKdf(byte[] secret, byte[] label, long context) {
if(secret.length != CIPHER_KEY_BYTES) if(secret.length != SecretKey.LENGTH)
throw new IllegalArgumentException();
if(Arrays.equals(secret, BLANK_SECRET))
throw new IllegalArgumentException(); throw new IllegalArgumentException();
// The label must be null-terminated // The label must be null-terminated
if(label[label.length - 1] != '\0') if(label[label.length - 1] != '\0')
@@ -402,20 +382,20 @@ class CryptoComponentImpl implements CryptoComponent {
prf.init(k); prf.init(k);
int macLength = prf.getMacSize(); int macLength = prf.getMacSize();
// The output of the PRF must be long enough to use as a key // The output of the PRF must be long enough to use as a key
if(macLength < CIPHER_KEY_BYTES) throw new RuntimeException(); if(macLength < SecretKey.LENGTH) throw new RuntimeException();
byte[] mac = new byte[macLength]; byte[] mac = new byte[macLength];
prf.update((byte) 0); // Counter prf.update((byte) 0); // Counter
prf.update(label, 0, label.length); // Null-terminated prf.update(label, 0, label.length); // Null-terminated
byte[] contextBytes = new byte[4]; byte[] contextBytes = new byte[4];
ByteUtils.writeUint32(context, contextBytes, 0); ByteUtils.writeUint32(context, contextBytes, 0);
prf.update(contextBytes, 0, contextBytes.length); prf.update(contextBytes, 0, contextBytes.length);
prf.update((byte) CIPHER_KEY_BYTES); // Output length prf.update((byte) SecretKey.LENGTH); // Output length
prf.doFinal(mac, 0); prf.doFinal(mac, 0);
// The output is the first CIPHER_KEY_BYTES bytes of the MAC // The output is the first SecretKey.LENGTH bytes of the MAC
if(mac.length == CIPHER_KEY_BYTES) return mac; if(mac.length == SecretKey.LENGTH) return mac;
byte[] output = new byte[CIPHER_KEY_BYTES]; byte[] truncated = new byte[SecretKey.LENGTH];
System.arraycopy(mac, 0, output, 0, output.length); System.arraycopy(mac, 0, truncated, 0, truncated.length);
return output; return truncated;
} }
// Password-based key derivation function - see PKCS#5 v2.1, section 5.2 // Password-based key derivation function - see PKCS#5 v2.1, section 5.2
@@ -424,7 +404,7 @@ class CryptoComponentImpl implements CryptoComponent {
Digest digest = new SHA256Digest(); Digest digest = new SHA256Digest();
PKCS5S2ParametersGenerator gen = new PKCS5S2ParametersGenerator(digest); PKCS5S2ParametersGenerator gen = new PKCS5S2ParametersGenerator(digest);
gen.init(utf8, salt, iterations); gen.init(utf8, salt, iterations);
int keyLengthInBits = CIPHER_KEY_BYTES * 8; int keyLengthInBits = SecretKey.LENGTH * 8;
CipherParameters p = gen.generateDerivedParameters(keyLengthInBits); CipherParameters p = gen.generateDerivedParameters(keyLengthInBits);
return ((KeyParameter) p).getKey(); return ((KeyParameter) p).getKey();
} }
@@ -461,7 +441,7 @@ class CryptoComponentImpl implements CryptoComponent {
private long sampleRunningTime(int iterations) { 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]; byte[] salt = new byte[PBKDF_SALT_BYTES];
int keyLengthInBits = CIPHER_KEY_BYTES * 8; int keyLengthInBits = SecretKey.LENGTH * 8;
long start = System.nanoTime(); long start = System.nanoTime();
Digest digest = new SHA256Digest(); Digest digest = new SHA256Digest();
PKCS5S2ParametersGenerator gen = new PKCS5S2ParametersGenerator(digest); PKCS5S2ParametersGenerator gen = new PKCS5S2ParametersGenerator(digest);

View File

@@ -1,41 +1,23 @@
package org.briarproject.crypto; package org.briarproject.crypto;
import org.briarproject.api.crypto.MessageDigest;
import org.briarproject.api.crypto.PseudoRandom; import org.briarproject.api.crypto.PseudoRandom;
import org.briarproject.util.ByteUtils; import org.briarproject.util.ByteUtils;
class PseudoRandomImpl implements PseudoRandom { class PseudoRandomImpl implements PseudoRandom {
private final MessageDigest messageDigest; private final FortunaGenerator generator;
private byte[] state; PseudoRandomImpl(int seed1, int seed2) {
private int offset; byte[] seed = new byte[8];
ByteUtils.writeUint32(seed1, seed, 0);
PseudoRandomImpl(MessageDigest messageDigest, int seed1, int seed2) { ByteUtils.writeUint32(seed2, seed, 4);
this.messageDigest = messageDigest; generator = new FortunaGenerator(seed);
byte[] seedBytes = new byte[8];
ByteUtils.writeUint32(seed1, seedBytes, 0);
ByteUtils.writeUint32(seed2, seedBytes, 4);
messageDigest.update(seedBytes);
state = messageDigest.digest();
offset = 0;
} }
public synchronized byte[] nextBytes(int bytes) { public synchronized byte[] nextBytes(int length) {
byte[] b = new byte[bytes]; byte[] b = new byte[length];
int half = state.length / 2; int offset = 0;
int off = 0, len = b.length, available = half - offset; while(offset < length) offset += generator.nextBytes(b, offset, length);
while(available < len) {
System.arraycopy(state, offset, b, off, available);
off += available;
len -= available;
messageDigest.update(state, half, half);
state = messageDigest.digest();
offset = 0;
available = half;
}
System.arraycopy(state, offset, b, off, len);
offset += len;
return b; return b;
} }
} }

View File

@@ -83,7 +83,7 @@ class H2Database extends JdbcDatabase {
@Override @Override
protected Connection createConnection() throws SQLException { protected Connection createConnection() throws SQLException {
byte[] key = config.getEncryptionKey(); byte[] key = config.getEncryptionKey().getBytes();
if(key == null) throw new IllegalStateException(); if(key == null) throw new IllegalStateException();
Properties props = new Properties(); Properties props = new Properties();
props.setProperty("user", "user"); props.setProperty("user", "user");

View File

@@ -2,13 +2,14 @@ package org.briarproject;
import java.io.File; import java.io.File;
import org.briarproject.api.crypto.SecretKey;
import org.briarproject.api.db.DatabaseConfig; import org.briarproject.api.db.DatabaseConfig;
public class TestDatabaseConfig implements DatabaseConfig { public class TestDatabaseConfig implements DatabaseConfig {
private final File dir; private final File dir;
private final long maxSize; private final long maxSize;
private volatile byte[] key = new byte[] { 'f', 'o', 'o' }; private volatile SecretKey key = new SecretKey(new byte[SecretKey.LENGTH]);
public TestDatabaseConfig(File dir, long maxSize) { public TestDatabaseConfig(File dir, long maxSize) {
this.dir = dir; this.dir = dir;
@@ -23,11 +24,11 @@ public class TestDatabaseConfig implements DatabaseConfig {
return dir; return dir;
} }
public void setEncryptionKey(byte[] key) { public void setEncryptionKey(SecretKey key) {
this.key = key; this.key = key;
} }
public byte[] getEncryptionKey() { public SecretKey getEncryptionKey() {
return key; return key;
} }