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}