//////////////////////////////////////////////////////////////////////////// 
// 
// Copyright (C) DSTC Pty Ltd (ACN 052 372 577) 1993, 1994, 1995.
// Unpublished work.  All Rights Reserved.
// 
// The software contained on this media is the property of the
// DSTC Pty Ltd.  Use of this software is strictly in accordance
// with the license agreement in the accompanying LICENSE.DOC 
// file. If your distribution of this software does not contain 
// a LICENSE.DOC file then you have no rights to use this 
// software in any manner and should contact DSTC at the address 
// below to determine an appropriate licensing arrangement.
// 
//      DSTC Pty Ltd
//      Level 7, GP South
//      University of Queensland
//      St Lucia, 4072
//      Australia
//      Tel: +61 7 3365 4310
//      Fax: +61 7 3365 4311
//      Email: jcsi@dstc.qut.edu.au
// 
// This software is being provided "AS IS" without warranty of
// any kind.  In no event shall DSTC Pty Ltd be liable for
// damage of any kind arising out of or in connection with
// the use or performance of this software.
// 
//////////////////////////////////////////////////////////////////////////// 

package com.dstc.security.provider;

import java.security.Key;
import java.security.SecureRandom;
import java.security.AlgorithmParameters;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.InvalidAlgorithmParameterException;
import java.security.spec.AlgorithmParameterSpec;
import javax.crypto.Cipher;
import javax.crypto.CipherSpi;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.ShortBufferException;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;

/**
 * <p>Implements the RC4 stream cipher algorithm from RSA.
 *
 * @see javax.crypto.Cipher
 * @see com.dstc.security.provider.RC2SecretKey
 *
 * @version 0.98, 98/07/01
 * @author Ming Yung
 */
public final class RC4 extends CipherSpi 
{
  private short S[];
  private short x, y;

  public static void main(String args[])
  {
try
{
    System.out.println(RC4.modulo(-93,256));
}
catch (Exception e)
{
e.printStackTrace();
}
  }

  public RC4 () 
  {
    S = new short[256];
  }

  public void engineSetPadding(String padding) throws NoSuchPaddingException 
  {
  }

  public void engineSetMode(String mode) throws NoSuchAlgorithmException 
  {
  }

  protected int engineGetBlockSize() 
  {
    //Not a block cipher
    return 0;
  }

  protected int engineGetOutputSize(int inputLen)
  {
    return inputLen;
  }

  protected byte[] engineGetIV() 
  {
    return null;
  }

  protected AlgorithmParameters engineGetParameters()
  {
    return null;
  }

  protected void engineInit(int opmode, Key key, SecureRandom random) 
    throws InvalidKeyException 
  {
    if (!(key instanceof RC4SecretKey)) 
    {
      throw new InvalidKeyException ("Not an RC4 Key");
    }

    byte keyBytes[] = key.getEncoded();
    int keyLen = keyBytes.length;

    for (short i = 0; i < 256; i++) 
    {
      S[i] = i;
    }

    short j = 0;
    short k = 0;
    for (short i = 0; i < 256; i++) 
    {
      j = (short) modulo (keyBytes[k] + S[i] + j, 256);
      swapByte (i, j);
      k = (short) modulo (k + 1, keyLen);
    }

    this.x = 0;
    this.y = 0;
  } 

  protected void engineInit(int opmode, Key key, AlgorithmParameterSpec params,
                            SecureRandom random) 
    throws InvalidKeyException, InvalidAlgorithmParameterException 
  {
    engineInit(opmode, key, random);
  }


  protected byte[] engineUpdate(byte input[], int inputOffset, int inputLen) 
  {  
    return encrypt(input, inputOffset, inputLen);
  }

  protected int engineUpdate(byte input[], int inputOffset, int inputLen,
                             byte output[], int outputOffset) 
    throws ShortBufferException 
  {
    //if (output.length - outputOffset < engineGetOutputSize (inputLen)) 
    //{
    //  throw new ShortBufferException("Output array too short");
    //}

    byte out[] = encrypt(input, inputOffset, inputLen);
    System.arraycopy(out, 0, output, outputOffset, out.length);
    return out.length;
  }

  protected byte[] engineDoFinal(byte input[], int inputOffset, int inputLen) 
    throws IllegalBlockSizeException, BadPaddingException 
  {
    return encrypt(input, inputOffset, inputLen);
  }

  protected int engineDoFinal(byte input[], int inputOffset, int inputLen,
                              byte output[], int outputOffset) 
    throws ShortBufferException, 
           IllegalBlockSizeException, BadPaddingException 
  {
    //if (output.length - outputOffset < engineGetOutputSize(inputLen)) 
    //{
    //  throw new ShortBufferException("Output array too short");
    //}

    byte out[] = encrypt(input, inputOffset, inputLen);
    System.arraycopy(out, 0, output, outputOffset, out.length);
    return out.length;
  }

  private final byte[] encrypt(byte input[], int inputOffset, int inputLen) 
  {
    byte retval[] = new byte[inputLen];
    for (int i=0; i < inputLen; i++) 
    {
      x = (short)modulo(x + 1, 256);
      y = (short)modulo(y + S[x], 256);
      swapByte(x, y);
      retval[i] = (byte)(input[i + inputOffset] ^ S[modulo(S[x] + S[y], 256)]);
    }
    return retval;
  }
    
  private final void swapByte(short i, short j) 
  {
    short temp = S[i];
    S[i] = S[j];
    S[j] = temp;
  }

  private static final int modulo(int a, int b) 
  {
    int retval = (a % b);
    if (retval < 0)
      retval += b;
    return retval;
    //return ((a % b) + ((a < 0) ? b : 0))//;
  }
}
