package ssh;

import Tools.*;
 
/**
 * CipherIdea -- implements IDEA cipher Inspired by idea.[ch] in
 * ssh-1.2.22 (hence related variable and function names), but
 * implemented by Jon Howell. Comments that relate to the algorithm are
 * also verbatim from the C source.
 *
 * @todo reimplement IDEA from some public document, like a paper 
 * @rcs $Id: CipherIdea.java,v 1.2 2000/05/22 01:35:49 jonh Exp $
 */
public class CipherIdea
	extends Cipher {

	short keySchedule[];

	public CipherIdea() {
		keySchedule = new short[52];
	}

	public void setKey(byte[] key) {
		int i;

		if (key.length<16) {
			throw new ArrayIndexOutOfBoundsException(
				"CipherIdea.setKey(): key too short");
		}

		// Keys for the first round are directly taken from
		// the user-supplied key
		for (i=0; i<8; i++) {
			keySchedule[i] = Endian.BigGetShort(key, 2*i);
		}

		// Each round uses the key of the previous key, rotated
		// to the left by 25 bits.  The last four keys (output
		// transform) are the first four keys from what would be
		// the ninth round.
		// jonh says: I have no idea what that means.
		int offset = 0;
		for (i=8; i<52; i++) {
			if ((i & 7) == 0) {
				offset += 8;
			}
			// java's >>> coerces its arguments to 'int' first,
			// so >>>'ing a short really doesn't do anything like
			// what you expect. In effect, when you cast it to short,
			// you have a signed shift again. Stupid Java.
			// So I explicitly turn things into ints, using a mask
			// so the short is treated as unsigned.
			int a = (keySchedule[offset+((i+1)&7)-8]) & 0xffff;
			int b = (keySchedule[offset+((i+2)&7)-8]) & 0xffff;
			keySchedule[offset+(i&7)] = (short) ((a<<9) | (b>>>7));
		}
	}

	public void destroyContext() {
		// no general way to do this in Java, is there?
		// [in C, you can memset(&struct, 0, sizeof(struct)); ]
		for (int i=0; i<52; i++) {
			keySchedule[i] = 0;
		}
	}


	// Performs the "multiplication" operation of IDEA: returns a*b
	// mod 65537, where a and b are first converted to 65536 if they
	// are zero, and result 65536 is converted to zero.  Both inputs
	// should be less than 65536.  Only the lower 16 bits of result
	// are significant; other bits are garbage.
	private int mulop(int a, short sb) {
		int b = sb & 0xffff;
		int ab = a*b;
		if (ab!=0) {
			int lo = ab & 0xffff;
			int hi = (ab>>>16) & 0xffff;
			return (lo-hi) + (lo<hi ? 1 : 0);
				// second term translates from the C (lo<hi) -- seems
				// nonportable. Does C actually specify true == 1, or
				// just true != 0?
		}
		if (a==0) {
			return 1 - b;
		}
		return 1 - a;
	}

	// Performs the IDEA cipher transform on a block of data.
	private void transform(int l, int r, int[] output) {
		int round;
		short[] keys = keySchedule;	// shorthand
		int offset;	// since we can't increment keys[] itself, as you can in C
		int t1, t2, x1, x2, x3, x4;

		offset = 0;
		x1 = l >>> 16;
		x2 = l;
		x3 = r >>> 16;
		x4 = r;
		t1 = 0;	// avoid "may not have been initialized" warning
				// (warning is false; compiler doesn't notice that
				// the for() loop must execute at least once.)
		for (round = 0; round < 8; round++) {
			x1 = mulop(x1 & 0xffff, keys[offset+0]);
			x3 = x3 + keys[offset+2];
			x4 = mulop(x4 & 0xffff, keys[offset+3]);
			x2 = x2 + keys[offset+1];
			t1 = x1 ^ x3;
			t2 = x2 ^ x4;
			t1 = mulop(t1 & 0xffff, keys[offset+4]);
			t2 = t1 + t2;
			t2 = mulop(t2 & 0xffff, keys[offset+5]);
			t1 = t1 + t2;
			x1 = x1 ^ t2;
			x4 = x4 ^ t1;
			t1 = t1 ^ x2;
			x2 = t2 ^ x3;
			x3 = t1;
			offset += 6;
		}
		x1 = mulop(x1 & 0xffff, keys[offset+0]);
		x3 = (x2 + keys[offset+2]) & 0xffff;
		x2 = t1 + keys[offset+1]; /* t1 == old x3 */
		x4 = mulop(x4 & 0xffff, keys[offset+3]);
		output[0] = (x1 << 16) | (x2 & 0xffff);
		output[1] = (x3 << 16) | (x4 & 0xffff);
	}

	// Encrypts len bytes from src to dest in CFB mode.  Len need
	// not be a multiple of 8; if it is not, inputVector at return will contain
	// garbage.  Otherwise, inputVector will be modified at end to a value
	// suitable for continuing encryption.
	public void encipher(byte[] dest, byte[] src, int len) {
		int iv0, iv1;
		int	out[] = new int[2];
		int i;

		iv0 = Endian.BigGetInt(inputVector, 0);
		iv1 = Endian.BigGetInt(inputVector, 4);

		for (i=0; i<len; i+=8) {
			transform(iv0, iv1, out);
			iv0 = out[0] ^ Endian.BigGetInt(src, i);
			iv1 = out[1] ^ Endian.BigGetInt(src, i+4);
			if (i+8 <= len) {
				Endian.BigPutInt(dest, i,	iv0);
				Endian.BigPutInt(dest, i+4, iv1);
			} else {
				switch (len-i) {
					// each case falls through to the one below
					case 7:		dest[i+6] = (byte) ((iv1 >>>  8) & 0x0ff);
					case 6:		dest[i+5] = (byte) ((iv1 >>> 16) & 0x0ff);
					case 5:		dest[i+4] = (byte) ((iv1 >>> 24) & 0x0ff);
					case 4:		dest[i+3] = (byte) ((iv0       ) & 0x0ff);
					case 3:		dest[i+2] = (byte) ((iv0 >>>  8) & 0x0ff);
					case 2:		dest[i+1] = (byte) ((iv0 >>> 16) & 0x0ff);
					case 1:		dest[i+0] = (byte) ((iv0 >>> 24) & 0x0ff);
				}
			}
		}

		// stuff input vectors back into argument array
		Endian.BigPutInt(inputVector, 0, iv0);
		Endian.BigPutInt(inputVector, 4, iv1);
	}

	// Decrypts len bytes from src to dest in CFB mode.  Len need
	// not be a multiple of 8; if it is not, inputVector at return will contain
	// garbage.  Otherwise, inputVector will be modified at end to a value
	// suitable for continuing decryption.
	public void decipher(byte[] dest, byte[] src, int len) {
		int iv0, iv1;
		int plain0, plain1;
		int	out[] = new int[2];
		int i;

		// System.out.println("packet to decrypt: "+Arrays.dumpBytes(src));

		iv0 = Endian.BigGetInt(inputVector, 0);
		iv1 = Endian.BigGetInt(inputVector, 4);

		for (i=0; i<len; i+=8) {
			transform(iv0, iv1, out);
			iv0 = Endian.BigGetInt(src, i);
			iv1 = Endian.BigGetInt(src, i+4);
			plain0 = out[0] ^ iv0;
			plain1 = out[1] ^ iv1;

			if (i+8 <= len) {
				Endian.BigPutInt(dest, i,	plain0);
				Endian.BigPutInt(dest, i+4,	plain1);
			} else {
				switch (len-i) {
					// each case falls through to the one below
					case 7:		dest[i+6] = (byte) ((plain1 >>>  8) & 0x0ff);
					case 6:		dest[i+5] = (byte) ((plain1 >>> 16) & 0x0ff);
					case 5:		dest[i+4] = (byte) ((plain1 >>> 24) & 0x0ff);
					case 4:		dest[i+3] = (byte) ((plain0       ) & 0x0ff);
					case 3:		dest[i+2] = (byte) ((plain0 >>>  8) & 0x0ff);
					case 2:		dest[i+1] = (byte) ((plain0 >>> 16) & 0x0ff);
					case 1:		dest[i+0] = (byte) ((plain0 >>> 24) & 0x0ff);
				}
			}
		}

		// stuff input vectors back into argument array
		Endian.BigPutInt(inputVector, 0, iv0);
		Endian.BigPutInt(inputVector, 4, iv1);

		// System.out.println("decrypted packet:  "+Arrays.dumpBytes(dest));
	}
}
