package org.apache.kerberos.kerb.crypto;
/**
* Camellia - based on RFC 3713, about half the size of CamelliaEngine.
*
* This is based on CamelliaEngine.java from bouncycastle library.
*/
public class Camellia {
private static final int BLOCK_SIZE = 16;
private int[] state = new int[4]; // for encryption and decryption
private CamelliaKey camKey;
public void setKey(boolean forEncryption, byte[] key) {
camKey = new CamelliaKey(key, forEncryption);
}
private void process128Block(byte[] in, int inOff,
byte[] out, int outOff) {
for (int i = 0; i < 4; i++) {
state[i] = BytesUtil.bytes2int(in, inOff + (i * 4), true);
state[i] ^= camKey.kw[i];
}
camKey.f2(state, camKey.subkey, 0);
camKey.f2(state, camKey.subkey, 4);
camKey.f2(state, camKey.subkey, 8);
camKey.fls(state, camKey.ke, 0);
camKey.f2(state, camKey.subkey, 12);
camKey.f2(state, camKey.subkey, 16);
camKey.f2(state, camKey.subkey, 20);
camKey.fls(state, camKey.ke, 4);
camKey.f2(state, camKey.subkey, 24);
camKey.f2(state, camKey.subkey, 28);
camKey.f2(state, camKey.subkey, 32);
state[2] ^= camKey.kw[4];
state[3] ^= camKey.kw[5];
state[0] ^= camKey.kw[6];
state[1] ^= camKey.kw[7];
BytesUtil.int2bytes(state[2], out, outOff, true);
BytesUtil.int2bytes(state[3], out, outOff + 4, true);
BytesUtil.int2bytes(state[0], out, outOff + 8, true);
BytesUtil.int2bytes(state[1], out, outOff + 12, true);
}
private void processBlockLargerBlock(byte[] in, int inOff,
byte[] out, int outOff) {
for (int i = 0; i < 4; i++) {
state[i] = BytesUtil.bytes2int(in, inOff + (i * 4), true);
state[i] ^= camKey.kw[i];
}
camKey.f2(state, camKey.subkey, 0);
camKey.f2(state, camKey.subkey, 4);
camKey.f2(state, camKey.subkey, 8);
camKey.fls(state, camKey.ke, 0);
camKey.f2(state, camKey.subkey, 12);
camKey.f2(state, camKey.subkey, 16);
camKey.f2(state, camKey.subkey, 20);
camKey.fls(state, camKey.ke, 4);
camKey.f2(state, camKey.subkey, 24);
camKey.f2(state, camKey.subkey, 28);
camKey.f2(state, camKey.subkey, 32);
camKey.fls(state, camKey.ke, 8);
camKey.f2(state, camKey.subkey, 36);
camKey.f2(state, camKey.subkey, 40);
camKey.f2(state, camKey.subkey, 44);
state[2] ^= camKey.kw[4];
state[3] ^= camKey.kw[5];
state[0] ^= camKey.kw[6];
state[1] ^= camKey.kw[7];
BytesUtil.int2bytes(state[2], out, outOff, true);
BytesUtil.int2bytes(state[3], out, outOff + 4, true);
BytesUtil.int2bytes(state[0], out, outOff + 8, true);
BytesUtil.int2bytes(state[1], out, outOff + 12, true);
}
public void processBlock(byte[] in, int inOff) {
byte[] out = new byte[BLOCK_SIZE];
if (camKey.is128()) {
process128Block(in, inOff, out, 0);
} else {
processBlockLargerBlock(in, inOff, out, 0);
}
System.arraycopy(out, 0, in, inOff, BLOCK_SIZE);
}
public void encrypt(byte[] data, byte[] iv) {
byte[] cipher = new byte[BLOCK_SIZE];
byte[] cipherState = new byte[BLOCK_SIZE];
int blocksNum = (data.length + BLOCK_SIZE - 1) / BLOCK_SIZE;
int lastBlockLen = data.length - (blocksNum - 1) * BLOCK_SIZE;
if (blocksNum == 1) {
cbcEnc(data, 0, 1, cipherState);
return;
}
if (iv != null) {
System.arraycopy(iv, 0, cipherState, 0, BLOCK_SIZE);
}
int contBlocksNum, offset = 0;
while (blocksNum > 2) {
contBlocksNum = (data.length - offset) / BLOCK_SIZE;
if (contBlocksNum > 0) {
// Encrypt a series of contiguous blocks in place if we can, but
// don't touch the last two blocks.
contBlocksNum = (contBlocksNum > blocksNum - 2) ? blocksNum - 2 : contBlocksNum;
cbcEnc(data, offset, contBlocksNum, cipherState);
offset += contBlocksNum * BLOCK_SIZE;
blocksNum -= contBlocksNum;
} else {
cbcEnc(data, offset, 1, cipherState);
offset += BLOCK_SIZE;
blocksNum--;
}
}
// Encrypt the last two blocks and store the results in reverse order
byte[] blockN2 = new byte[BLOCK_SIZE];
byte[] blockN1 = new byte[BLOCK_SIZE];
System.arraycopy(data, offset, blockN2, 0, BLOCK_SIZE);
cbcEnc(blockN2, 0, 1, cipherState);
System.arraycopy(data, offset + BLOCK_SIZE, blockN1, 0, lastBlockLen);
cbcEnc(blockN1, 0, 1, cipherState);
System.arraycopy(blockN1, 0, data, offset, BLOCK_SIZE);
System.arraycopy(blockN2, 0, data, offset + BLOCK_SIZE, lastBlockLen);
if (iv != null) {
System.arraycopy(cipherState, 0, iv, 0, BLOCK_SIZE);
}
}
public void decrypt(byte[] data, byte[] iv) {
byte[] cipher = new byte[BLOCK_SIZE];
byte[] cipherState = new byte[BLOCK_SIZE];
int blocksNum = (data.length + BLOCK_SIZE - 1) / BLOCK_SIZE;
int lastBlockLen = data.length - (blocksNum - 1) * BLOCK_SIZE;
if (blocksNum == 1) {
cbcDec(data, 0, 1, cipherState);
return;
}
if (iv != null) {
System.arraycopy(iv, 0, cipherState, 0, BLOCK_SIZE);
}
int contBlocksNum, offset = 0;
while (blocksNum > 2) {
contBlocksNum = (data.length - offset) / BLOCK_SIZE;
if (contBlocksNum > 0) {
// Decrypt a series of contiguous blocks in place if we can, but
// don't touch the last two blocks.
contBlocksNum = (contBlocksNum > blocksNum - 2) ? blocksNum - 2 : contBlocksNum;
cbcDec(data, offset, contBlocksNum, cipherState);
offset += contBlocksNum * BLOCK_SIZE;
blocksNum -= contBlocksNum;
} else {
cbcDec(data, offset, 1, cipherState);
offset += BLOCK_SIZE;
blocksNum--;
}
}
// Decrypt the last two blocks
byte[] blockN2 = new byte[BLOCK_SIZE];
byte[] blockN1 = new byte[BLOCK_SIZE];
System.arraycopy(data, offset, blockN2, 0, BLOCK_SIZE);
System.arraycopy(data, offset + BLOCK_SIZE, blockN1, 0, lastBlockLen);
if (iv != null) {
System.arraycopy(blockN2, 0, iv, 0, BLOCK_SIZE);
}
byte[] tmpCipherState = new byte[BLOCK_SIZE];
System.arraycopy(blockN1, 0, tmpCipherState, 0, BLOCK_SIZE);
cbcDec(blockN2, 0, 1, tmpCipherState);
System.arraycopy(blockN2, lastBlockLen, blockN1, lastBlockLen, BLOCK_SIZE - lastBlockLen);
cbcDec(blockN1, 0, 1, cipherState);
System.arraycopy(blockN1, 0, data, offset, BLOCK_SIZE);
System.arraycopy(blockN2, 0, data, offset + BLOCK_SIZE, lastBlockLen);
}
/**
* CBC encrypt nblocks blocks of data in place, using and updating iv.
*/
public void cbcEnc(byte[] data, int offset, int blocksNum, byte[] cipherState) {
byte[] cipher = new byte[BLOCK_SIZE];
for (int i = 0; i < blocksNum; ++i) {
System.arraycopy(data, offset + i * BLOCK_SIZE, cipher, 0, BLOCK_SIZE);
Util.xor(cipherState, 0, cipher);
processBlock(cipher, 0);
System.arraycopy(cipher, 0, data, offset + i * BLOCK_SIZE, BLOCK_SIZE);
System.arraycopy(cipher, 0, cipherState, 0, BLOCK_SIZE);
}
}
/**
* CBC encrypt nblocks blocks of data in place, using and updating iv.
*/
public void cbcDec(byte[] data, int offset, int blocksNum, byte[] cipherState) {
byte[] lastBlock = new byte[BLOCK_SIZE];
byte[] cipher = new byte[BLOCK_SIZE];
System.arraycopy(data, offset + (blocksNum - 1) * BLOCK_SIZE, lastBlock, 0, BLOCK_SIZE);
for (int i = blocksNum; i > 0; i--) {
System.arraycopy(data, offset + (i - 1) * BLOCK_SIZE, cipher, 0, BLOCK_SIZE);
processBlock(cipher, 0);
if (i == 1) {
Util.xor(cipherState, 0, cipher);
} else {
Util.xor(data, offset + (i - 2) * BLOCK_SIZE, cipher);
}
System.arraycopy(cipher, 0, data, offset + (i - 1) * BLOCK_SIZE, BLOCK_SIZE);
}
System.arraycopy(lastBlock, 0, cipherState, 0, BLOCK_SIZE);
}
}