001package edu.boisestate.lowry.crypto;
002
003import java.nio.ByteBuffer;
004import java.security.SecureRandom;
005
006/**
007 * An implementation of the Counter (CTR) mode of operation.
008 *
009 * @author Jayce Lowry.
010 */
011public class CTRMode implements SymmetricCipher {
012    /**
013     * The underlying block cipher used.
014     */
015    protected final BlockCipher blockCipher;
016    /**
017     * The underlying block cipher's block size.
018     */
019    protected final int blockSize;
020
021    /**
022     * Creates a CTRMode instance with the given block
023     * cipher as the underlying cipher.
024     *
025     * @param cipher The underlying block cipher for this instance.
026     */
027    public CTRMode(BlockCipher cipher) {
028        this.blockCipher = cipher;
029        blockSize = cipher.getBlockSize();
030    }
031
032    @Override
033    public byte[] encrypt(byte[] plaintext, byte[] key) {
034        // Randomly generate a nonce
035        SecureRandom rand = new SecureRandom();
036        byte[] nonce = new byte[blockSize / 2];
037        rand.nextBytes(nonce);
038        // Prepare the counter with the nonce as the first half
039        byte[] counter = new byte[blockSize];
040        System.arraycopy(nonce, 0, counter, 0, nonce.length);
041        // Copy plaintext to working ciphertext array
042        byte[] ciphertext = new byte[plaintext.length];
043        System.arraycopy(plaintext, 0, ciphertext, 0, plaintext.length);
044        // Encrypt
045        runCTR(ciphertext, key, counter);
046        // Prepend nonce to the ciphertext
047        ByteBuffer buffer = ByteBuffer.allocate(nonce.length + ciphertext.length);
048        buffer.put(nonce).put(ciphertext);
049        return buffer.array();
050    }
051
052    @Override
053    public byte[] decrypt(byte[] ciphertext, byte[] key) {
054        // Separate nonce and data
055        ByteBuffer buffer = ByteBuffer.wrap(ciphertext);
056        byte[] nonce = new byte[blockSize / 2];
057        byte[] encryptedData = new byte[ciphertext.length - (blockSize / 2)];
058        buffer.get(nonce).get(encryptedData);
059        // Prepare counter
060        byte[] counter = new byte[blockSize];
061        System.arraycopy(nonce, 0, counter, 0, nonce.length);
062        // Decrypt
063        byte[] plaintext = new byte[encryptedData.length];
064        System.arraycopy(encryptedData, 0, plaintext, 0, encryptedData.length);
065        runCTR(plaintext, key, counter);
066        return plaintext;
067    }
068
069    /**
070     * Runs the CTR operation on the given text, using a secret key
071     * and initial counter. The data in text is modified in-place,
072     * becoming the encrypted or decrypted text.
073     *
074     * @param text The plaintext or ciphertext.
075     * @param key The secret key.
076     * @param iv The initial counter.
077     */
078    protected void runCTR(byte[] text, byte[] key, byte[] iv) {
079        // XOR text data with the encrypted counter. Once each position
080        // of the current counter has been seen, re-encrypt it and increment.
081        byte[] encryptedCounter = new byte[0];
082        int j = blockSize;
083        for (int i = 0; i < text.length; i++) {
084            if (j == blockSize) {
085                encryptedCounter = blockCipher.encipher(iv, key);
086                incrementBlock(iv, blockSize / 2, iv.length);
087                j = 0;
088            }
089            text[i] ^= encryptedCounter[j];
090            j++;
091        }
092    }
093
094    /**
095     * Increments a specific sub-block of the given bytes, treating it as an
096     * unsigned integer (big endian). This is essentially a ripple-carry addition
097     * of the sub-block and 1.
098     *
099     * @param input The input block containing the sub-block to increment.
100     * @param start The index of the most significant byte of the sub-block, inclusive.
101     * @param end The index of the least significant byte of the sub-block, exclusive.
102     * @throws IllegalArgumentException If the input is null, the indices are out of bounds,
103     * or the ending index is before the starting index.
104     * @throws IllegalStateException If the entire block overflows.
105     */
106    protected void incrementBlock(byte[] input, int start, int end) {
107        if (input == null || start < 0 || end > input.length || end < start) {
108            throw new IllegalArgumentException();
109        } else if (start == end) {
110            return;
111        }
112        int i;
113        for (i = end - 1; i >= start; i--) {
114            input[i]++;
115            if (input[i] != 0) { // No carry
116                break;
117            }
118            // Carry
119        }
120        if (i < start) { // Overflow
121            throw new IllegalStateException();
122        }
123    }
124}