mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-19 14:19:53 +01:00
Key derivation function based on NIST SP 800-108.
This commit is contained in:
@@ -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();
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user