Made secret keys erasable from memory.

This commit is contained in:
akwizgran
2011-11-15 14:43:06 +00:00
parent 23be7fd876
commit f41d48eb9f
17 changed files with 135 additions and 75 deletions

View File

@@ -6,26 +6,23 @@ import java.security.Signature;
import javax.crypto.Cipher;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
public interface CryptoComponent {
SecretKey deriveIncomingFrameKey(byte[] secret);
ErasableKey deriveIncomingFrameKey(byte[] secret);
SecretKey deriveIncomingIvKey(byte[] secret);
ErasableKey deriveIncomingIvKey(byte[] secret);
SecretKey deriveIncomingMacKey(byte[] secret);
ErasableKey deriveIncomingMacKey(byte[] secret);
SecretKey deriveOutgoingFrameKey(byte[] secret);
ErasableKey deriveOutgoingFrameKey(byte[] secret);
SecretKey deriveOutgoingIvKey(byte[] secret);
ErasableKey deriveOutgoingIvKey(byte[] secret);
SecretKey deriveOutgoingMacKey(byte[] secret);
ErasableKey deriveOutgoingMacKey(byte[] secret);
KeyPair generateKeyPair();
SecretKey generateSecretKey();
Cipher getFrameCipher();
Cipher getIvCipher();
@@ -39,4 +36,6 @@ public interface CryptoComponent {
SecureRandom getSecureRandom();
Signature getSignature();
ErasableKey generateTestKey();
}

View File

@@ -0,0 +1,9 @@
package net.sf.briar.api.crypto;
import javax.crypto.SecretKey;
public interface ErasableKey extends SecretKey {
/** Erases the key from memory. */
void erase();
}

View File

@@ -10,13 +10,11 @@ import java.security.Security;
import java.security.Signature;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.Mac;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.crypto.ErasableKey;
import net.sf.briar.api.crypto.KeyParser;
import net.sf.briar.api.crypto.MessageDigest;
@@ -32,14 +30,13 @@ class CryptoComponentImpl implements CryptoComponent {
private static final int KEY_PAIR_BITS = 256;
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;
private static final int SECRET_KEY_BYTES = 32;
private static final String IV_CIPHER_ALGO = "AES/ECB/NoPadding";
private static final String MAC_ALGO = "HMacSHA256";
private static final String SIGNATURE_ALGO = "ECDSA";
private final KeyParser keyParser;
private final KeyPairGenerator keyPairGenerator;
private final KeyGenerator keyGenerator;
@Inject
CryptoComponentImpl() {
@@ -49,9 +46,6 @@ class CryptoComponentImpl implements CryptoComponent {
keyPairGenerator = KeyPairGenerator.getInstance(KEY_PAIR_ALGO,
PROVIDER);
keyPairGenerator.initialize(KEY_PAIR_BITS);
keyGenerator = KeyGenerator.getInstance(SECRET_KEY_ALGO,
PROVIDER);
keyGenerator.init(SECRET_KEY_BITS);
} catch(NoSuchAlgorithmException e) {
throw new RuntimeException(e);
} catch(NoSuchProviderException e) {
@@ -59,58 +53,59 @@ class CryptoComponentImpl implements CryptoComponent {
}
}
public SecretKey deriveIncomingFrameKey(byte[] secret) {
public ErasableKey deriveIncomingFrameKey(byte[] secret) {
SharedSecret s = new SharedSecret(secret);
return deriveFrameKey(s, !s.getAlice());
}
private SecretKey deriveFrameKey(SharedSecret s, boolean alice) {
private ErasableKey deriveFrameKey(SharedSecret s, boolean alice) {
if(alice) return deriveKey("F_A", s.getSecret());
else return deriveKey("F_B", s.getSecret());
}
private SecretKey deriveKey(String name, byte[] secret) {
private ErasableKey deriveKey(String name, byte[] secret) {
MessageDigest digest = getMessageDigest();
assert digest.getDigestLength() == SECRET_KEY_BYTES;
try {
digest.update(name.getBytes("UTF-8"));
} catch(UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
digest.update(secret);
return new SecretKeySpec(digest.digest(), SECRET_KEY_ALGO);
return new ErasableKeyImpl(digest.digest(), SECRET_KEY_ALGO);
}
public SecretKey deriveIncomingIvKey(byte[] secret) {
public ErasableKey deriveIncomingIvKey(byte[] secret) {
SharedSecret s = new SharedSecret(secret);
return deriveIvKey(s, !s.getAlice());
}
private SecretKey deriveIvKey(SharedSecret s, boolean alice) {
private ErasableKey deriveIvKey(SharedSecret s, boolean alice) {
if(alice) return deriveKey("I_A", s.getSecret());
else return deriveKey("I_B", s.getSecret());
}
public SecretKey deriveIncomingMacKey(byte[] secret) {
public ErasableKey deriveIncomingMacKey(byte[] secret) {
SharedSecret s = new SharedSecret(secret);
return deriveMacKey(s, !s.getAlice());
}
private SecretKey deriveMacKey(SharedSecret s, boolean alice) {
private ErasableKey deriveMacKey(SharedSecret s, boolean alice) {
if(alice) return deriveKey("M_A", s.getSecret());
else return deriveKey("M_B", s.getSecret());
}
public SecretKey deriveOutgoingFrameKey(byte[] secret) {
public ErasableKey deriveOutgoingFrameKey(byte[] secret) {
SharedSecret s = new SharedSecret(secret);
return deriveFrameKey(s, s.getAlice());
}
public SecretKey deriveOutgoingIvKey(byte[] secret) {
public ErasableKey deriveOutgoingIvKey(byte[] secret) {
SharedSecret s = new SharedSecret(secret);
return deriveIvKey(s, s.getAlice());
}
public SecretKey deriveOutgoingMacKey(byte[] secret) {
public ErasableKey deriveOutgoingMacKey(byte[] secret) {
SharedSecret s = new SharedSecret(secret);
return deriveMacKey(s, s.getAlice());
}
@@ -119,10 +114,6 @@ class CryptoComponentImpl implements CryptoComponent {
return keyPairGenerator.generateKeyPair();
}
public SecretKey generateSecretKey() {
return keyGenerator.generateKey();
}
public Cipher getFrameCipher() {
try {
return Cipher.getInstance(FRAME_CIPHER_ALGO, PROVIDER);
@@ -186,4 +177,10 @@ class CryptoComponentImpl implements CryptoComponent {
throw new RuntimeException(e);
}
}
public ErasableKey generateTestKey() {
byte[] b = new byte[SECRET_KEY_BYTES];
getSecureRandom().nextBytes(b);
return new ErasableKeyImpl(b, SECRET_KEY_ALGO);
}
}

View File

@@ -0,0 +1,55 @@
package net.sf.briar.crypto;
import java.util.Arrays;
import net.sf.briar.api.crypto.ErasableKey;
class ErasableKeyImpl implements ErasableKey {
private static final long serialVersionUID = -4438380720846443120L;
private final byte[] key;
private final String algorithm;
private boolean erased = false;
ErasableKeyImpl(byte[] key, String algorithm) {
this.key = key;
this.algorithm = algorithm;
}
public String getAlgorithm() {
return algorithm;
}
public byte[] getEncoded() {
if(erased) throw new IllegalStateException();
byte[] b = new byte[key.length];
System.arraycopy(key, 0, b, 0, key.length);
return b;
}
public String getFormat() {
return "RAW";
}
public void erase() {
if(erased) throw new IllegalStateException();
for(int i = 0; i < key.length; i++) key[i] = 0;
erased = true;
}
@Override
public int hashCode() {
// Not good, but the array can't be used because it's mutable
return algorithm.hashCode();
}
@Override
public boolean equals(Object o) {
if(o instanceof ErasableKeyImpl) {
ErasableKeyImpl e = (ErasableKeyImpl) o;
return algorithm.equals(e.algorithm) && Arrays.equals(key, e.key);
}
return false;
}
}

View File

@@ -13,7 +13,7 @@ import java.security.InvalidKeyException;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.SecretKey;
import net.sf.briar.api.crypto.ErasableKey;
import javax.crypto.ShortBufferException;
import javax.crypto.spec.IvParameterSpec;
@@ -21,7 +21,7 @@ class ConnectionDecrypterImpl extends FilterInputStream
implements ConnectionDecrypter {
private final Cipher frameCipher;
private final SecretKey frameKey;
private final ErasableKey frameKey;
private final byte[] iv, buf;
private int bufOff = 0, bufLen = 0;
@@ -29,7 +29,7 @@ implements ConnectionDecrypter {
private boolean betweenFrames = true;
ConnectionDecrypterImpl(InputStream in, byte[] iv, Cipher frameCipher,
SecretKey frameKey) {
ErasableKey frameKey) {
super(in);
if(iv.length != IV_LENGTH) throw new IllegalArgumentException();
this.iv = iv;

View File

@@ -12,22 +12,22 @@ import java.security.InvalidKeyException;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.SecretKey;
import net.sf.briar.api.crypto.ErasableKey;
import javax.crypto.spec.IvParameterSpec;
class ConnectionEncrypterImpl extends FilterOutputStream
implements ConnectionEncrypter {
private final Cipher frameCipher;
private final SecretKey frameKey;
private final ErasableKey frameKey;
private final byte[] iv, encryptedIv;
private long capacity, frame = 0L;
private boolean ivWritten = false, betweenFrames = false;
ConnectionEncrypterImpl(OutputStream out, long capacity, byte[] iv,
Cipher ivCipher, Cipher frameCipher, SecretKey ivKey,
SecretKey frameKey) {
Cipher ivCipher, Cipher frameCipher, ErasableKey ivKey,
ErasableKey frameKey) {
super(out);
this.capacity = capacity;
this.iv = iv;

View File

@@ -7,7 +7,7 @@ import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import net.sf.briar.api.crypto.ErasableKey;
import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.protocol.TransportIndex;
@@ -29,7 +29,7 @@ class ConnectionReaderFactoryImpl implements ConnectionReaderFactory {
TransportIndex i, byte[] encryptedIv, byte[] secret) {
// Decrypt the IV
Cipher ivCipher = crypto.getIvCipher();
SecretKey ivKey = crypto.deriveIncomingIvKey(secret);
ErasableKey ivKey = crypto.deriveIncomingIvKey(secret);
byte[] iv;
try {
ivCipher.init(Cipher.DECRYPT_MODE, ivKey);
@@ -60,12 +60,12 @@ class ConnectionReaderFactoryImpl implements ConnectionReaderFactory {
byte[] iv = IvEncoder.encodeIv(initiator, i, connection);
// Create the decrypter
Cipher frameCipher = crypto.getFrameCipher();
SecretKey frameKey = crypto.deriveIncomingFrameKey(secret);
ErasableKey frameKey = crypto.deriveIncomingFrameKey(secret);
ConnectionDecrypter decrypter = new ConnectionDecrypterImpl(in, iv,
frameCipher, frameKey);
// Create the reader
Mac mac = crypto.getMac();
SecretKey macKey = crypto.deriveIncomingMacKey(secret);
ErasableKey macKey = crypto.deriveIncomingMacKey(secret);
return new ConnectionReaderImpl(decrypter, mac, macKey);
}
}

View File

@@ -11,7 +11,7 @@ import java.security.InvalidKeyException;
import java.util.Arrays;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import net.sf.briar.api.crypto.ErasableKey;
import net.sf.briar.api.FormatException;
import net.sf.briar.api.transport.ConnectionReader;
@@ -30,7 +30,7 @@ implements ConnectionReader {
private boolean betweenFrames = true;
ConnectionReaderImpl(ConnectionDecrypter decrypter, Mac mac,
SecretKey macKey) {
ErasableKey macKey) {
super(decrypter.getInputStream());
this.decrypter = decrypter;
this.mac = mac;

View File

@@ -14,7 +14,7 @@ import java.util.logging.Logger;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.SecretKey;
import net.sf.briar.api.crypto.ErasableKey;
import net.sf.briar.api.Bytes;
import net.sf.briar.api.ContactId;
@@ -75,7 +75,7 @@ DatabaseListener {
}
private synchronized void calculateIvs(ContactId c) throws DbException {
SecretKey ivKey = crypto.deriveIncomingIvKey(db.getSharedSecret(c));
ErasableKey ivKey = crypto.deriveIncomingIvKey(db.getSharedSecret(c));
for(TransportId t : localTransportIds) {
TransportIndex i = db.getRemoteIndex(c, t);
if(i != null) {
@@ -86,7 +86,7 @@ DatabaseListener {
}
private synchronized void calculateIvs(ContactId c, TransportId t,
TransportIndex i, SecretKey ivKey, ConnectionWindow w)
TransportIndex i, ErasableKey ivKey, ConnectionWindow w)
throws DbException {
for(Long unseen : w.getUnseen()) {
Bytes iv = new Bytes(encryptIv(i, unseen, ivKey));
@@ -95,7 +95,7 @@ DatabaseListener {
}
private synchronized byte[] encryptIv(TransportIndex i, long connection,
SecretKey ivKey) {
ErasableKey ivKey) {
byte[] iv = IvEncoder.encodeIv(true, i, connection);
try {
ivCipher.init(Cipher.ENCRYPT_MODE, ivKey);
@@ -131,7 +131,7 @@ DatabaseListener {
TransportIndex i1 = ctx1.getTransportIndex();
if(c1.equals(c) && i1.equals(i)) it.remove();
}
SecretKey ivKey = crypto.deriveIncomingIvKey(db.getSharedSecret(c));
ErasableKey ivKey = crypto.deriveIncomingIvKey(db.getSharedSecret(c));
calculateIvs(c, ctx.getTransportId(), i, ivKey, w);
} catch(NoSuchContactException e) {
// The contact was removed - clean up when we get the event
@@ -182,7 +182,7 @@ DatabaseListener {
for(ContactId c : db.getContacts()) {
try {
byte[] secret = db.getSharedSecret(c);
SecretKey ivKey = crypto.deriveIncomingIvKey(secret);
ErasableKey ivKey = crypto.deriveIncomingIvKey(secret);
TransportIndex i = db.getRemoteIndex(c, t);
if(i != null) {
ConnectionWindow w = db.getConnectionWindow(c, i);

View File

@@ -7,7 +7,7 @@ import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import net.sf.briar.api.crypto.ErasableKey;
import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.protocol.TransportIndex;
@@ -36,7 +36,7 @@ class ConnectionWriterFactoryImpl implements ConnectionWriterFactory {
byte[] secret) {
// Decrypt the IV
Cipher ivCipher = crypto.getIvCipher();
SecretKey ivKey = crypto.deriveIncomingIvKey(secret);
ErasableKey ivKey = crypto.deriveIncomingIvKey(secret);
byte[] iv;
try {
ivCipher.init(Cipher.DECRYPT_MODE, ivKey);
@@ -63,14 +63,14 @@ class ConnectionWriterFactoryImpl implements ConnectionWriterFactory {
// Create the encrypter
Cipher ivCipher = crypto.getIvCipher();
Cipher frameCipher = crypto.getFrameCipher();
SecretKey ivKey = crypto.deriveOutgoingIvKey(secret);
SecretKey frameKey = crypto.deriveOutgoingFrameKey(secret);
ErasableKey ivKey = crypto.deriveOutgoingIvKey(secret);
ErasableKey frameKey = crypto.deriveOutgoingFrameKey(secret);
byte[] iv = IvEncoder.encodeIv(initiator, i, connection);
ConnectionEncrypter encrypter = new ConnectionEncrypterImpl(out,
capacity, iv, ivCipher, frameCipher, ivKey, frameKey);
// Create the writer
Mac mac = crypto.getMac();
SecretKey macKey = crypto.deriveOutgoingMacKey(secret);
ErasableKey macKey = crypto.deriveOutgoingMacKey(secret);
return new ConnectionWriterImpl(encrypter, mac, macKey);
}
}

View File

@@ -10,7 +10,7 @@ import java.io.OutputStream;
import java.security.InvalidKeyException;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import net.sf.briar.api.crypto.ErasableKey;
import net.sf.briar.api.transport.ConnectionWriter;
import net.sf.briar.util.ByteUtils;
@@ -31,7 +31,7 @@ implements ConnectionWriter {
protected long frame = 0L;
ConnectionWriterImpl(ConnectionEncrypter encrypter, Mac mac,
SecretKey macKey) {
ErasableKey macKey) {
super(encrypter.getOutputStream());
this.encrypter = encrypter;
this.mac = mac;

View File

@@ -5,7 +5,7 @@ import static net.sf.briar.util.ByteUtils.MAX_32_BIT_UNSIGNED;
import java.io.IOException;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import net.sf.briar.api.crypto.ErasableKey;
import net.sf.briar.util.ByteUtils;
@@ -23,7 +23,7 @@ class PaddedConnectionWriter extends ConnectionWriterImpl {
private IOException exception = null;
PaddedConnectionWriter(ConnectionEncrypter encrypter, Mac mac,
SecretKey macKey) {
ErasableKey macKey) {
super(encrypter, mac, macKey);
padding = new byte[maxPayloadLength];
}

View File

@@ -6,7 +6,7 @@ import static org.junit.Assert.assertArrayEquals;
import java.io.ByteArrayInputStream;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import net.sf.briar.api.crypto.ErasableKey;
import javax.crypto.spec.IvParameterSpec;
import junit.framework.TestCase;
@@ -26,7 +26,7 @@ public class ConnectionDecrypterImplTest extends TestCase {
private static final int MAC_LENGTH = 32;
private final Cipher ivCipher, frameCipher;
private final SecretKey ivKey, frameKey;
private final ErasableKey ivKey, frameKey;
private final TransportIndex transportIndex = new TransportIndex(13);
private final long connection = 12345L;
@@ -36,8 +36,8 @@ public class ConnectionDecrypterImplTest extends TestCase {
CryptoComponent crypto = i.getInstance(CryptoComponent.class);
ivCipher = crypto.getIvCipher();
frameCipher = crypto.getFrameCipher();
ivKey = crypto.generateSecretKey();
frameKey = crypto.generateSecretKey();
ivKey = crypto.generateTestKey();
frameKey = crypto.generateTestKey();
}
@Test

View File

@@ -6,7 +6,7 @@ import static org.junit.Assert.assertArrayEquals;
import java.io.ByteArrayOutputStream;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import net.sf.briar.api.crypto.ErasableKey;
import javax.crypto.spec.IvParameterSpec;
import junit.framework.TestCase;
@@ -24,7 +24,7 @@ public class ConnectionEncrypterImplTest extends TestCase {
private static final int MAC_LENGTH = 32;
private final Cipher ivCipher, frameCipher;
private final SecretKey ivKey, frameKey;
private final ErasableKey ivKey, frameKey;
private final TransportIndex transportIndex = new TransportIndex(13);
private final long connection = 12345L;
@@ -34,8 +34,8 @@ public class ConnectionEncrypterImplTest extends TestCase {
CryptoComponent crypto = i.getInstance(CryptoComponent.class);
ivCipher = crypto.getIvCipher();
frameCipher = crypto.getFrameCipher();
ivKey = crypto.generateSecretKey();
frameKey = crypto.generateSecretKey();
ivKey = crypto.generateTestKey();
frameKey = crypto.generateTestKey();
}
@Test

View File

@@ -6,7 +6,7 @@ import java.util.Collection;
import java.util.Collections;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import net.sf.briar.api.crypto.ErasableKey;
import junit.framework.TestCase;
import net.sf.briar.TestUtils;
@@ -79,7 +79,7 @@ public class ConnectionRecogniserImplTest extends TestCase {
@Test
public void testExpectedIv() throws Exception {
// Calculate the expected IV for connection number 3
SecretKey ivKey = crypto.deriveIncomingIvKey(secret);
ErasableKey ivKey = crypto.deriveIncomingIvKey(secret);
Cipher ivCipher = crypto.getIvCipher();
ivCipher.init(Cipher.ENCRYPT_MODE, ivKey);
byte[] iv = IvEncoder.encodeIv(true, remoteIndex, 3L);

View File

@@ -11,7 +11,7 @@ import java.util.Random;
import javax.crypto.Cipher;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import net.sf.briar.api.crypto.ErasableKey;
import junit.framework.TestCase;
import net.sf.briar.api.crypto.CryptoComponent;
@@ -29,7 +29,7 @@ public class FrameReadWriteTest extends TestCase {
private final CryptoComponent crypto;
private final Cipher ivCipher, frameCipher;
private final SecretKey ivKey, frameKey, macKey;
private final ErasableKey ivKey, frameKey, macKey;
private final Mac mac;
private final Random random;
private final byte[] secret = new byte[100];

View File

@@ -3,7 +3,7 @@ package net.sf.briar.transport;
import static net.sf.briar.api.transport.TransportConstants.MAX_FRAME_LENGTH;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import net.sf.briar.api.crypto.ErasableKey;
import junit.framework.TestCase;
import net.sf.briar.api.crypto.CryptoComponent;
@@ -16,7 +16,7 @@ import com.google.inject.Injector;
public abstract class TransportTest extends TestCase {
protected final Mac mac;
protected final SecretKey macKey;
protected final ErasableKey macKey;
protected final int headerLength = 4, macLength, maxPayloadLength;
public TransportTest() throws Exception {
@@ -24,7 +24,7 @@ public abstract class TransportTest extends TestCase {
Injector i = Guice.createInjector(new CryptoModule());
CryptoComponent crypto = i.getInstance(CryptoComponent.class);
mac = crypto.getMac();
macKey = crypto.generateSecretKey();
macKey = crypto.generateTestKey();
macLength = mac.getMacLength();
maxPayloadLength = MAX_FRAME_LENGTH - headerLength - macLength;
}