Key derivation function based on NIST SP 800-108.

This commit is contained in:
akwizgran
2011-11-15 17:19:11 +00:00
parent 6a15c03e81
commit 9220bb3426
9 changed files with 83 additions and 56 deletions

View File

@@ -9,11 +9,13 @@ import javax.crypto.Mac;
public interface CryptoComponent { public interface CryptoComponent {
ErasableKey deriveFrameKey(byte[] source, boolean initiator); ErasableKey deriveFrameKey(byte[] secret, boolean initiator);
ErasableKey deriveIvKey(byte[] source, boolean initiator); ErasableKey deriveIvKey(byte[] secret, boolean initiator);
ErasableKey deriveMacKey(byte[] source, boolean initiator); ErasableKey deriveMacKey(byte[] secret, boolean initiator);
byte[] deriveNextSecret(byte[] secret, long connection);
KeyPair generateKeyPair(); KeyPair generateKeyPair();

View File

@@ -1,22 +1,21 @@
package net.sf.briar.crypto; package net.sf.briar.crypto;
import java.io.UnsupportedEncodingException; import java.security.GeneralSecurityException;
import java.security.KeyPair; import java.security.KeyPair;
import java.security.KeyPairGenerator; import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.security.Security; import java.security.Security;
import java.security.Signature; import java.security.Signature;
import javax.crypto.Cipher; import javax.crypto.Cipher;
import javax.crypto.Mac; import javax.crypto.Mac;
import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.IvParameterSpec;
import net.sf.briar.api.crypto.CryptoComponent; import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.crypto.ErasableKey; import net.sf.briar.api.crypto.ErasableKey;
import net.sf.briar.api.crypto.KeyParser; import net.sf.briar.api.crypto.KeyParser;
import net.sf.briar.api.crypto.MessageDigest; import net.sf.briar.api.crypto.MessageDigest;
import net.sf.briar.util.ByteUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.jce.provider.BouncyCastleProvider;
@@ -30,10 +29,21 @@ class CryptoComponentImpl implements CryptoComponent {
private static final int KEY_PAIR_BITS = 256; private static final int KEY_PAIR_BITS = 256;
private static final String FRAME_CIPHER_ALGO = "AES/CTR/NoPadding"; private static final String FRAME_CIPHER_ALGO = "AES/CTR/NoPadding";
private static final String SECRET_KEY_ALGO = "AES"; private static final String SECRET_KEY_ALGO = "AES";
private static final int SECRET_KEY_BYTES = 32; private static final int SECRET_KEY_BYTES = 32; // 256 bits
private static final String IV_CIPHER_ALGO = "AES/ECB/NoPadding"; private static final String IV_CIPHER_ALGO = "AES/ECB/NoPadding";
private static final String MAC_ALGO = "HMacSHA256"; private static final String MAC_ALGO = "HMacSHA256";
private static final String SIGNATURE_ALGO = "ECDSA"; private static final String SIGNATURE_ALGO = "ECDSA";
private static final String KEY_DERIVATION_ALGO = "AES/CTR/NoPadding";
private static final int KEY_DERIVATION_IV_BYTES = 16; // 128 bits
// Context strings for key derivation
private static final byte[] FRAME_I = { 'F', 'R', 'A', 'M', 'E', '_', 'I' };
private static final byte[] FRAME_R = { 'F', 'R', 'A', 'M', 'E', '_', 'R' };
private static final byte[] IV_I = { 'I', 'V', '_', 'I' };
private static final byte[] IV_R = { 'I', 'V', '_', 'R' };
private static final byte[] MAC_I = { 'M', 'A', 'C', '_', 'I' };
private static final byte[] MAC_R = { 'M', 'A', 'C', '_', 'R' };
private static final byte[] NEXT = { 'N', 'E', 'X', 'T' };
private final KeyParser keyParser; private final KeyParser keyParser;
private final KeyPairGenerator keyPairGenerator; private final KeyPairGenerator keyPairGenerator;
@@ -46,38 +56,67 @@ class CryptoComponentImpl implements CryptoComponent {
keyPairGenerator = KeyPairGenerator.getInstance(KEY_PAIR_ALGO, keyPairGenerator = KeyPairGenerator.getInstance(KEY_PAIR_ALGO,
PROVIDER); PROVIDER);
keyPairGenerator.initialize(KEY_PAIR_BITS); keyPairGenerator.initialize(KEY_PAIR_BITS);
} catch(NoSuchAlgorithmException e) { } catch(GeneralSecurityException e) {
throw new RuntimeException(e);
} catch(NoSuchProviderException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
} }
public ErasableKey deriveFrameKey(byte[] source, boolean initiator) { public ErasableKey deriveFrameKey(byte[] secret, boolean initiator) {
if(initiator) return deriveKey("FRAME_I", source); if(initiator) return deriveKey(secret, FRAME_I);
else return deriveKey("FRAME_R", source); else return deriveKey(secret, FRAME_R);
} }
public ErasableKey deriveIvKey(byte[] source, boolean initiator) { public ErasableKey deriveIvKey(byte[] secret, boolean initiator) {
if(initiator) return deriveKey("IV_I", source); if(initiator) return deriveKey(secret, IV_I);
else return deriveKey("IV_R", source); else return deriveKey(secret, IV_R);
} }
public ErasableKey deriveMacKey(byte[] source, boolean initiator) { public ErasableKey deriveMacKey(byte[] secret, boolean initiator) {
if(initiator) return deriveKey("MAC_I", source); if(initiator) return deriveKey(secret, MAC_I);
else return deriveKey("MAC_R", source); else return deriveKey(secret, MAC_R);
} }
private ErasableKey deriveKey(String name, byte[] source) { private ErasableKey deriveKey(byte[] secret, byte[] context) {
MessageDigest digest = getMessageDigest(); byte[] key = counterModeKdf(secret, context);
assert digest.getDigestLength() == SECRET_KEY_BYTES; return new ErasableKeyImpl(key, SECRET_KEY_ALGO);
}
// Key derivation function based on a block cipher in CTR mode - see
// NIST SP 800-108, section 5.1
private byte[] counterModeKdf(byte[] secret, byte[] context) {
// The secret must be usable as a key
if(secret.length != SECRET_KEY_BYTES)
throw new IllegalArgumentException();
ErasableKey key = new ErasableKeyImpl(secret, SECRET_KEY_ALGO);
// The context must leave four bytes free for the length
if(context.length + 4 > SECRET_KEY_BYTES)
throw new IllegalArgumentException();
byte[] input = new byte[SECRET_KEY_BYTES];
// The initial bytes of the input are the context
System.arraycopy(context, 0, input, 0, context.length);
// The final bytes of the input are the length as a big-endian uint32
ByteUtils.writeUint32(context.length, input, input.length - 4);
// Initialise the counter to zero
byte[] zero = new byte[KEY_DERIVATION_IV_BYTES];
IvParameterSpec iv = new IvParameterSpec(zero);
try { try {
digest.update(name.getBytes("UTF-8")); Cipher cipher = Cipher.getInstance(KEY_DERIVATION_ALGO, PROVIDER);
} catch(UnsupportedEncodingException e) { cipher.init(Cipher.ENCRYPT_MODE, key, iv);
byte[] output = cipher.doFinal(input);
assert output.length == SECRET_KEY_BYTES;
return output;
} catch(GeneralSecurityException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
digest.update(source); }
return new ErasableKeyImpl(digest.digest(), SECRET_KEY_ALGO);
public byte[] deriveNextSecret(byte[] secret, long connection) {
if(connection < 0 || connection > ByteUtils.MAX_32_BIT_UNSIGNED)
throw new IllegalArgumentException();
byte[] context = new byte[NEXT.length + 4];
System.arraycopy(NEXT, 0, context, 0, NEXT.length);
ByteUtils.writeUint32(connection, context, NEXT.length);
return counterModeKdf(secret, context);
} }
public KeyPair generateKeyPair() { public KeyPair generateKeyPair() {
@@ -93,11 +132,7 @@ class CryptoComponentImpl implements CryptoComponent {
public Cipher getFrameCipher() { public Cipher getFrameCipher() {
try { try {
return Cipher.getInstance(FRAME_CIPHER_ALGO, PROVIDER); return Cipher.getInstance(FRAME_CIPHER_ALGO, PROVIDER);
} catch(NoSuchAlgorithmException e) { } catch(GeneralSecurityException e) {
throw new RuntimeException(e);
} catch(NoSuchPaddingException e) {
throw new RuntimeException(e);
} catch(NoSuchProviderException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
} }
@@ -105,11 +140,7 @@ class CryptoComponentImpl implements CryptoComponent {
public Cipher getIvCipher() { public Cipher getIvCipher() {
try { try {
return Cipher.getInstance(IV_CIPHER_ALGO, PROVIDER); return Cipher.getInstance(IV_CIPHER_ALGO, PROVIDER);
} catch(NoSuchAlgorithmException e) { } catch(GeneralSecurityException e) {
throw new RuntimeException(e);
} catch(NoSuchPaddingException e) {
throw new RuntimeException(e);
} catch(NoSuchProviderException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
} }
@@ -121,9 +152,7 @@ class CryptoComponentImpl implements CryptoComponent {
public Mac getMac() { public Mac getMac() {
try { try {
return Mac.getInstance(MAC_ALGO, PROVIDER); return Mac.getInstance(MAC_ALGO, PROVIDER);
} catch(NoSuchAlgorithmException e) { } catch(GeneralSecurityException e) {
throw new RuntimeException(e);
} catch(NoSuchProviderException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
} }
@@ -132,9 +161,7 @@ class CryptoComponentImpl implements CryptoComponent {
try { try {
return new DoubleDigest(java.security.MessageDigest.getInstance( return new DoubleDigest(java.security.MessageDigest.getInstance(
DIGEST_ALGO, PROVIDER)); DIGEST_ALGO, PROVIDER));
} catch(NoSuchAlgorithmException e) { } catch(GeneralSecurityException e) {
throw new RuntimeException(e);
} catch(NoSuchProviderException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
} }
@@ -147,9 +174,7 @@ class CryptoComponentImpl implements CryptoComponent {
public Signature getSignature() { public Signature getSignature() {
try { try {
return Signature.getInstance(SIGNATURE_ALGO, PROVIDER); return Signature.getInstance(SIGNATURE_ALGO, PROVIDER);
} catch(NoSuchAlgorithmException e) { } catch(GeneralSecurityException e) {
throw new RuntimeException(e);
} catch(NoSuchProviderException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
} }

View File

@@ -99,7 +99,7 @@ public class ProtocolIntegrationTest extends TestCase {
assertEquals(crypto.getMessageDigest().getDigestLength(), assertEquals(crypto.getMessageDigest().getDigestLength(),
UniqueId.LENGTH); UniqueId.LENGTH);
Random r = new Random(); Random r = new Random();
aliceToBobSecret = new byte[123]; aliceToBobSecret = new byte[32];
r.nextBytes(aliceToBobSecret); r.nextBytes(aliceToBobSecret);
// Create two groups: one restricted, one unrestricted // Create two groups: one restricted, one unrestricted
GroupFactory groupFactory = i.getInstance(GroupFactory.class); GroupFactory groupFactory = i.getInstance(GroupFactory.class);

View File

@@ -97,9 +97,9 @@ public abstract class DatabaseComponentTest extends TestCase {
properties); properties);
transports = Collections.singletonList(transport); transports = Collections.singletonList(transport);
Random r = new Random(); Random r = new Random();
inSecret = new byte[123]; inSecret = new byte[32];
r.nextBytes(inSecret); r.nextBytes(inSecret);
outSecret = new byte[123]; outSecret = new byte[32];
r.nextBytes(outSecret); r.nextBytes(outSecret);
} }

View File

@@ -123,9 +123,9 @@ public class H2DatabaseTest extends TestCase {
remoteTransports = Collections.singletonList(remoteTransport); remoteTransports = Collections.singletonList(remoteTransport);
subscriptions = Collections.singletonMap(group, 0L); subscriptions = Collections.singletonMap(group, 0L);
Random r = new Random(); Random r = new Random();
inSecret = new byte[123]; inSecret = new byte[32];
r.nextBytes(inSecret); r.nextBytes(inSecret);
outSecret = new byte[123]; outSecret = new byte[32];
r.nextBytes(outSecret); r.nextBytes(outSecret);
} }

View File

@@ -43,7 +43,7 @@ public class ConnectionRecogniserImplTest extends TestCase {
Injector i = Guice.createInjector(new CryptoModule()); Injector i = Guice.createInjector(new CryptoModule());
crypto = i.getInstance(CryptoComponent.class); crypto = i.getInstance(CryptoComponent.class);
contactId = new ContactId(1); contactId = new ContactId(1);
inSecret = new byte[123]; inSecret = new byte[32];
new Random().nextBytes(inSecret); new Random().nextBytes(inSecret);
transportId = new TransportId(TestUtils.getRandomId()); transportId = new TransportId(TestUtils.getRandomId());
localIndex = new TransportIndex(13); localIndex = new TransportIndex(13);

View File

@@ -39,7 +39,7 @@ public class ConnectionWriterTest extends TestCase {
new TestDatabaseModule(), new TransportBatchModule(), new TestDatabaseModule(), new TransportBatchModule(),
new TransportModule(), new TransportStreamModule()); new TransportModule(), new TransportStreamModule());
connectionWriterFactory = i.getInstance(ConnectionWriterFactory.class); connectionWriterFactory = i.getInstance(ConnectionWriterFactory.class);
outSecret = new byte[123]; outSecret = new byte[32];
new Random().nextBytes(outSecret); new Random().nextBytes(outSecret);
} }

View File

@@ -44,7 +44,7 @@ public class FrameReadWriteTest extends TestCase {
frameCipher = crypto.getFrameCipher(); frameCipher = crypto.getFrameCipher();
random = new Random(); random = new Random();
// Since we're sending frames to ourselves, we only need outgoing keys // Since we're sending frames to ourselves, we only need outgoing keys
outSecret = new byte[123]; outSecret = new byte[32];
random.nextBytes(outSecret); random.nextBytes(outSecret);
ivKey = crypto.deriveIvKey(outSecret, true); ivKey = crypto.deriveIvKey(outSecret, true);
frameKey = crypto.deriveFrameKey(outSecret, true); frameKey = crypto.deriveFrameKey(outSecret, true);

View File

@@ -65,9 +65,9 @@ public class BatchConnectionReadWriteTest extends TestCase {
transportIndex = new TransportIndex(1); transportIndex = new TransportIndex(1);
// Create matching secrets for Alice and Bob // Create matching secrets for Alice and Bob
Random r = new Random(); Random r = new Random();
aliceToBobSecret = new byte[123]; aliceToBobSecret = new byte[32];
r.nextBytes(aliceToBobSecret); r.nextBytes(aliceToBobSecret);
bobToAliceSecret = new byte[123]; bobToAliceSecret = new byte[32];
r.nextBytes(bobToAliceSecret); r.nextBytes(bobToAliceSecret);
} }