(Bad form, I know, but I answer my question to document it for other people)
Counter update ...
public static byte[] update_iv(byte iv[], long blocks) { ByteBuffer buf = ByteBuffer.wrap(iv); buf.order(ByteOrder.BIG_ENDIAN); long tblocks = buf.getLong(8); tblocks += blocks; buf.putLong(8, tblocks); return buf.array(); }
Explanation AES / CTR-BE
This is the main idea. If you read erickson's answer to my question, you will see that IV is basically:
<8 bytes nonce><8 bytes counter>
The counter is stored in BIG_ENDIAN format, so if you have to pull out the counter in state 1, you will get the following:
0x0 0x0 0x0 0x1
Then, when it falls into the second block, it updates it to
0x0 0x0 0x0 0x2
etc., it can technically overflow in nonce, but it is not recommended to encrypt this data in the first place.
Now I personally create nonce / counter randomly. So it becomes even harder to guess, this is not a requirement.
What you are doing is letting you update counter how many blocks in the counter you want to go, no matter if you start with 0x1 or any other counter value (like myself).
Now, if we finish half the block or less, we need to make sure that we move forward in the AES-CTR by a couple of bytes, so we can just do:
c.update(new byte[count])
where count is the number of characters, which is the distance to the block.
Explanation of my implementation
I have my keyfile stored on disk (in clear text, PLEASE DO NOT DO THIS!) Looks like this:
<16 bytes AES key> <8 bytes nonce> <8 bytes counter> <8 bytes (long) block count> <4 byte partial block count>
This gives us all the information we need to add something to the end of an already encrypted file without having to decrypt any content first. This is absolutely fantastic for log files that need to be encrypted, as well as any other content that can be transferred.
Testing
The way I tested that this really works is as follows:
echo "1234567890ABCDEF" > file1 echo "0987654321ABCDEFGHIJKLMNOPQRSTUVWXYZ" > file2 cat file1 file2 > file3
Now, if we encrypt file1 and then add file2 , we should get the same result as encrypting file3 , as long as we use the same / IV switch for both.
javac AESTest.java # Compile the java file java AESTest key file1 append.aes java AESTest key file2 append.aes append
Adding append tells the program to go into add mode and move the number of blocks forward and partially go to the next CTR cycle using the aforementioned c.update() method. From there, it starts encryption, as at any other time, and simply adds data to the output file.
java AESTest key file3 noappend.aes
Since my program will simply ignore the block counter / partial block, if you do not pass the append argument, it will just start encrypting the file using the same / IV switch as before.
Now, if we look at both files using HexEditor or vbindiff , we can verify that the two files are the same, but one of them has added content after the fact.
Full source ...
(Please note that this is the first time I have been programming in Java from a school that was a few years ago, please excuse the awful code)
Full source code for my program, where all this is implemented.
import java.util.Random; import java.security.*; import javax.crypto.*; import javax.crypto.spec.*; import java.lang.String; import java.io.File; import java.io.*; import java.nio.ByteBuffer; import java.nio.ByteOrder; public class AESTest { public static byte[] update_iv(byte iv[], long blocks) { ByteBuffer buf = ByteBuffer.wrap(iv); buf.order(ByteOrder.BIG_ENDIAN); long tblocks = buf.getLong(8); tblocks += blocks; buf.putLong(8, tblocks); return buf.array(); } public static void main(String args[]) throws Exception { if (args.length < 3) { System.out.println("Not enough parameters:"); System.out.println("keyfile input output [append]"); return; } File keyfile = new File(args[0]); DataInputStream key_in; DataOutputStream key_out; DataInputStream input = new DataInputStream(new FileInputStream(args[1])); DataOutputStream output = null; byte key[] = new byte[16 + 16]; byte aeskey[] = new byte[16]; byte iv[] = new byte[16]; byte ivOrig[] = new byte[16]; long blocks = 0; int count = 0; if (!keyfile.isFile()) { System.out.println("Creating new key"); Random ranGen = new SecureRandom(); ranGen.nextBytes(aeskey); ranGen.nextBytes(iv); iv = update_iv(iv, 0); System.arraycopy(iv, 0, ivOrig, 0, 16); } else { System.out.println("Using existing key..."); key_in = new DataInputStream(new FileInputStream(keyfile)); try { for (int i = 0; i < key.length; i++) key[i] = key_in.readByte(); } catch (EOFException e) { } System.arraycopy(key, 0, aeskey, 0, 16); System.arraycopy(key, 16, iv, 0, 16); System.arraycopy(key, 16, ivOrig, 0, 16); if (args.length == 4) { if (args[3].compareTo("append") == 0) { blocks = key_in.readLong(); count = key_in.readInt(); System.out.println("Moving IV " + blocks + " forward"); iv = update_iv(iv, blocks); output = new DataOutputStream(new FileOutputStream(args[2], true));