package uk.ac.cam.db538.crypto; import java.nio.ByteBuffer; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; public class AesCbc { private static final int AES_BLOCKSIZE = 16; /** * Inserts data into an array of specified length. Puts random data behind to fill the rest. * @param data * @param length * @return */ private static byte[] wrapData(byte[] data, int length, boolean putRandom) { ByteBuffer buffer = ByteBuffer.allocate(length); if (data.length >= length) buffer.put(data, 0, length); else { buffer.put(data); byte[] rand = new byte[length - data.length]; if (putRandom) { try { SecureRandom.getInstance("SHA1PRNG").nextBytes(rand); } catch (NoSuchAlgorithmException e) { } } else { for (int i = 0; i < rand.length; ++i) rand[i] = 0; } buffer.put(rand); } return buffer.array(); } private static byte[] xor(byte[] original, byte[] added) { // assumes parameters are arrays of AES_BLOCKSIZE length ! byte[] result = new byte[AES_BLOCKSIZE]; for (int i = 0; i < AES_BLOCKSIZE; ++i) result[i] = (byte) (original[i] ^ added[i]); return result; } private static AesAlgorithm mAes = null; /** * Encrypts data with AES/CBC encryption. * @param data Data to encrypt * @param iv Initialization vector * @param key Encryption key * @param alignWithRandom If true, puts random data at the end to align to AES block size. Otherwise puts zeros. * @param storeLength If true, stores the length of data used to align to AES block size as one extra byte at the end. * @return */ public static byte[] encrypt(byte[] data, byte[] iv, byte[] key, boolean alignWithRandom, boolean storeLength) { // set up AES if (mAes == null) mAes = new AesAlgorithm(); mAes.setKey(key); int lengthCrap = (AES_BLOCKSIZE - data.length % AES_BLOCKSIZE) % AES_BLOCKSIZE; if (lengthCrap != 0) data = wrapData(data, data.length + lengthCrap, alignWithRandom); byte[] result = new byte[(storeLength) ? data.length + 1 : data.length]; byte[] buffer, buffer2; for (int i = 0; i < data.length / AES_BLOCKSIZE; ++i) { // get this block of data buffer = new byte[AES_BLOCKSIZE]; System.arraycopy(data, AES_BLOCKSIZE * i, buffer, 0, AES_BLOCKSIZE); // apply IV buffer = xor(buffer, iv); // encrypt buffer2 = mAes.encrypt(buffer); // copy to result System.arraycopy(buffer2, 0, result, AES_BLOCKSIZE * i, AES_BLOCKSIZE); // IV is now the previous result iv = buffer2; } if (storeLength) result[data.length] = (byte)lengthCrap; return result; } /** * Decrypts data with AES/CBC algorithm * @param data Data to encrypt * @param iv Initialization vector * @param key Encryption key * @param lengthStored Indicates whether the last byte holds the length of random data used to align to AES block size. * @return */ public static byte[] decrypt(byte[] data, byte[] iv, byte[] key, boolean lengthStored) { // set up AES if (mAes == null) mAes = new AesAlgorithm(); mAes.setKey(key); int lengthCrap = (lengthStored) ? data[data.length - 1] : 0; int length = (lengthStored) ? data.length - lengthCrap - 1 : data.length; byte[] result = new byte[length]; byte[] buffer, decrypted, xored; // decrypt with AES int blockCount = data.length / AES_BLOCKSIZE; for (int i = 0; i < blockCount; ++i) { buffer = new byte[AES_BLOCKSIZE]; // get this block of data System.arraycopy(data, AES_BLOCKSIZE * i, buffer, 0, AES_BLOCKSIZE); // decrypt decrypted = mAes.decrypt(buffer); // apply iv xored = xor(decrypted, iv); // copy to result if (i == blockCount - 1) System.arraycopy(xored, 0, result, AES_BLOCKSIZE * i, AES_BLOCKSIZE - lengthCrap); else System.arraycopy(xored, 0, result, AES_BLOCKSIZE * i, AES_BLOCKSIZE); // IV is now the original block iv = buffer; } return result; } }