package org.rzo.netty.ahessian.crypto;

import java.util.Arrays;

/**
 * Taken from the cryptix project This class implements the RC4 (TM) stream
 * cipher.
 * <p>
 * The source code (C version) from which this port was done, is the one posted
 * to the sci.crypt, alt.security, comp.security.misc, and alt.privacy
 * newsgroups on Wed, 14 Sep 1994 06:35:31 GMT by "David Sterndark"
 * &lt;sterndark@netcom.com&gt; (Message-ID: &lt;sternCvKL4B.Hyy@netcom.com&gt;)
 * <p>
 * RC4 (TM) was designed by Ron Rivest, and was previously a trade secret of RSA
 * Data Security, Inc. The algorithm is now in the public domain. The name "RC4"
 * is a trademark of RSA Data Security, Inc.
 * <p>
 * <b>References:</b>
 * <ol>
 * <li>Bruce Schneier, "Section 17.1 RC4," <cite>Applied Cryptography, 2nd
 * edition</cite>, John Wiley &amp; Sons, 1996.
 * </ol>
 * <p>
 * <b>Copyright</b> &copy; 1997 <a href="http://www.systemics.com/">Systemics
 * Ltd</a> on behalf of the <a
 * href="http://www.systemics.com/docs/cryptix/">Cryptix Development Team</a>. <br>
 * All rights reserved.
 * <p>
 * <b>$Revision: 1.6 $</b>
 * 
 * @author Raif S. Naffah
 * @author David Hopwood
 * @since Cryptix 2.2.2
 */

public class RC4Cipher implements StreamCipher
{
	// RC4 constants and variables
	// ............................................................................

	/**
	 * The state of the cipher object when it is uninitialized, that is, the
	 * state it is in right after it has been created.
	 */
	public static final int UNINITIALIZED = 0;

	/**
	 * The state of the cipher when it is ready to encrypt, that is, the state
	 * it is in right after a call to <code>initEncrypt</code>.
	 * 
	 * @see #initEncrypt
	 */
	public static final int ENCRYPT = 1;

	/**
	 * The state of the cipher when it is ready to decrypt, that is, the state
	 * it is in right after a call to <code>initDecrypt</code>.
	 * 
	 * @see #initDecrypt
	 */
	public static final int DECRYPT = 2;

	/**
	 * Will hold the contents of the current set S-box.
	 */
	private int[] sBox = new int[256];

	/**
	 * The two indices for the S-box computation referred to as i and j in
	 * Schneier.
	 */
	private int x, y;

	/**
	 * The block size of this cipher. Being a stream cipher this value is 1!
	 */
	private static final int BLOCK_SIZE = 1;

	private int state; // defaults to UNINITIALIZED = 0
	private String cipherName = "RC4";

	// Constructor, finalizer, and clone()
	// ............................................................................

	/**
	 * Constructs an RC4 cipher object, in the UNINITIALIZED state. This calls
	 * the Cipher constructor with <i>implBuffering</i> false,
	 * <i>implPadding</i> false and the provider set to "Cryptix".
	 */
	public RC4Cipher()
	{
		// super(false, false, "Cryptix");
	}

	/**
	 * Always throws a CloneNotSupportedException (cloning of ciphers is not
	 * supported for security reasons).
	 */
	public final Object clone() throws CloneNotSupportedException
	{
		throw new CloneNotSupportedException();
	}

	// Implementation of JCE methods
	// ............................................................................

	/**
	 * <b>SPI</b>: Returns the length of an input block, in bytes.
	 * 
	 * @return the length in bytes of an input block for this cipher.
	 */
	public int engineBlockSize()
	{
		return BLOCK_SIZE;
	}

	/**
	 * <b>SPI</b>: Initializes this cipher for encryption, using the specified
	 * key.
	 * 
	 * @param key
	 *            the key to use for encryption.
	 * @exception CryptoException
	 *                if the key is invalid.
	 */
	public void engineInitEncrypt(byte[] key, byte[] iv) throws CryptoException
	{
		makeKey(key);
		state = ENCRYPT;
	}

	/**
	 * <b>SPI</b>: Initializes this cipher for decryption, using the specified
	 * key.
	 * 
	 * @param key
	 *            the key to use for decryption.
	 * @exception CryptoException
	 *                if the key is invalid.
	 */
	public void engineInitDecrypt(byte[] key, byte[] iv) throws CryptoException
	{
		makeKey(key);
		state = ENCRYPT;
	}

	/**
	 * <b>SPI</b>: This is the main engine method for updating data.
	 * <p>
	 * <i>in</i> and <i>out</i> may be the same array, and the input and output
	 * regions may overlap.
	 * 
	 * @param in
	 *            the input data.
	 * @param inOffset
	 *            the offset into in specifying where the data starts.
	 * @param inLen
	 *            the length of the subarray.
	 * @param out
	 *            the output array.
	 * @param outOffset
	 *            the offset indicating where to start writing into the out
	 *            array.
	 * @return the number of bytes written. reports an error.
	 */
	protected int engineUpdate(byte[] in, int inOffset, int inLen, byte[] out,
			int outOffset)
	{
		if (inLen < 0)
			throw new IllegalArgumentException("inLen < 0");

		boolean doEncrypt = (getState() == ENCRYPT);

		// Avoid overlapping input and output regions.
		if (in == out
				&& (outOffset >= inOffset && outOffset < inOffset + inLen || inOffset >= outOffset
						&& inOffset < outOffset + inLen))
		{
			byte[] newin = new byte[inLen];
			System.arraycopy(in, inOffset, newin, 0, inLen);
			in = newin;
			inOffset = 0;
		}

		rc4(in, inOffset, inLen, out, outOffset);

		return inLen;
	}

	// Own methods
	// ............................................................................

	/**
	 * RC4 encryption/decryption. The input and output regions are assumed not
	 * to overlap.
	 * 
	 * @param in
	 *            the input data.
	 * @param inOffset
	 *            the offset into in specifying where the data starts.
	 * @param inLen
	 *            the length of the subarray.
	 * @param out
	 *            the output array.
	 * @param outOffset
	 *            the offset indicating where to start writing into the out
	 *            array.
	 */
	private void rc4(byte[] in, int inOffset, int inLen, byte[] out,
			int outOffset)
	{
		int xorIndex, t;

		for (int i = 0; i < inLen; i++)
		{
			x = (x + 1) & 0xFF;
			y = (sBox[x] + y) & 0xFF;

			t = sBox[x];
			sBox[x] = sBox[y];
			sBox[y] = t;

			xorIndex = (sBox[x] + sBox[y]) & 0xFF;
			out[outOffset++] = (byte) (in[inOffset++] ^ sBox[xorIndex]);
		}
	}

	/**
	 * Expands a user-key to a working key schedule.
	 * <p>
	 * The key bytes are first extracted from the user-key and then used to
	 * build the contents of this key schedule.
	 * <p>
	 * The method's only exceptions are when the user-key's contents are null,
	 * or a byte array of zero length.
	 * 
	 * @param key
	 *            the user-key object to use.
	 * @exception CryptoException
	 *                if one of the following occurs:
	 *                <ul>
	 *                <li> key.getEncoded() == null; <li> The encoded byte array
	 *                form of the key is zero-length;
	 *                </ul>
	 */
	private void makeKey(byte[] userkey) throws CryptoException
	{

		if (userkey == null)
			throw new CryptoException(getAlgorithm() + ": Null user key");

		int len = userkey.length;
		if (len == 0)
			throw new CryptoException(getAlgorithm()
					+ ": Invalid user key length");

		x = y = 0;
		for (int i = 0; i < 256; i++)
			sBox[i] = i;

		int i1 = 0, i2 = 0, t;

		for (int i = 0; i < 256; i++)
		{
			i2 = ((userkey[i1] & 0xFF) + sBox[i] + i2) & 0xFF;

			t = sBox[i];
			sBox[i] = sBox[i2];
			sBox[i2] = t;

			i1 = (i1 + 1) % len;
		}
	}

	/**
	 * Returns this algorithm's standard cipher name (<em>not</em> including
	 * mode and padding).
	 * <p>
	 * See <a href="../guide/ijce/Algorithms.html#Cipher"> <cite>International
	 * JCE Standard Algorithm Names</cite></a> for a list of Cipher algorithm
	 * names.
	 * 
	 * @return the standard cipher name (such as "DES").
	 */
	public final String getAlgorithm()
	{
		return cipherName;
	}

	/**
	 * Returns the state of this Cipher object. Possible states are:
	 * <p>
	 * <dl>
	 * <dt>UNINITIALIZED
	 * <dd>The cipher has not been initialized.
	 * <dt>ENCRYPT
	 * <dd>The cipher has been initialized for encryption. It may be used for
	 * encryption only.
	 * <dt>DECRYPT
	 * <dd>The cipher has been initialized for decryption. It may be used for
	 * decryption only.
	 * </dl>
	 * 
	 * @return the state of this cipher object.
	 * 
	 * @see #UNINITIALIZED
	 * @see #ENCRYPT
	 * @see #DECRYPT
	 */
	public final int getState()
	{
		return state;
	}

	public final byte[] crypt(byte[] data, int position, int length)
	{
		byte[] buffer = new byte[length];
		engineUpdate(data, position, length, buffer, 0);
		return buffer;
	}

	public final byte[] crypt(byte[] data)
	{
		byte[] buffer = new byte[data.length];
		engineUpdate(data, 0, data.length, buffer, 0);
		return buffer;
	}

	public final void crypt(byte[] in, int in_offset, int length, byte[] out,
			int out_offset)
	{
		engineUpdate(in, in_offset, length, out, out_offset);
	}

	public static void main(String[] args) throws Exception
	{
		byte[] iv = new byte[CryptoConstants.SYM_IV_SIZE];
		byte[] key = new byte[CryptoConstants.SYM_KEY_SIZE];
		StreamCipher eCipher = StreamCipherFactory.createCipher("RC4");
		StreamCipher dCipher = StreamCipherFactory.createCipher("RC4");
		eCipher.engineInitEncrypt(key, iv);
		dCipher.engineInitDecrypt(key, iv);
		byte[] msg = "this is a secret message".getBytes();
		byte[] cMsg;
		byte[] eMsg = new byte[0];
		long t = System.currentTimeMillis();
		for (int i = 0; i < 1000000; i++)
		{
			cMsg = eCipher.crypt(msg, 0, msg.length);
			eMsg = dCipher.crypt(cMsg, 0, cMsg.length);
		}
		System.out.println(System.currentTimeMillis() - t);
		if (!Arrays.equals(msg, eMsg))
			System.out.println("error");

	}

}
