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}