001package edu.boisestate.lowry.crypto; 002 003import java.nio.ByteBuffer; 004import java.nio.ByteOrder; 005 006/** 007 * An implementation of the RC6-32/20/b block cipher. This implementation 008 * is configured for word size w = 32 and r = 20 rounds. It also supports 009 * variable secret key lengths 'b' (16, 24, or 32 bytes), corresponding to 010 * requirements for the AES-128, AES-192, and AES-256. 011 * 012 * @author Jayce Lowry 013 */ 014public class RC6Cipher implements BlockCipher { 015 /** 016 * The block size in bytes. In RC6, the block size is defined as 017 * four words (4w). For w = 32 bits (4 bytes), the block size is 018 * 16 bytes (128 bits). 019 */ 020 private static final int BLOCK_SIZE_BYTES = 16; 021 /** 022 * The number of registers. 023 */ 024 private static final int NUM_REGISTERS = 4; 025 /** 026 * The number of rounds for encryption and decryption. 027 */ 028 private static final int NUM_ROUNDS = 20; 029 /** 030 * The key size, corresponding to the RC6 'b' parameter. 031 */ 032 private final KeySize keySize; 033 /** 034 * The round keys S[0, ..., 2r + 3]. 035 */ 036 private int[] roundKeys; 037 038 /** 039 * Creates an instance of this cipher, configured for 040 * the given key size. 041 * 042 * @param keySize The key size for this cipher. 043 */ 044 public RC6Cipher(KeySize keySize) { 045 this.keySize = keySize; 046 roundKeys = null; 047 } 048 049 @Override 050 public byte[] encipher(byte[] plaintext, byte[] key) { 051 // Validate inputs 052 if (plaintext == null || plaintext.length != BLOCK_SIZE_BYTES) { 053 throw new IllegalArgumentException(); 054 } 055 // Initialize round keys if necessary and load plaintext into registers 056 if (roundKeys == null) { 057 roundKeys = keySchedule(key); 058 } 059 int[] registers = bytesToRegisters(plaintext); // The registers [A, B, C, D] 060 061 // Pre-whitening 062 registers[1] = registers[1] + roundKeys[0]; 063 registers[3] = registers[3] + roundKeys[1]; 064 065 // Run r rounds of encryption 066 for (int i = 1; i <= NUM_ROUNDS; i++) { 067 int t = Integer.rotateLeft(registers[1] * (2 * registers[1] + 1), 5); // Rotate a distance lg 32 = 5 068 int u = Integer.rotateLeft(registers[3] * (2 * registers[3] + 1), 5); 069 registers[0] = Integer.rotateLeft(registers[0] ^ t, u) + roundKeys[2 * i]; 070 registers[2] = Integer.rotateLeft(registers[2] ^ u, t) + roundKeys[2 * i + 1]; 071 rotateRegistersLeft(registers); 072 } 073 // Post-whitening 074 registers[0] = registers[0] + roundKeys[2 * NUM_ROUNDS + 2]; 075 registers[2] = registers[2] + roundKeys[2 * NUM_ROUNDS + 3]; 076 077 return registersToBytes(registers); 078 } 079 080 @Override 081 public byte[] decipher(byte[] ciphertext, byte[] key) { 082 // Validate inputs 083 if (ciphertext == null || ciphertext.length != BLOCK_SIZE_BYTES) { 084 throw new IllegalArgumentException(); 085 } else if (key.length != keySize.numBytes) { 086 throw new UnsupportedOperationException(); 087 } 088 // Initialize round keys if necessary and load ciphertext into registers 089 if (roundKeys == null) { 090 roundKeys = keySchedule(key); 091 } 092 int[] registers = bytesToRegisters(ciphertext); // The registers [A, B, C, D] 093 094 registers[2] = registers[2] - roundKeys[2 * NUM_ROUNDS + 3]; 095 registers[0] = registers[0] - roundKeys[2 * NUM_ROUNDS + 2]; 096 097 // Run r rounds of decryption 098 for (int i = NUM_ROUNDS; i >= 1; i--) { 099 rotateRegistersRight(registers); 100 int u = Integer.rotateLeft(registers[3] * (2 * registers[3] + 1), 5); 101 int t = Integer.rotateLeft(registers[1] * (2 * registers[1] + 1), 5); 102 registers[2] = Integer.rotateRight(registers[2] - roundKeys[2 * i + 1], t) ^ u; 103 registers[0] = Integer.rotateRight(registers[0] - roundKeys[2 * i], u) ^ t; 104 } 105 registers[3] = registers[3] - roundKeys[1]; 106 registers[1] = registers[1] - roundKeys[0]; 107 return registersToBytes(registers); 108 } 109 110 @Override 111 public int getBlockSize() { 112 return BLOCK_SIZE_BYTES; 113 } 114 115 /** 116 * Preemptively calls the key schedule with the given key 117 * for the purpose of performing bulk encryption. A call 118 * to this will cause they key parameter for encipher() 119 * and decipher() to be ignored. 120 * 121 * @param key The key to initialize. 122 */ 123 public void initKey(byte[] key) { 124 this.roundKeys = keySchedule(key); 125 } 126 127 /** 128 * Resets the stored key to nothing. A call to this 129 * will cause the key parameter for encipher() and 130 * decipher() to be usable following a call to 131 * initKey(). 132 */ 133 public void resetKey() { 134 this.roundKeys = null; 135 } 136 137 /** 138 * Converts an array of bytes to an array of 32-bit 139 * registers (words). Bytes are placed in a little-endian 140 * fashion. The length of bytes is assumed to be a multiple 141 * of four. 142 * @implNote We use an int array because ints are always 143 * 32 bits in Java, exactly what is needed for the 144 * w = 32 parameter. Loading the registers little 145 * endian corresponds to, for example, how the placement 146 * of bytes into A, B, C, D is described, and the same 147 * for the array L in the key schedule. 148 * 149 * @param bytes The array of bytes. 150 * @return an array of 32-bit registers. 151 */ 152 private int[] bytesToRegisters(byte[] bytes) { 153 ByteBuffer buffer = ByteBuffer.wrap(bytes); 154 int[] registers = new int[bytes.length / Integer.BYTES]; 155 buffer.order(ByteOrder.LITTLE_ENDIAN).asIntBuffer().get(registers); 156 return registers; 157 } 158 159 /** 160 * Converts an array of registers to an array of bytes 161 * in a little endian fashion. 162 * 163 * @param registers The array of registers. 164 * @return an array of bytes. 165 */ 166 private byte[] registersToBytes(int[] registers) { 167 ByteBuffer buffer = ByteBuffer.allocate(registers.length * Integer.BYTES); 168 buffer.order(ByteOrder.LITTLE_ENDIAN).asIntBuffer().put(registers); 169 return buffer.array(); 170 } 171 172 /** 173 * Rotate/permute the registers to the left. This is 174 * the same operation as the parallel assignment step 175 * (A, B, C, D) = (B, C, D, A) for enciphering. 176 * 177 * @param registers The registers. 178 */ 179 private void rotateRegistersLeft(int[] registers) { 180 int tmp = registers[0]; 181 for (int i = 0; i < NUM_REGISTERS - 1; i ++) { 182 registers[i] = registers[i + 1]; 183 } 184 registers[NUM_REGISTERS - 1] = tmp; 185 } 186 187 /** 188 * Rotate/permute the registers to the left. This is 189 * the same operation as the parallel assignment step 190 * (A, B, C, D) = (D, A, B, C) for deciphering. 191 * 192 * @param registers The registers. 193 */ 194 private void rotateRegistersRight(int[] registers) { 195 int tmp = registers[NUM_REGISTERS - 1]; 196 for (int i = NUM_REGISTERS - 1; i > 0; i--) { 197 registers[i] = registers[i - 1]; 198 } 199 registers[0] = tmp; 200 } 201 202 /** 203 * Derives 2r + 4 round keys from the given key, loaded 204 * into an array of 32-bit registers. 205 * 206 * @param key The private key. 207 * @return An array of the round keys as registers. 208 */ 209 private int[] keySchedule(byte[] key) { 210 if (key == null) { 211 throw new IllegalArgumentException(); 212 } else if (key.length != keySize.numBytes) { 213 throw new UnsupportedOperationException(); 214 } 215 // Magic constants 216 final int P = 0xB7E15163; 217 final int Q = 0x9E3779B9; 218 219 // Load the key into a set of registers 220 int[] keyRegisters = bytesToRegisters(key); // The array L 221 int numRoundKeys = 2 * NUM_ROUNDS + 4; 222 int[] roundKeys = new int[numRoundKeys]; // The array S 223 224 // Initialize S with the sequence P, P + Q, P + 2Q, ..., P + (2r + 3)Q 225 roundKeys[0] = P; 226 for (int i = 1; i < numRoundKeys; i++) { 227 roundKeys[i] = roundKeys[i - 1] + Q; 228 } 229 230 // Mix the key registers L into the round key array S 231 int i, j, A, B; 232 i = j = A = B = 0; 233 int iterations = 3 * Math.max(keyRegisters.length, numRoundKeys); 234 235 for (int s = 1; s <= iterations; s++) { 236 roundKeys[i] = Integer.rotateLeft(roundKeys[i] + (A + B), 3); 237 A = roundKeys[i]; 238 239 keyRegisters[j] = Integer.rotateLeft(keyRegisters[j] + (A + B), (A + B)); 240 B = keyRegisters[j]; 241 242 i = (i + 1) % numRoundKeys; 243 j = (j + 1) % keyRegisters.length; 244 } 245 246 return roundKeys; 247 } 248}