Replaced encrypted IVs with pseudo-random tags.

This commit is contained in:
akwizgran
2011-12-02 12:57:39 +00:00
parent f3fdd85996
commit 14d5e6fe64
12 changed files with 104 additions and 174 deletions

View File

@@ -27,13 +27,11 @@ 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 FRAME_CIPHER_ALGO = "AES/CTR/NoPadding";
private static final String CIPHER_ALGO = "AES/CTR/NoPadding";
private static final String SECRET_KEY_ALGO = "AES";
private static final int SECRET_KEY_BYTES = 32; // 256 bits
private static final String TAG_CIPHER_ALGO = "AES/ECB/NoPadding";
private static final String MAC_ALGO = "HMacSHA256";
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
// Labels for key derivation, null-terminated
@@ -104,7 +102,7 @@ class CryptoComponentImpl implements CryptoComponent {
assert ivBytes[ivBytes.length - 1] == 0;
IvParameterSpec iv = new IvParameterSpec(ivBytes);
try {
Cipher cipher = Cipher.getInstance(KEY_DERIVATION_ALGO, PROVIDER);
Cipher cipher = Cipher.getInstance(CIPHER_ALGO, PROVIDER);
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
byte[] output = cipher.doFinal(KEY_DERIVATION_INPUT);
assert output.length == SECRET_KEY_BYTES;
@@ -137,7 +135,7 @@ class CryptoComponentImpl implements CryptoComponent {
public Cipher getFrameCipher() {
try {
return Cipher.getInstance(FRAME_CIPHER_ALGO, PROVIDER);
return Cipher.getInstance(CIPHER_ALGO, PROVIDER);
} catch(GeneralSecurityException e) {
throw new RuntimeException(e);
}
@@ -178,7 +176,7 @@ class CryptoComponentImpl implements CryptoComponent {
public Cipher getTagCipher() {
try {
return Cipher.getInstance(TAG_CIPHER_ALGO, PROVIDER);
return Cipher.getInstance(CIPHER_ALGO, PROVIDER);
} catch(GeneralSecurityException e) {
throw new RuntimeException(e);
}

View File

@@ -1,6 +1,5 @@
package net.sf.briar.transport;
import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH;
import static net.sf.briar.util.ByteUtils.MAX_32_BIT_UNSIGNED;
import java.io.EOFException;
@@ -13,10 +12,11 @@ import java.security.InvalidKeyException;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import net.sf.briar.api.crypto.ErasableKey;
import javax.crypto.ShortBufferException;
import javax.crypto.spec.IvParameterSpec;
import net.sf.briar.api.crypto.ErasableKey;
class ConnectionDecrypterImpl extends FilterInputStream
implements ConnectionDecrypter {
@@ -28,14 +28,13 @@ implements ConnectionDecrypter {
private long frame = 0L;
private boolean betweenFrames = true;
ConnectionDecrypterImpl(InputStream in, byte[] iv, Cipher frameCipher,
ConnectionDecrypterImpl(InputStream in, Cipher frameCipher,
ErasableKey frameKey) {
super(in);
if(iv.length != TAG_LENGTH) throw new IllegalArgumentException();
this.iv = iv;
this.frameCipher = frameCipher;
this.frameKey = frameKey;
buf = new byte[TAG_LENGTH];
iv = IvEncoder.encodeIv(0, frameCipher.getBlockSize());
buf = new byte[frameCipher.getBlockSize()];
}
public InputStream getInputStream() {

View File

@@ -25,27 +25,17 @@ implements ConnectionEncrypter {
private long capacity, frame = 0L;
private boolean tagWritten = false, betweenFrames = false;
ConnectionEncrypterImpl(OutputStream out, long capacity, byte[] iv,
Cipher tagCipher, Cipher frameCipher, ErasableKey tagKey,
ErasableKey frameKey) {
ConnectionEncrypterImpl(OutputStream out, long capacity, Cipher tagCipher,
Cipher frameCipher, ErasableKey tagKey, ErasableKey frameKey) {
super(out);
this.capacity = capacity;
this.iv = iv;
this.frameCipher = frameCipher;
this.frameKey = frameKey;
iv = IvEncoder.encodeIv(0, frameCipher.getBlockSize());
// Encrypt the tag
try {
tagCipher.init(Cipher.ENCRYPT_MODE, tagKey);
tag = tagCipher.doFinal(iv);
} catch(BadPaddingException badCipher) {
throw new IllegalArgumentException(badCipher);
} catch(IllegalBlockSizeException badCipher) {
throw new IllegalArgumentException(badCipher);
} catch(InvalidKeyException badKey) {
throw new IllegalArgumentException(badKey);
}
if(tag.length != TAG_LENGTH) throw new IllegalArgumentException();
tag = TagEncoder.encodeTag(0, tagCipher, tagKey);
tagKey.erase();
if(tag.length != TAG_LENGTH) throw new IllegalArgumentException();
}
public OutputStream getOutputStream() {

View File

@@ -1,11 +1,8 @@
package net.sf.briar.transport;
import java.io.InputStream;
import java.security.InvalidKeyException;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.Mac;
import net.sf.briar.api.crypto.CryptoComponent;
@@ -28,26 +25,12 @@ class ConnectionReaderFactoryImpl implements ConnectionReaderFactory {
public ConnectionReader createConnectionReader(InputStream in,
ConnectionContext ctx, byte[] tag) {
// Decrypt the tag
// Validate the tag
Cipher tagCipher = crypto.getTagCipher();
ErasableKey tagKey = crypto.deriveTagKey(ctx.getSecret(), true);
byte[] iv;
try {
tagCipher.init(Cipher.DECRYPT_MODE, tagKey);
iv = tagCipher.doFinal(tag);
} catch(BadPaddingException badCipher) {
throw new IllegalArgumentException(badCipher);
} catch(IllegalBlockSizeException badCipher) {
throw new IllegalArgumentException(badCipher);
} catch(InvalidKeyException badKey) {
throw new IllegalArgumentException(badKey);
}
boolean valid = TagEncoder.validateTag(tag, 0, tagCipher, tagKey);
tagKey.erase();
// Validate the tag
int index = ctx.getTransportIndex().getInt();
long connection = ctx.getConnectionNumber();
if(!IvEncoder.validateIv(iv, index, connection))
throw new IllegalArgumentException();
if(!valid) throw new IllegalArgumentException();
return createConnectionReader(in, true, ctx);
}
@@ -64,11 +47,8 @@ class ConnectionReaderFactoryImpl implements ConnectionReaderFactory {
ErasableKey macKey = crypto.deriveMacKey(secret, initiator);
ByteUtils.erase(secret);
// Create the decrypter
int index = ctx.getTransportIndex().getInt();
long connection = ctx.getConnectionNumber();
byte[] iv = IvEncoder.encodeIv(index, connection);
Cipher frameCipher = crypto.getFrameCipher();
ConnectionDecrypter decrypter = new ConnectionDecrypterImpl(in, iv,
ConnectionDecrypter decrypter = new ConnectionDecrypterImpl(in,
frameCipher, frameKey);
// Create the reader
Mac mac = crypto.getMac();

View File

@@ -2,7 +2,6 @@ package net.sf.briar.transport;
import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH;
import java.security.InvalidKeyException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
@@ -15,9 +14,7 @@ import java.util.concurrent.Executor;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import net.sf.briar.api.Bytes;
import net.sf.briar.api.ContactId;
@@ -103,22 +100,10 @@ DatabaseListener {
// Locking: this
private Bytes calculateTag(Context ctx, byte[] secret) {
byte[] iv = IvEncoder.encodeIv(ctx.transportIndex.getInt(),
ctx.connection);
ErasableKey tagKey = crypto.deriveTagKey(secret, true);
try {
tagCipher.init(Cipher.ENCRYPT_MODE, tagKey);
byte[] tag = tagCipher.doFinal(iv);
return new Bytes(tag);
} catch(BadPaddingException badCipher) {
throw new RuntimeException(badCipher);
} catch(IllegalBlockSizeException badCipher) {
throw new RuntimeException(badCipher);
} catch(InvalidKeyException badKey) {
throw new RuntimeException(badKey);
} finally {
tagKey.erase();
}
byte[] tag = TagEncoder.encodeTag(0, tagCipher, tagKey);
tagKey.erase();
return new Bytes(tag);
}
public void acceptConnection(final TransportId t, final byte[] tag,

View File

@@ -1,11 +1,8 @@
package net.sf.briar.transport;
import java.io.OutputStream;
import java.security.InvalidKeyException;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.Mac;
import net.sf.briar.api.crypto.CryptoComponent;
@@ -36,23 +33,9 @@ class ConnectionWriterFactoryImpl implements ConnectionWriterFactory {
// Decrypt the tag
Cipher tagCipher = crypto.getTagCipher();
ErasableKey tagKey = crypto.deriveTagKey(ctx.getSecret(), true);
byte[] iv;
try {
tagCipher.init(Cipher.DECRYPT_MODE, tagKey);
iv = tagCipher.doFinal(tag);
} catch(BadPaddingException badCipher) {
throw new RuntimeException(badCipher);
} catch(IllegalBlockSizeException badCipher) {
throw new RuntimeException(badCipher);
} catch(InvalidKeyException badKey) {
throw new RuntimeException(badKey);
}
boolean valid = TagEncoder.validateTag(tag, 0, tagCipher, tagKey);
tagKey.erase();
// Validate the tag
int index = ctx.getTransportIndex().getInt();
long connection = ctx.getConnectionNumber();
if(!IvEncoder.validateIv(iv, index, connection))
throw new IllegalArgumentException();
if(!valid) throw new IllegalArgumentException();
return createConnectionWriter(out, capacity, false, ctx);
}
@@ -65,13 +48,10 @@ class ConnectionWriterFactoryImpl implements ConnectionWriterFactory {
ErasableKey macKey = crypto.deriveMacKey(secret, initiator);
ByteUtils.erase(secret);
// Create the encrypter
int index = ctx.getTransportIndex().getInt();
long connection = ctx.getConnectionNumber();
byte[] iv = IvEncoder.encodeIv(index, connection);
Cipher tagCipher = crypto.getTagCipher();
Cipher frameCipher = crypto.getFrameCipher();
ConnectionEncrypter encrypter = new ConnectionEncrypterImpl(out,
capacity, iv, tagCipher, frameCipher, tagKey, frameKey);
capacity, tagCipher, frameCipher, tagKey, frameKey);
// Create the writer
Mac mac = crypto.getMac();
return new ConnectionWriterImpl(encrypter, mac, macKey);

View File

@@ -1,45 +1,19 @@
package net.sf.briar.transport;
import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH;
import net.sf.briar.util.ByteUtils;
class IvEncoder {
static byte[] encodeIv(int index, long connection) {
byte[] iv = new byte[TAG_LENGTH];
// Encode the transport index as an unsigned 16-bit integer
ByteUtils.writeUint16(index, iv, 4);
// Encode the connection number as an unsigned 32-bit integer
ByteUtils.writeUint32(connection, iv, 6);
static byte[] encodeIv(long frame, int blockSize) {
if(frame > ByteUtils.MAX_32_BIT_UNSIGNED)
throw new IllegalArgumentException();
byte[] iv = new byte[blockSize];
updateIv(iv, frame);
return iv;
}
static void updateIv(byte[] iv, long frame) {
if(iv.length != TAG_LENGTH) throw new IllegalArgumentException();
// Encode the frame number as an unsigned 32-bit integer
ByteUtils.writeUint32(frame, iv, 10);
}
static boolean validateIv(byte[] iv, int index, long connection) {
if(iv.length != TAG_LENGTH) return false;
// Check that the reserved bits are all zero
for(int i = 0; i < 3; i++) if(iv[i] != 0) return false;
for(int i = 10; i < iv.length; i++) if(iv[i] != 0) return false;
// Check that the transport index matches
if(index != getTransportIndex(iv)) return false;
// Check that the connection number matches
if(connection != getConnectionNumber(iv)) return false;
// The IV is valid
return true;
}
static int getTransportIndex(byte[] iv) {
if(iv.length != TAG_LENGTH) throw new IllegalArgumentException();
return ByteUtils.readUint16(iv, 4);
}
static long getConnectionNumber(byte[] iv) {
if(iv.length != TAG_LENGTH) throw new IllegalArgumentException();
return ByteUtils.readUint32(iv, 6);
// Encode the frame number as a uint32, leaving 2 bytes for the counter
ByteUtils.writeUint32(frame, iv, iv.length - 6);
}
}

View File

@@ -0,0 +1,54 @@
package net.sf.briar.transport;
import java.security.GeneralSecurityException;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import net.sf.briar.api.crypto.ErasableKey;
import net.sf.briar.api.transport.TransportConstants;
import net.sf.briar.util.ByteUtils;
class TagEncoder {
static byte[] encodeTag(long frame, Cipher tagCipher, ErasableKey tagKey) {
if(frame > ByteUtils.MAX_32_BIT_UNSIGNED)
throw new IllegalArgumentException();
// The plaintext is blank
byte[] plaintext = new byte[TransportConstants.TAG_LENGTH];
// Encode the frame number as a uint32 at the end of the IV
byte[] iv = new byte[tagCipher.getBlockSize()];
if(iv.length != plaintext.length) throw new IllegalArgumentException();
ByteUtils.writeUint32(frame, iv, iv.length - 4);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
try {
tagCipher.init(Cipher.ENCRYPT_MODE, tagKey, ivSpec);
return tagCipher.doFinal(plaintext);
} catch(GeneralSecurityException e) {
// Unsuitable cipher or key
throw new IllegalArgumentException(e);
}
}
static boolean validateTag(byte[] tag, long frame, Cipher tagCipher,
ErasableKey tagKey) {
if(tag.length != TransportConstants.TAG_LENGTH) return false;
// Encode the frame number as a uint32 at the end of the IV
byte[] iv = new byte[tagCipher.getBlockSize()];
if(iv.length != tag.length) throw new IllegalArgumentException();
ByteUtils.writeUint32(frame, iv, iv.length - 4);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
try {
tagCipher.init(Cipher.DECRYPT_MODE, tagKey, ivSpec);
byte[] plaintext = tagCipher.doFinal(tag);
// The plaintext should be blank
for(int i = 0; i < plaintext.length; i++) {
if(plaintext[i] != 0) return false;
}
return true;
} catch(GeneralSecurityException e) {
// Unsuitable cipher or key
throw new IllegalArgumentException(e);
}
}
}