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

@@ -0,0 +1,16 @@
package org.briarproject;
import java.util.Random;
import org.briarproject.api.crypto.SeedProvider;
public class TestSeedProvider implements SeedProvider {
private final Random random = new Random();
public byte[] getSeed() {
byte[] seed = new byte[32];
random.nextBytes(seed);
return seed;
}
}

View File

@@ -0,0 +1,98 @@
package org.briarproject.crypto;
import static org.junit.Assert.assertArrayEquals;
import org.briarproject.BriarTestCase;
import org.junit.Test;
import org.spongycastle.crypto.BlockCipher;
import org.spongycastle.crypto.engines.AESLightEngine;
import org.spongycastle.crypto.params.KeyParameter;
public class FortunaGeneratorTest extends BriarTestCase {
@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));
}
}

View File

@@ -0,0 +1,67 @@
package org.briarproject.crypto;
import static org.briarproject.crypto.FortunaSecureRandom.SELF_TEST_VECTOR_1;
import static org.briarproject.crypto.FortunaSecureRandom.SELF_TEST_VECTOR_2;
import static org.briarproject.crypto.FortunaSecureRandom.SELF_TEST_VECTOR_3;
import static org.junit.Assert.assertArrayEquals;
import org.briarproject.BriarTestCase;
import org.briarproject.api.crypto.MessageDigest;
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;
public class FortunaSecureRandomTest extends BriarTestCase {
@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
MessageDigest 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);
}
}

View File

@@ -1,17 +1,20 @@
package org.briarproject.crypto;
import static org.junit.Assert.assertArrayEquals;
import org.briarproject.BriarTestCase;
import org.briarproject.TestSeedProvider;
import org.briarproject.api.crypto.CryptoComponent;
import org.briarproject.api.crypto.KeyPair;
import org.briarproject.api.crypto.SeedProvider;
import org.junit.Test;
public class KeyAgreementTest extends BriarTestCase {
@Test
public void testKeyAgreement() throws Exception {
CryptoComponent crypto = new CryptoComponentImpl();
SeedProvider seedProvider = new TestSeedProvider();
CryptoComponent crypto = new CryptoComponentImpl(seedProvider);
KeyPair aPair = crypto.generateAgreementKeyPair();
byte[] aPub = aPair.getPublic().getEncoded();
KeyPair bPair = crypto.generateAgreementKeyPair();

View File

@@ -6,9 +6,9 @@ import java.util.List;
import java.util.Random;
import org.briarproject.BriarTestCase;
import org.briarproject.TestSeedProvider;
import org.briarproject.api.crypto.CryptoComponent;
import org.briarproject.api.crypto.SecretKey;
import org.junit.Test;
public class KeyDerivationTest extends BriarTestCase {
@@ -17,7 +17,7 @@ public class KeyDerivationTest extends BriarTestCase {
private final byte[] secret;
public KeyDerivationTest() {
crypto = new CryptoComponentImpl();
crypto = new CryptoComponentImpl(new TestSeedProvider());
secret = new byte[32];
new Random().nextBytes(secret);
}

View File

@@ -6,6 +6,7 @@ import java.security.GeneralSecurityException;
import java.util.Random;
import org.briarproject.BriarTestCase;
import org.briarproject.TestSeedProvider;
import org.briarproject.api.crypto.KeyPair;
import org.briarproject.api.crypto.KeyParser;
import org.briarproject.api.crypto.PrivateKey;
@@ -15,7 +16,8 @@ import org.junit.Test;
public class KeyEncodingAndParsingTest extends BriarTestCase {
private final CryptoComponentImpl crypto = new CryptoComponentImpl();
private final CryptoComponentImpl crypto =
new CryptoComponentImpl(new TestSeedProvider());
@Test
public void testAgreementPublicKeyEncodingAndParsing() throws Exception {

View File

@@ -5,15 +5,16 @@ import static org.junit.Assert.assertArrayEquals;
import java.util.Random;
import org.briarproject.BriarTestCase;
import org.briarproject.api.crypto.CryptoComponent;
import org.briarproject.TestSeedProvider;
import org.junit.Test;
public class PasswordBasedKdfTest extends BriarTestCase {
private final CryptoComponentImpl crypto =
new CryptoComponentImpl(new TestSeedProvider());
@Test
public void testEncryptionAndDecryption() {
CryptoComponent crypto = new CryptoComponentImpl();
Random random = new Random();
byte[] input = new byte[1234];
random.nextBytes(input);
@@ -25,7 +26,6 @@ public class PasswordBasedKdfTest extends BriarTestCase {
@Test
public void testInvalidCiphertextReturnsNull() {
CryptoComponent crypto = new CryptoComponentImpl();
Random random = new Random();
byte[] input = new byte[1234];
random.nextBytes(input);
@@ -41,7 +41,6 @@ public class PasswordBasedKdfTest extends BriarTestCase {
@Test
public void testCalibration() {
CryptoComponentImpl crypto = new CryptoComponentImpl();
// If the target time is unachievable, one iteration should be used
int iterations = crypto.chooseIterationCount(0);
assertEquals(1, iterations);