Author: dbkr
Date: 2006-07-29 16:37:24 +0000 (Sat, 29 Jul 2006)
New Revision: 9811
Removed:
trunk/apps/Freemail/src/freemail/utils/ChainedAsymmetricBlockCipher.java
Modified:
trunk/apps/Freemail/docs/spec/spec.tex
trunk/apps/Freemail/src/freemail/OutboundContact.java
trunk/apps/Freemail/src/freemail/RTSFetcher.java
Log:
Change RTS messages to be encrypted with AES, and just encrypt the AES key (and
IV) with the RSA key. Update spec to reflect the more precise details (padding
method used...)
Modified: trunk/apps/Freemail/docs/spec/spec.tex
===================================================================
--- trunk/apps/Freemail/docs/spec/spec.tex 2006-07-29 16:30:40 UTC (rev
9810)
+++ trunk/apps/Freemail/docs/spec/spec.tex 2006-07-29 16:37:24 UTC (rev
9811)
@@ -51,7 +51,7 @@
Following the last data item, there are two carriage-return-line-feeds,
followed by Alice's signature. This is the SHA-256 hash of the message RSA
encrypted with Alice's private key, included as raw bytes.
-The final message comprises a randomly generated AES session key, encrypted
with Bob's public RSA key, followed by this message-signature combination
encrypted with this sessions key. The encrypted session key must be precicely
one RSA block of ciphertext. All bytes after this are part of the symmetrically
encrypted message.
+The final message comprises a randomly generated IV, followed by an AES
session key, encrypted with Bob's public RSA key, followed by this
message-signature combination encrypted with this session key. The encrypted
session key must be precicely one RSA block of ciphertext. All bytes after this
are part of the symmetrically encrypted message. The main message is encoded
with a block size of 128 bits in CBC mode, with PKCS7 padding.
It is the sender's responsibility to keep the private part of the 'commssk'
key private. It is valid to assume that any message inserted on 'commssk' was
written by Alice and intended for Bob, since only Alice has the private key and
only they have the public key.
Modified: trunk/apps/Freemail/src/freemail/OutboundContact.java
===================================================================
--- trunk/apps/Freemail/src/freemail/OutboundContact.java 2006-07-29
16:30:40 UTC (rev 9810)
+++ trunk/apps/Freemail/src/freemail/OutboundContact.java 2006-07-29
16:37:24 UTC (rev 9811)
@@ -12,7 +12,6 @@
import freemail.utils.EmailAddress;
import freemail.utils.PropsFile;
import freemail.utils.DateStringFactory;
-import freemail.utils.ChainedAsymmetricBlockCipher;
import freemail.fcp.HighLevelFCPClient;
import freemail.fcp.FCPInsertErrorMessage;
import freemail.fcp.FCPBadFileException;
@@ -25,6 +24,13 @@
import org.bouncycastle.crypto.AsymmetricBlockCipher;
import org.bouncycastle.crypto.engines.RSAEngine;
import org.bouncycastle.crypto.InvalidCipherTextException;
+import org.bouncycastle.crypto.engines.AESEngine;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.crypto.params.ParametersWithIV;
+import org.bouncycastle.crypto.paddings.PKCS7Padding;
+import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
+import org.bouncycastle.crypto.modes.CBCBlockCipher;
+import org.bouncycastle.util.encoders.Base64;
public class OutboundContact {
public static final String OUTBOX_DIR = "outbox";
@@ -41,6 +47,11 @@
private static final long RETRANSMIT_DELAY = 26 * 60 * 60 * 1000;
// how long do we wait before we give up all hope and just bounce the
message back? 5 days is fairly standard, so we'll go with that for now, except
that means things bounce when the recipient goes to the Bahamas for a
fortnight. Could be longer if we have a GUI to see what messages are in what
delivery state.
private static final long FAIL_DELAY = 5 * 24 * 60 * 60 * 1000;
+ private static final int AES_KEY_LENGTH = 256 / 8;
+ // this is defined in the AES standard (although the Rijndael
+ // algorithm does support other block sizes.
+ // we read 128 bytes for our IV, so it needs to be constant.
+ private static final int AES_BLOCK_LENGTH = 128 / 8;
public OutboundContact(File accdir, EmailAddress a) throws
BadFreemailAddressException {
this.address = a;
@@ -226,6 +237,28 @@
return Base32.encode(buf);
}
+ private byte[] getAESParams() {
+ byte[] retval = new byte[AES_KEY_LENGTH + AES_BLOCK_LENGTH];
+
+ String params = this.contactfile.get("aesparams");
+ if (params != null) {
+ return Base64.decode(params);
+ }
+
+ SecureRandom rnd = new SecureRandom();
+
+ byte[] aes_iv_and_key = new byte[AES_KEY_LENGTH +
AES_BLOCK_LENGTH];
+
+ rnd.nextBytes(retval);
+
+ // save them for next time (if insertion fails) so we can
+ // generate the same message, otherwise they'll collide
+ // unnecessarily.
+ this.contactfile.put("aesparams", new
String(Base64.encode(retval)));
+
+ return retval;
+ }
+
/**
* Set up an outbound contact. Fetch the mailsite, generate a new SSK
keypair and post an RTS message to the appropriate KSK.
* Will block for mailsite retrieval and RTS insertion
@@ -265,7 +298,7 @@
rtsmessage.append("mailsite="+our_mailsite_uri+"\r\n");
rtsmessage.append("\r\n");
- System.out.println(rtsmessage.toString());
+ //System.out.println(rtsmessage.toString());
// sign the message
@@ -296,17 +329,47 @@
return false;
}
- // now encrypt it
+ // make up a symmetric key
+ PaddedBufferedBlockCipher aescipher = new
PaddedBufferedBlockCipher(new CBCBlockCipher(new AESEngine()), new
PKCS7Padding());
+
+ // quick paranoia check!
+ if (aescipher.getBlockSize() != AES_BLOCK_LENGTH) {
+ // bouncycastle must have changed their implementation,
so
+ // we're in trouble
+ System.out.println("Incompatible block size change
detected in cryptography API! Are you using a newer version of the bouncycastle
libraries? If so, we suggest you downgrade for now, or check for a newer
version of Freemail.");
+ return false;
+ }
+
+ byte[] aes_iv_and_key = this.getAESParams();
+
+ // now encrypt that with our recipient's public key
AsymmetricBlockCipher enccipher = new RSAEngine();
enccipher.init(true, their_pub_key);
- byte[] encmsg = null;
+ byte[] encrypted_aes_params = null;
try {
- encmsg =
ChainedAsymmetricBlockCipher.encrypt(enccipher, bos.toByteArray());
+ encrypted_aes_params =
enccipher.processBlock(aes_iv_and_key, 0, aes_iv_and_key.length);
} catch (InvalidCipherTextException e) {
e.printStackTrace();
return false;
}
+ // now encrypt the message with the symmetric key
+ KeyParameter kp = new KeyParameter(aes_iv_and_key,
aescipher.getBlockSize(), AES_KEY_LENGTH);
+ ParametersWithIV kpiv = new ParametersWithIV(kp,
aes_iv_and_key, 0, aescipher.getBlockSize());
+ aescipher.init(true, kpiv);
+
+ byte[] encmsg = new
byte[aescipher.getOutputSize(bos.toByteArray().length)+encrypted_aes_params.length];
+ System.arraycopy(encrypted_aes_params, 0, encmsg, 0,
encrypted_aes_params.length);
+ int offset = encrypted_aes_params.length;
+ offset += aescipher.processBytes(bos.toByteArray(), 0,
bos.toByteArray().length, encmsg, offset);
+
+ try {
+ aescipher.doFinal(encmsg, offset);
+ } catch (InvalidCipherTextException icte) {
+ icte.printStackTrace();
+ return false;
+ }
+
// insert it!
HighLevelFCPClient cli = new HighLevelFCPClient();
if (cli.SlotInsert(encmsg,
"KSK@"+rtsksk+"-"+DateStringFactory.getKeyString(), 1, "") < 0) {
@@ -317,6 +380,9 @@
this.contactfile.put("status", "rts-sent");
// and remember when we sent it!
this.contactfile.put("rts-sent-at",
Long.toString(System.currentTimeMillis()));
+ // and since that's been sucessfully inserted to that key, we
can
+ // throw away the symmetric key
+ this.contactfile.remove("aesparams");
return true;
}
Modified: trunk/apps/Freemail/src/freemail/RTSFetcher.java
===================================================================
--- trunk/apps/Freemail/src/freemail/RTSFetcher.java 2006-07-29 16:30:40 UTC
(rev 9810)
+++ trunk/apps/Freemail/src/freemail/RTSFetcher.java 2006-07-29 16:37:24 UTC
(rev 9811)
@@ -3,7 +3,6 @@
import freemail.fcp.HighLevelFCPClient;
import freemail.utils.DateStringFactory;
import freemail.utils.PropsFile;
-import freemail.utils.ChainedAsymmetricBlockCipher;
import java.io.File;
import java.io.FileInputStream;
@@ -22,6 +21,12 @@
import org.bouncycastle.crypto.AsymmetricBlockCipher;
import org.bouncycastle.crypto.engines.RSAEngine;
import org.bouncycastle.crypto.InvalidCipherTextException;
+import org.bouncycastle.crypto.engines.AESEngine;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.crypto.params.ParametersWithIV;
+import org.bouncycastle.crypto.paddings.PKCS7Padding;
+import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
+import org.bouncycastle.crypto.modes.CBCBlockCipher;
import freenet.support.io.LineReadingInputStream;
import freenet.support.io.TooLongException;
@@ -153,7 +158,7 @@
System.out.println("Error reading RTS message!");
return false;
} catch (InvalidCipherTextException icte) {
- System.out.println("Could not decrypt RTS message -
discarding.");
+ System.out.println("Could not decrypt RTS message -
discarding."+icte.getMessage());
return true;
}
@@ -179,7 +184,7 @@
messagebytes += lis.getLastBytesRead();
if (line == null || line.equals("")) break;
- System.out.println(line);
+ //System.out.println(line);
ps.println(line);
}
@@ -194,6 +199,12 @@
return true;
}
+ // read the rest of the file intio a byte array.
+ // will probably have extra stuff on the end because
+ // the byte array return by the decrypt function
+ // isn't resized when we know how much plaintext
+ // there is. It would be a waste of time, we know
+ // we have to read exactly one RSA block's worth.
their_encrypted_sig = new byte[bis.available()];
int totalread = 0;
@@ -203,8 +214,6 @@
totalread += read;
}
- System.out.println("read "+totalread+" bytes of
signature");
-
bis.close();
} catch (IOException ioe) {
System.out.println("IO error whilst handling RTS
message. "+ioe.getMessage());
@@ -213,8 +222,6 @@
return false;
}
-
-
PropsFile rtsprops = new PropsFile(rtsfile);
try {
@@ -277,7 +284,7 @@
byte[] their_hash;
try {
- their_hash =
deccipher.processBlock(their_encrypted_sig, 0, their_encrypted_sig.length);
+ their_hash =
deccipher.processBlock(their_encrypted_sig, 0, deccipher.getInputBlockSize());
} catch (InvalidCipherTextException icte) {
System.out.println("It was not possible to decrypt the
signature of this RTS message. Discarding the RTS message.");
msfile.delete();
@@ -287,14 +294,14 @@
// finally we can now check that our hash and their hash
// match!
- if (their_hash.length != our_hash.length) {
+ if (their_hash.length < our_hash.length) {
System.out.println("The signature of the RTS message is
not valid (our hash: "+our_hash.length+"bytes, their hash:
"+their_hash.length+"bytes. Discarding the RTS message.");
msfile.delete();
rtsfile.delete();
return true;
}
int i;
- for (i = 0; i < their_hash.length; i++) {
+ for (i = 0; i < our_hash.length; i++) {
if (their_hash[i] != our_hash[i]) {
System.out.println("The signature of the RTS
message is not valid. Discarding the RTS message.");
msfile.delete();
@@ -344,19 +351,48 @@
}
private byte[] decrypt_rts(File rtsmessage) throws IOException,
InvalidCipherTextException {
- byte[] ciphertext = new byte[(int)rtsmessage.length()];
+ // initialise our ciphers
+ RSAKeyParameters ourprivkey =
AccountManager.getPrivateKey(this.accdir);
+ AsymmetricBlockCipher deccipher = new RSAEngine();
+ deccipher.init(false, ourprivkey);
+
+ PaddedBufferedBlockCipher aescipher = new
PaddedBufferedBlockCipher(new CBCBlockCipher(new AESEngine()), new
PKCS7Padding());
+
+ // first n bytes will be an encrypted RSA block containting the
+ // AES IV and Key. Read that.
+ byte[] encrypted_params = new
byte[deccipher.getInputBlockSize()];
FileInputStream fis = new FileInputStream(rtsmessage);
int read = 0;
+
+ while (read < encrypted_params.length) {
+ read += fis.read(encrypted_params, read,
encrypted_params.length - read);
+ if (read < 0) break;
+ }
+
+ if (read < 0) {
+ throw new InvalidCipherTextException("RTS Message too
short");
+ }
+
+ byte[] aes_iv_and_key =
deccipher.processBlock(encrypted_params, 0, encrypted_params.length);
+
+ KeyParameter kp = new KeyParameter(aes_iv_and_key,
aescipher.getBlockSize(), aes_iv_and_key.length - aescipher.getBlockSize());
+ ParametersWithIV kpiv = new ParametersWithIV(kp,
aes_iv_and_key, 0, aescipher.getBlockSize());
+ aescipher.init(false, kpiv);
+
+ byte[] plaintext = new
byte[aescipher.getOutputSize((int)rtsmessage.length() - read)];
+
+ int ptbytes = 0;
while (read < rtsmessage.length()) {
- read += fis.read(ciphertext, read,
(int)rtsmessage.length() - read);
+ byte[] buf = new byte[(int)rtsmessage.length() - read];
+
+ int thisread = fis.read(buf, 0,
(int)rtsmessage.length() - read);
+ ptbytes += aescipher.processBytes(buf, 0, thisread,
plaintext, ptbytes);
+ read += thisread;
}
- RSAKeyParameters ourprivkey =
AccountManager.getPrivateKey(this.accdir);
+ fis.close();
- // decrypt it
- AsymmetricBlockCipher deccipher = new RSAEngine();
- deccipher.init(false, ourprivkey);
- byte[] plaintext =
ChainedAsymmetricBlockCipher.decrypt(deccipher, ciphertext);
+ aescipher.doFinal(plaintext, ptbytes);
return plaintext;
}
Deleted:
trunk/apps/Freemail/src/freemail/utils/ChainedAsymmetricBlockCipher.java
===================================================================
--- trunk/apps/Freemail/src/freemail/utils/ChainedAsymmetricBlockCipher.java
2006-07-29 16:30:40 UTC (rev 9810)
+++ trunk/apps/Freemail/src/freemail/utils/ChainedAsymmetricBlockCipher.java
2006-07-29 16:37:24 UTC (rev 9811)
@@ -1,39 +0,0 @@
-package freemail.utils;
-
-import java.io.IOException;
-import java.io.ByteArrayOutputStream;
-import java.io.ByteArrayInputStream;
-
-import org.bouncycastle.crypto.AsymmetricBlockCipher;
-import org.bouncycastle.crypto.InvalidCipherTextException;
-
-/*
- * A wrapper around AsymmetricBlockCipher to chain several blocks together.
- * This class just concatentates them, ie. without CBC or suchlike.
- *
- * Clearly this is intended for use with small amounts of data, where it's not
worthwhile encrypting a symmetric key and using that
- */
-public class ChainedAsymmetricBlockCipher {
- public static byte[] encrypt(AsymmetricBlockCipher cipher, byte[] in)
throws InvalidCipherTextException {
- ByteArrayOutputStream bos = new ByteArrayOutputStream();
- ByteArrayInputStream bis = new ByteArrayInputStream(in);
-
- int read;
- byte[] buf = new byte[cipher.getInputBlockSize()];
-
- while ( (read = bis.read(buf, 0, cipher.getInputBlockSize())) >
0) {
- byte[] obuf = cipher.processBlock(buf, 0, read);
- try {
- bos.write(obuf);
- } catch (IOException ioe) {
- throw new InvalidCipherTextException();
- }
- }
-
- return bos.toByteArray();
- }
-
- public static byte[] decrypt(AsymmetricBlockCipher cipher, byte[] in)
throws InvalidCipherTextException {
- return ChainedAsymmetricBlockCipher.encrypt(cipher, in);
- }
-}