//////////////////////////////////////////////////////////////////////////// 
// 
// 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.*;

/**
 * <p>Implements the MD5 Message Digest algorithm from RSA.
 *
 * @see java.security.MessageDigest
 *
 * @version 0.98, 98/07/01
 * @author Ming Yung
 */
public final class MD5 extends MessageDigestSpi implements Cloneable
{
  private static int blockSize = 64; //in bytes
  private byte inputBuffer[];
  private int inputBufferOffset;
  private int mdBuffer[];
  private int plainTextLength;

  public MD5() 
  {
    //Initialize
    inputBuffer = new byte[blockSize];
    inputBufferOffset = 0;
    plainTextLength = 0;
    mdBuffer = new int[4];
    mdBuffer[0] = 0x67452301;
    mdBuffer[1] = 0xefcdab89;
    mdBuffer[2] = 0x98badcfe;
    mdBuffer[3] = 0x10325476;
  }

  protected byte[] engineDigest() 
  {
    int temp =  56 - (plainTextLength & 0x3f);
    int extra = temp > 0 ? temp : 64 + temp;

    //NB: There are either one or two blocks to encrypt

    //Step 1: Add padding
    inputBuffer[inputBufferOffset] = (byte)0x80;
    int start = 1;

    if (inputBufferOffset + extra > blockSize) 
    {
      //Two blocks to encrypt: Do first block
      start = blockSize - inputBufferOffset;
      for (int i=1; i<start; i++) 
      {
        inputBuffer[inputBufferOffset + i] = 0x00;
      }
      doDecodeBlock(mdBuffer, bytesToInts (inputBuffer, 0, blockSize));
      inputBufferOffset = -start;
    }
      
    for (int i=start; i<extra; i++) 
    {
      inputBuffer[inputBufferOffset + i] = 0x00;
    }
    inputBufferOffset += extra;

    //Step 2: Append length in bits
    long lb = (long)(plainTextLength*8);
    for (int i=0; i<8; i++) 
    {
      inputBuffer[inputBufferOffset+i] = (byte)((lb >>> 8*i) & 0xff);
    }

    //Last block to doDecode
    doDecodeBlock(mdBuffer, bytesToInts(inputBuffer, 0, blockSize));
    
    byte retval[] = intsToBytes(mdBuffer);
    doReset();
    return retval;
  }

  protected void engineReset() 
  {
    doReset();
  }

  private final void doReset() 
  {
    inputBufferOffset = 0;
    plainTextLength = 0;
    mdBuffer[0] = 0x67452301;
    mdBuffer[1] = 0xefcdab89;
    mdBuffer[2] = 0x98badcfe;
    mdBuffer[3] = 0x10325476;
  }

  protected void engineUpdate(byte input)
  {
    byte in[] = new byte[1];
    in[0] = input;
    engineUpdate(in, 0, 1);
  }

  protected void engineUpdate(byte input[], int inputOffset, int length) 
  {
    int len;
    int left = length;
    int inputUsed = 0;
    while (true) 
    {
      len = blockSize - inputBufferOffset;
      if (len <= left) 
      {
        //Enough input for block
        System.arraycopy(input, inputUsed + inputOffset, inputBuffer,
                          inputBufferOffset, len);
        doDecodeBlock(mdBuffer, bytesToInts(inputBuffer, 0, blockSize));
        left -= len;
        inputBufferOffset = 0;
        inputUsed += len;
      } 
      else 
      {
        //Buffer the remaining input
        System.arraycopy(input, inputUsed + inputOffset, inputBuffer,
                         inputBufferOffset, left);
        inputBufferOffset += left;
        break;
      }
    }
    plainTextLength += length;
  }

  public Object clone() throws CloneNotSupportedException
  {
    MD5 retval = new MD5();
    retval.inputBufferOffset = this.inputBufferOffset;
    retval.plainTextLength = this.plainTextLength;
    System.arraycopy(this.mdBuffer, 0, retval.mdBuffer, 0, 4);
    System.arraycopy(this.inputBuffer, 0, retval.inputBuffer, 0, blockSize);
    return retval;
  }

  private static void FF(int buf[], int x[], int a, int b, int c, 
                         int d, int k, int s, int i) 
  {
     buf[a] += ((buf[b] & buf[c]) | ((~buf[b]) & buf[d])) + x[k] + i;
     buf[a] = (buf[a] << s) | (buf[a] >>> (32-s));
     buf[a] += buf[b];
  }

  private static void GG(int buf[], int x[], int a, int b, int c, 
                         int d, int k, int s, int i) 
  {
     buf[a] += ((buf[b] & buf[d]) | (buf[c] & (~buf[d]))) + x[k] + i;
     buf[a] = (buf[a] << s) | (buf[a] >>> (32-s));
     buf[a] += buf[b];
  }

  private static void HH(int buf[], int x[], int a, int b, int c, 
                         int d, int k, int s, int i) 
  {
     buf[a] += (buf[b] ^ buf[c] ^ buf[d]) + x[k] + i;
     buf[a] = (buf[a] << s) | (buf[a] >>> (32-s));
     buf[a] += buf[b];
  }
    
  private static void II(int buf[], int x[], int a, int b, int c, 
                         int d, int k, int s, int i) 
  {
     buf[a] += (buf[c] ^ (buf[b] | (~buf[d]))) + x[k] + i;
     buf[a] = (buf[a] << s) | (buf[a] >>> (32-s));
     buf[a] += buf[b];
  }
    
  private static void doDecodeBlock(int inout[], int block[]) 
  {
    int temp[] = new int[4];
    System.arraycopy(inout, 0, temp, 0, 4);

    //Round 1
    FF(inout, block, 0, 1, 2, 3, 0, 7, 0xd76aa478);
    FF(inout, block, 3, 0, 1, 2, 1, 12, 0xe8c7b756);
    FF(inout, block, 2, 3, 0, 1, 2, 17, 0x242070db);
    FF(inout, block, 1, 2, 3, 0, 3, 22, 0xc1bdceee);

    FF(inout, block, 0, 1, 2, 3, 4, 7, 0xf57c0faf);
    FF(inout, block, 3, 0, 1, 2, 5, 12, 0x4787c62a);
    FF(inout, block, 2, 3, 0, 1, 6, 17, 0xa8304613);
    FF(inout, block, 1, 2, 3, 0, 7, 22, 0xfd469501);

    FF(inout, block, 0, 1, 2, 3, 8, 7, 0x698098d8);
    FF(inout, block, 3, 0, 1, 2, 9, 12, 0x8b44f7af);
    FF(inout, block, 2, 3, 0, 1, 10, 17, 0xffff5bb1);
    FF(inout, block, 1, 2, 3, 0, 11, 22, 0x895cd7be);

    FF(inout, block, 0, 1, 2, 3, 12, 7, 0x6b901122);
    FF(inout, block, 3, 0, 1, 2, 13, 12, 0xfd987193);
    FF(inout, block, 2, 3, 0, 1, 14, 17, 0xa679438e);
    FF(inout, block, 1, 2, 3, 0, 15, 22, 0x49b40821);

    //Round 2
    GG(inout, block, 0, 1, 2, 3, 1, 5, 0xf61e2562);
    GG(inout, block, 3, 0, 1, 2, 6, 9, 0xc040b340);
    GG(inout, block, 2, 3, 0, 1, 11, 14, 0x265e5a51);
    GG(inout, block, 1, 2, 3, 0, 0, 20, 0xe9b6c7aa);

    GG(inout, block, 0, 1, 2, 3, 5, 5, 0xd62f105d);
    GG(inout, block, 3, 0, 1, 2, 10, 9, 0x2441453);
    GG(inout, block, 2, 3, 0, 1, 15, 14, 0xd8a1e681);
    GG(inout, block, 1, 2, 3, 0, 4, 20, 0xe7d3fbc8);

    GG(inout, block, 0, 1, 2, 3, 9, 5, 0x21e1cde6);
    GG(inout, block, 3, 0, 1, 2, 14, 9, 0xc33707d6);
    GG(inout, block, 2, 3, 0, 1, 3, 14, 0xf4d50d87);
    GG(inout, block, 1, 2, 3, 0, 8, 20, 0x455a14ed);

    GG(inout, block, 0, 1, 2, 3, 13, 5, 0xa9e3e905);
    GG(inout, block, 3, 0, 1, 2, 2, 9, 0xfcefa3f8);
    GG(inout, block, 2, 3, 0, 1, 7, 14, 0x676f02d9);
    GG(inout, block, 1, 2, 3, 0, 12, 20, 0x8d2a4c8a);

    //Round 3
    HH(inout, block, 0, 1, 2, 3, 5, 4, 0xfffa3942);
    HH(inout, block, 3, 0, 1, 2, 8, 11, 0x8771f681);
    HH(inout, block, 2, 3, 0, 1, 11, 16, 0x6d9d6122);
    HH(inout, block, 1, 2, 3, 0, 14, 23, 0xfde5380c);

    HH(inout, block, 0, 1, 2, 3, 1, 4, 0xa4beea44);
    HH(inout, block, 3, 0, 1, 2, 4, 11, 0x4bdecfa9);
    HH(inout, block, 2, 3, 0, 1, 7, 16, 0xf6bb4b60);
    HH(inout, block, 1, 2, 3, 0, 10, 23, 0xbebfbc70);

    HH(inout, block, 0, 1, 2, 3, 13, 4, 0x289b7ec6);
    HH(inout, block, 3, 0, 1, 2, 0, 11, 0xeaa127fa);
    HH(inout, block, 2, 3, 0, 1, 3, 16, 0xd4ef3085);
    HH(inout, block, 1, 2, 3, 0, 6, 23, 0x4881d05);

    HH(inout, block, 0, 1, 2, 3, 9, 4, 0xd9d4d039);
    HH(inout, block, 3, 0, 1, 2, 12, 11, 0xe6db99e5);
    HH(inout, block, 2, 3, 0, 1, 15, 16, 0x1fa27cf8);
    HH(inout, block, 1, 2, 3, 0, 2, 23, 0xc4ac5665);

    //Round 4
    II(inout, block, 0, 1, 2, 3, 0, 6, 0xf4292244);
    II(inout, block, 3, 0, 1, 2, 7, 10, 0x432aff97);
    II(inout, block, 2, 3, 0, 1, 14, 15, 0xab9423a7);
    II(inout, block, 1, 2, 3, 0, 5, 21, 0xfc93a039);

    II(inout, block, 0, 1, 2, 3, 12, 6, 0x655b59c3);
    II(inout, block, 3, 0, 1, 2, 3, 10, 0x8f0ccc92);
    II(inout, block, 2, 3, 0, 1, 10, 15, 0xffeff47d);
    II(inout, block, 1, 2, 3, 0, 1, 21, 0x85845dd1);

    II(inout, block, 0, 1, 2, 3, 8, 6, 0x6fa87e4f);
    II(inout, block, 3, 0, 1, 2, 15, 10, 0xfe2ce6e0);
    II(inout, block, 2, 3, 0, 1, 6, 15, 0xa3014314);
    II(inout, block, 1, 2, 3, 0, 13, 21, 0x4e0811a1);

    II(inout, block, 0, 1, 2, 3, 4, 6, 0xf7537e82);
    II(inout, block, 3, 0, 1, 2, 11, 10, 0xbd3af235);
    II(inout, block, 2, 3, 0, 1, 2, 15, 0x2ad7d2bb);
    II(inout, block, 1, 2, 3, 0, 9, 21, 0xeb86d391);

    for (int i=0; i<4; i++) 
    {
      inout[i] += temp[i];
    }
  }

  private static final byte[] intsToBytes(int ints[]) 
  {
    //Converts every int to 4 bytes
    byte bytes[] = new byte[4*ints.length];
    for (int j=0; j < bytes.length; j++) 
    {
      bytes[j] = (byte)((ints[j/4] >>> 8*(j%4)) & 0xff);
    }
    return bytes;
  }

  private static final int[] bytesToInts(byte bytes[], int offset, int length) 
  {
    //Convert every 4 bytes to an int
    int retval[] = new int[(length + 3)/4];
    for (int j=0; j < length; j++) 
    {
      retval[j/4] += (int) (bytes[j + offset] & 0xff) << 8*(j%4);
    }
    return retval;
  }
}
