blob: 08cf738e6e2814e84b691a4d7c5fb71f3f9e0c1a [file] [log] [blame]
package org.bouncycastle.crypto.engines;
import org.bouncycastle.crypto.BlockCipher;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.DataLengthException;
import org.bouncycastle.crypto.OutputLengthException;
import org.bouncycastle.crypto.params.KeyParameter;
/**
* A class that provides a basic International Data Encryption Algorithm (IDEA) engine.
* <p>
* This implementation is based on the "HOWTO: INTERNATIONAL DATA ENCRYPTION ALGORITHM"
* implementation summary by Fauzan Mirza (F.U.Mirza@sheffield.ac.uk). (barring 1 typo at the
* end of the mulinv function!).
* <p>
* It can be found at ftp://ftp.funet.fi/pub/crypt/cryptography/symmetric/idea/
* <p>
* Note: This algorithm was patented in the USA, Japan and Europe. These patents expired in 2011/2012.
*/
public class IDEAEngine
implements BlockCipher
{
protected static final int BLOCK_SIZE = 8;
private int[] workingKey = null;
/**
* standard constructor.
*/
public IDEAEngine()
{
}
/**
* initialise an IDEA cipher.
*
* @param forEncryption whether or not we are for encryption.
* @param params the parameters required to set up the cipher.
* @exception IllegalArgumentException if the params argument is
* inappropriate.
*/
public void init(
boolean forEncryption,
CipherParameters params)
{
if (params instanceof KeyParameter)
{
workingKey = generateWorkingKey(forEncryption,
((KeyParameter)params).getKey());
return;
}
throw new IllegalArgumentException("invalid parameter passed to IDEA init - " + params.getClass().getName());
}
public String getAlgorithmName()
{
return "IDEA";
}
public int getBlockSize()
{
return BLOCK_SIZE;
}
public int processBlock(
byte[] in,
int inOff,
byte[] out,
int outOff)
{
if (workingKey == null)
{
throw new IllegalStateException("IDEA engine not initialised");
}
if ((inOff + BLOCK_SIZE) > in.length)
{
throw new DataLengthException("input buffer too short");
}
if ((outOff + BLOCK_SIZE) > out.length)
{
throw new OutputLengthException("output buffer too short");
}
ideaFunc(workingKey, in, inOff, out, outOff);
return BLOCK_SIZE;
}
public void reset()
{
}
private static final int MASK = 0xffff;
private static final int BASE = 0x10001;
private int bytesToWord(
byte[] in,
int inOff)
{
return ((in[inOff] << 8) & 0xff00) + (in[inOff + 1] & 0xff);
}
private void wordToBytes(
int word,
byte[] out,
int outOff)
{
out[outOff] = (byte)(word >>> 8);
out[outOff + 1] = (byte)word;
}
/**
* return x = x * y where the multiplication is done modulo
* 65537 (0x10001) (as defined in the IDEA specification) and
* a zero input is taken to be 65536 (0x10000).
*
* @param x the x value
* @param y the y value
* @return x = x * y
*/
private int mul(
int x,
int y)
{
if (x == 0)
{
x = (BASE - y);
}
else if (y == 0)
{
x = (BASE - x);
}
else
{
int p = x * y;
y = p & MASK;
x = p >>> 16;
x = y - x + ((y < x) ? 1 : 0);
}
return x & MASK;
}
private void ideaFunc(
int[] workingKey,
byte[] in,
int inOff,
byte[] out,
int outOff)
{
int x0, x1, x2, x3, t0, t1;
int keyOff = 0;
x0 = bytesToWord(in, inOff);
x1 = bytesToWord(in, inOff + 2);
x2 = bytesToWord(in, inOff + 4);
x3 = bytesToWord(in, inOff + 6);
for (int round = 0; round < 8; round++)
{
x0 = mul(x0, workingKey[keyOff++]);
x1 += workingKey[keyOff++];
x1 &= MASK;
x2 += workingKey[keyOff++];
x2 &= MASK;
x3 = mul(x3, workingKey[keyOff++]);
t0 = x1;
t1 = x2;
x2 ^= x0;
x1 ^= x3;
x2 = mul(x2, workingKey[keyOff++]);
x1 += x2;
x1 &= MASK;
x1 = mul(x1, workingKey[keyOff++]);
x2 += x1;
x2 &= MASK;
x0 ^= x1;
x3 ^= x2;
x1 ^= t1;
x2 ^= t0;
}
wordToBytes(mul(x0, workingKey[keyOff++]), out, outOff);
wordToBytes(x2 + workingKey[keyOff++], out, outOff + 2); /* NB: Order */
wordToBytes(x1 + workingKey[keyOff++], out, outOff + 4);
wordToBytes(mul(x3, workingKey[keyOff]), out, outOff + 6);
}
/**
* The following function is used to expand the user key to the encryption
* subkey. The first 16 bytes are the user key, and the rest of the subkey
* is calculated by rotating the previous 16 bytes by 25 bits to the left,
* and so on until the subkey is completed.
*/
private int[] expandKey(
byte[] uKey)
{
int[] key = new int[52];
if (uKey.length < 16)
{
byte[] tmp = new byte[16];
System.arraycopy(uKey, 0, tmp, tmp.length - uKey.length, uKey.length);
uKey = tmp;
}
for (int i = 0; i < 8; i++)
{
key[i] = bytesToWord(uKey, i * 2);
}
for (int i = 8; i < 52; i++)
{
if ((i & 7) < 6)
{
key[i] = ((key[i - 7] & 127) << 9 | key[i - 6] >> 7) & MASK;
}
else if ((i & 7) == 6)
{
key[i] = ((key[i - 7] & 127) << 9 | key[i - 14] >> 7) & MASK;
}
else
{
key[i] = ((key[i - 15] & 127) << 9 | key[i - 14] >> 7) & MASK;
}
}
return key;
}
/**
* This function computes multiplicative inverse using Euclid's Greatest
* Common Divisor algorithm. Zero and one are self inverse.
* <p>
* i.e. x * mulInv(x) == 1 (modulo BASE)
*/
private int mulInv(
int x)
{
int t0, t1, q, y;
if (x < 2)
{
return x;
}
t0 = 1;
t1 = BASE / x;
y = BASE % x;
while (y != 1)
{
q = x / y;
x = x % y;
t0 = (t0 + (t1 * q)) & MASK;
if (x == 1)
{
return t0;
}
q = y / x;
y = y % x;
t1 = (t1 + (t0 * q)) & MASK;
}
return (1 - t1) & MASK;
}
/**
* Return the additive inverse of x.
* <p>
* i.e. x + addInv(x) == 0
*/
int addInv(
int x)
{
return (0 - x) & MASK;
}
/**
* The function to invert the encryption subkey to the decryption subkey.
* It also involves the multiplicative inverse and the additive inverse functions.
*/
private int[] invertKey(
int[] inKey)
{
int t1, t2, t3, t4;
int p = 52; /* We work backwards */
int[] key = new int[52];
int inOff = 0;
t1 = mulInv(inKey[inOff++]);
t2 = addInv(inKey[inOff++]);
t3 = addInv(inKey[inOff++]);
t4 = mulInv(inKey[inOff++]);
key[--p] = t4;
key[--p] = t3;
key[--p] = t2;
key[--p] = t1;
for (int round = 1; round < 8; round++)
{
t1 = inKey[inOff++];
t2 = inKey[inOff++];
key[--p] = t2;
key[--p] = t1;
t1 = mulInv(inKey[inOff++]);
t2 = addInv(inKey[inOff++]);
t3 = addInv(inKey[inOff++]);
t4 = mulInv(inKey[inOff++]);
key[--p] = t4;
key[--p] = t2; /* NB: Order */
key[--p] = t3;
key[--p] = t1;
}
t1 = inKey[inOff++];
t2 = inKey[inOff++];
key[--p] = t2;
key[--p] = t1;
t1 = mulInv(inKey[inOff++]);
t2 = addInv(inKey[inOff++]);
t3 = addInv(inKey[inOff++]);
t4 = mulInv(inKey[inOff]);
key[--p] = t4;
key[--p] = t3;
key[--p] = t2;
key[--p] = t1;
return key;
}
private int[] generateWorkingKey(
boolean forEncryption,
byte[] userKey)
{
if (forEncryption)
{
return expandKey(userKey);
}
else
{
return invertKey(expandKey(userKey));
}
}
}