Removed double-encryption of shared secrets.

This commit is contained in:
akwizgran
2011-11-15 14:09:28 +00:00
parent 6cdf68d6cb
commit 23be7fd876
8 changed files with 42 additions and 123 deletions

View File

@@ -1,18 +0,0 @@
package net.sf.briar.api.crypto;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import com.google.inject.BindingAnnotation;
/**
* Annotation for injecting the key that is used for encrypting and decrypting
* secrets stored in the database.
*/
@BindingAnnotation
@Target({ PARAMETER })
@Retention(RUNTIME)
public @interface SecretStorageKey {}

View File

@@ -1,8 +1,6 @@
package net.sf.briar.crypto;
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
@@ -10,22 +8,17 @@ import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.Security;
import java.security.Signature;
import java.util.Arrays;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.Mac;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.crypto.KeyParser;
import net.sf.briar.api.crypto.MessageDigest;
import net.sf.briar.api.crypto.SecretStorageKey;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
@@ -37,7 +30,6 @@ class CryptoComponentImpl implements CryptoComponent {
private static final String DIGEST_ALGO = "SHA-256";
private static final String KEY_PAIR_ALGO = "ECDSA";
private static final int KEY_PAIR_BITS = 256;
private static final String SECRET_STORAGE_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 int SECRET_KEY_BITS = 256;
@@ -45,15 +37,13 @@ class CryptoComponentImpl implements CryptoComponent {
private static final String MAC_ALGO = "HMacSHA256";
private static final String SIGNATURE_ALGO = "ECDSA";
private final SecretKey secretStorageKey;
private final KeyParser keyParser;
private final KeyPairGenerator keyPairGenerator;
private final KeyGenerator keyGenerator;
@Inject
CryptoComponentImpl(@SecretStorageKey SecretKey secretStorageKey) {
CryptoComponentImpl() {
Security.addProvider(new BouncyCastleProvider());
this.secretStorageKey = secretStorageKey;
try {
keyParser = new KeyParserImpl(KEY_PAIR_ALGO, PROVIDER);
keyPairGenerator = KeyPairGenerator.getInstance(KEY_PAIR_ALGO,
@@ -75,54 +65,29 @@ class CryptoComponentImpl implements CryptoComponent {
}
private SecretKey deriveFrameKey(SharedSecret s, boolean alice) {
if(alice) return deriveKey("F_A", s.getIv(), s.getCiphertext());
else return deriveKey("F_B", s.getIv(), s.getCiphertext());
if(alice) return deriveKey("F_A", s.getSecret());
else return deriveKey("F_B", s.getSecret());
}
private SecretKey deriveKey(String name, IvParameterSpec iv,
byte[] ciphertext) {
private SecretKey deriveKey(String name, byte[] secret) {
MessageDigest digest = getMessageDigest();
try {
digest.update(name.getBytes("UTF-8"));
} catch(UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
byte[] decrypted = decryptSharedSecret(iv, ciphertext);
digest.update(decrypted);
Arrays.fill(decrypted, (byte) 0); // Destroy the plaintext secret
digest.update(secret);
return new SecretKeySpec(digest.digest(), SECRET_KEY_ALGO);
}
private byte[] decryptSharedSecret(IvParameterSpec iv, byte[] ciphertext) {
try {
Cipher c = Cipher.getInstance(SECRET_STORAGE_ALGO, PROVIDER);
c.init(Cipher.DECRYPT_MODE, secretStorageKey, iv);
return c.doFinal(ciphertext);
} catch(BadPaddingException e) {
throw new RuntimeException(e);
} catch(IllegalBlockSizeException e) {
throw new RuntimeException(e);
} catch(InvalidAlgorithmParameterException e) {
throw new RuntimeException(e);
} catch(InvalidKeyException e) {
throw new RuntimeException(e);
} catch(NoSuchAlgorithmException e) {
throw new RuntimeException(e);
} catch(NoSuchPaddingException e) {
throw new RuntimeException(e);
} catch(NoSuchProviderException e) {
throw new RuntimeException(e);
}
}
public SecretKey deriveIncomingIvKey(byte[] secret) {
SharedSecret s = new SharedSecret(secret);
return deriveIvKey(s, !s.getAlice());
}
private SecretKey deriveIvKey(SharedSecret s, boolean alice) {
if(alice) return deriveKey("I_A", s.getIv(), s.getCiphertext());
else return deriveKey("I_B", s.getIv(), s.getCiphertext());
if(alice) return deriveKey("I_A", s.getSecret());
else return deriveKey("I_B", s.getSecret());
}
public SecretKey deriveIncomingMacKey(byte[] secret) {
@@ -131,8 +96,8 @@ class CryptoComponentImpl implements CryptoComponent {
}
private SecretKey deriveMacKey(SharedSecret s, boolean alice) {
if(alice) return deriveKey("M_A", s.getIv(), s.getCiphertext());
else return deriveKey("M_B", s.getIv(), s.getCiphertext());
if(alice) return deriveKey("M_A", s.getSecret());
else return deriveKey("M_B", s.getSecret());
}
public SecretKey deriveOutgoingFrameKey(byte[] secret) {

View File

@@ -1,10 +1,6 @@
package net.sf.briar.crypto;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.crypto.SecretStorageKey;
import com.google.inject.AbstractModule;
import com.google.inject.Singleton;
@@ -15,8 +11,5 @@ public class CryptoModule extends AbstractModule {
protected void configure() {
bind(CryptoComponent.class).to(
CryptoComponentImpl.class).in(Singleton.class);
// FIXME: Use a real key
bind(SecretKey.class).annotatedWith(SecretStorageKey.class).toInstance(
new SecretKeySpec(new byte[32], "AES"));
}
}

View File

@@ -1,29 +1,19 @@
package net.sf.briar.crypto;
import javax.crypto.spec.IvParameterSpec;
/**
* An encrypted shared secret from which authentication and encryption keys can
* be derived. The encrypted secret carries an IV for encrypting and decrypting
* it and a flag indicating whether Alice's keys or Bob's keys should be
* derived from the secret.
* <p>
* When two parties agree on a shared secret, they must determine which of them
* will derive Alice's keys and which Bob's. Each party then encrypts the
* secret with an independent key and IV.
* A shared secret from which authentication and encryption keys can be derived.
* The secret carries a flag indicating whether Alice's keys or Bob's keys
* should be derived from the secret. When two parties agree on a shared secret,
* they must decide which of them will derive Alice's keys and which Bob's.
*/
class SharedSecret {
static final int IV_BYTES = 16;
private final IvParameterSpec iv;
private final boolean alice;
private final byte[] ciphertext;
private final byte[] secret;
SharedSecret(byte[] secret) {
if(secret.length < IV_BYTES + 2) throw new IllegalArgumentException();
iv = new IvParameterSpec(secret, 0, IV_BYTES);
switch(secret[IV_BYTES]) {
SharedSecret(byte[] b) {
if(b.length < 2) throw new IllegalArgumentException();
switch(b[0]) {
case 0:
alice = false;
break;
@@ -33,21 +23,13 @@ class SharedSecret {
default:
throw new IllegalArgumentException();
}
ciphertext = new byte[secret.length - IV_BYTES - 1];
System.arraycopy(secret, IV_BYTES + 1, ciphertext, 0,
ciphertext.length);
secret = new byte[b.length - 1];
System.arraycopy(b, 1, secret, 0, secret.length);
}
SharedSecret(IvParameterSpec iv, boolean alice, byte[] ciphertext) {
if(iv.getIV().length != IV_BYTES) throw new IllegalArgumentException();
this.iv = iv;
SharedSecret(boolean alice, byte[] secret) {
this.alice = alice;
this.ciphertext = ciphertext;
}
/** Returns the IV used for encrypting and decrypting the secret. */
IvParameterSpec getIv() {
return iv;
this.secret = secret;
}
/**
@@ -58,22 +40,19 @@ class SharedSecret {
return alice;
}
/** Returns the encrypted shared secret. */
byte[] getCiphertext() {
return ciphertext;
/** Returns the shared secret. */
byte[] getSecret() {
return secret;
}
/**
* Returns a raw representation of the encrypted shared secret, suitable
* for storing in the database.
* Returns a raw representation of this object, suitable for storing in the
* database.
*/
byte[] getBytes() {
byte[] b = new byte[IV_BYTES + 1 + ciphertext.length];
byte[] ivBytes = iv.getIV();
assert ivBytes.length == IV_BYTES;
System.arraycopy(ivBytes, 0, b, 0, IV_BYTES);
if(alice) b[IV_BYTES] = (byte) 1;
System.arraycopy(ciphertext, 0, b, IV_BYTES + 1, ciphertext.length);
byte[] b = new byte[1 + secret.length];
if(alice) b[0] = (byte) 1;
System.arraycopy(secret, 0, b, 1, secret.length);
return b;
}
}

View File

@@ -97,9 +97,9 @@ public class ProtocolIntegrationTest extends TestCase {
assertEquals(crypto.getMessageDigest().getDigestLength(),
UniqueId.LENGTH);
// Create matching secrets: one for Alice, one for Bob
aliceSecret = new byte[45];
aliceSecret[16] = (byte) 1;
bobSecret = new byte[45];
aliceSecret = new byte[123];
aliceSecret[0] = (byte) 1;
bobSecret = new byte[123];
// Create two groups: one restricted, one unrestricted
GroupFactory groupFactory = i.getInstance(GroupFactory.class);
group = groupFactory.createGroup("Unrestricted group", null);

View File

@@ -22,7 +22,7 @@ public class CryptoComponentTest extends TestCase {
public void testKeyDerivation() {
// Create matching secrets: one for Alice, one for Bob
byte[] aliceSecret = new byte[123];
aliceSecret[SharedSecret.IV_BYTES] = (byte) 1;
aliceSecret[0] = (byte) 1;
byte[] bobSecret = new byte[123];
// Check that Alice's incoming keys match Bob's outgoing keys
assertEquals(crypto.deriveIncomingMacKey(aliceSecret),

View File

@@ -15,22 +15,22 @@ public class SharedSecretTest extends TestCase {
Random random = new Random();
byte[] secret = new byte[40];
random.nextBytes(secret);
secret[SharedSecret.IV_BYTES] = (byte) 0;
secret[0] = (byte) 0;
SharedSecret s = new SharedSecret(secret);
assertArrayEquals(secret, s.getBytes());
secret[SharedSecret.IV_BYTES] = (byte) 1;
secret[0] = (byte) 1;
s = new SharedSecret(secret);
assertArrayEquals(secret, s.getBytes());
// The Alice flag must be either 0 or 1
secret[SharedSecret.IV_BYTES] = (byte) 2;
secret[0] = (byte) 2;
try {
s = new SharedSecret(secret);
fail();
} catch(IllegalArgumentException expected) {}
// The ciphertext must be at least 1 byte long
secret = new byte[SharedSecret.IV_BYTES + 1];
// The secret must be at least 1 byte long
secret = new byte[1];
random.nextBytes(secret);
secret[SharedSecret.IV_BYTES] = (byte) 0;
secret[0] = (byte) 0;
try {
s = new SharedSecret(secret);
fail();

View File

@@ -63,9 +63,9 @@ public class BatchConnectionReadWriteTest extends TestCase {
transportId = new TransportId(TestUtils.getRandomId());
transportIndex = new TransportIndex(1);
// Create matching secrets for Alice and Bob
aliceSecret = new byte[100];
aliceSecret[16] = (byte) 1;
bobSecret = new byte[100];
aliceSecret = new byte[123];
aliceSecret[0] = (byte) 1;
bobSecret = new byte[123];
}
@Before