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);
-       }
-}


Reply via email to