package gnu.crypto.hash;

// ----------------------------------------------------------------------------
// $Id: MD5.java,v 1.2 2002/01/11 21:55:08 raif Exp $
//
// Copyright (C) 2001, 2002 Free Software Foundation, Inc.
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free
// Software Foundation; either version 2 of the License or (at your option) any
// later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along with
// this program; see the file COPYING.  If not, write to the
//
//    Free Software Foundation Inc.,
//    59 Temple Place - Suite 330,
//    Boston, MA 02111-1307
//    USA
//
// As a special exception, if you link this library with other files to produce
// an executable, this library does not by itself cause the resulting
// executable to be covered by the GNU General Public License.  This exception
// does not however invalidate any other reasons why the executable file might
// be covered by the GNU General Public License.
// ----------------------------------------------------------------------------

import gnu.crypto.Registry;
import gnu.crypto.util.Util;

import java.io.PrintWriter;

/**
 * The MD5 message-digest algorithm takes as input a message of arbitrary
 * length and produces as output a 128-bit "fingerprint" or "message digest" of
 * the input. It is conjectured that it is computationally infeasible to
 * produce two messages having the same message digest, or to produce any
 * message having a given prespecified target message digest.<p>
 *
 * References:<br>
 * The <a href="http://www.ietf.org/rfc/rfc1321.txt">MD5</a> Message-Digest
 * Algorithm.<br>
 * R. Rivest.<p>
 *
 * @version $Revision: 1.2 $
 */
public class MD5 extends BaseHash {

   // Debugging methods and variables
   // -------------------------------------------------------------------------

   private static final String NAME = "md5";
   private static final boolean DEBUG = false;
   private static final int debuglevel = 9;
   private static final PrintWriter err = new PrintWriter(System.out, true);
   private static void debug(String s) {
      err.println(">>> "+NAME+": "+s);
   }

   // Constants and variables
   // -------------------------------------------------------------------------

   private static final int BLOCK_SIZE = 64; // default block size in bytes

   /** 128-bit interim result. */
   private int h0, h1, h2, h3;

   private final int[] X = new int[16];

   private static final String DIGEST0 = "D41D8CD98F00B204E9800998ECF8427E";

   // Constructor(s)
   // -------------------------------------------------------------------------

   /** Trivial 0-arguments constructor. */
   public MD5() {
      super(Registry.MD5_HASH, 16, BLOCK_SIZE);

      resetContext();
   }

   /**
    * private constructor for cloning purposes.<p>
    *
    * @param md the instance to clone.
    */
   private MD5(MD5 md) {
      this();

      this.h0 = md.h0;
      this.h1 = md.h1;
      this.h2 = md.h2;
      this.h3 = md.h3;
      this.count = md.count;
      this.buffer = (byte[]) md.buffer.clone();
   }

   // Cloneable interface implementation
   // -------------------------------------------------------------------------

   /** @return a cloned copy of this instance. */
   public Object clone() {
      return new MD5(this);
   }

   // Implementation of concrete methods in BaseHash
   // -------------------------------------------------------------------------

   protected void transform(byte[] in, int offset) {
      for (int i = 0; i < 16; i++) {
         X[i] = (in[offset++] & 0xFF)       |
                (in[offset++] & 0xFF) <<  8 |
                (in[offset++] & 0xFF) << 16 |
                (in[offset++] & 0xFF) << 24;
      }

      int A = h0;
      int B = h1;
      int C = h2;
      int D = h3;

      // hex constants are from md5.c in FSF Gnu Privacy Guard 0.9.2
      // round 1
      A = FF(A, B, C, D,  X[0],  7, 0xD76AA478);
      D = FF(D, A, B, C,  X[1], 12, 0xE8C7B756);
      C = FF(C, D, A, B,  X[2], 17, 0x242070DB);
      B = FF(B, C, D, A,  X[3], 22, 0xC1BDCEEE);

      A = FF(A, B, C, D,  X[4],  7, 0xF57C0FAF);
      D = FF(D, A, B, C,  X[5], 12, 0x4787C62A);
      C = FF(C, D, A, B,  X[6], 17, 0xA8304613);
      B = FF(B, C, D, A,  X[7], 22, 0xFD469501);

      A = FF(A, B, C, D,  X[8],  7, 0x698098D8);
      D = FF(D, A, B, C,  X[9], 12, 0x8B44F7AF);
      C = FF(C, D, A, B, X[10], 17, 0xFFFF5BB1);
      B = FF(B, C, D, A, X[11], 22, 0x895CD7BE);

      A = FF(A, B, C, D, X[12],  7, 0x6B901122);
      D = FF(D, A, B, C, X[13], 12, 0xFD987193);
      C = FF(C, D, A, B, X[14], 17, 0xA679438E);
      B = FF(B, C, D, A, X[15], 22, 0x49B40821);

      // round 2
      A = GG(A, B, C, D,  X[1],  5, 0xF61E2562);
      D = GG(D, A, B, C,  X[6],  9, 0xC040B340);
      C = GG(C, D, A, B, X[11], 14, 0x265E5A51);
      B = GG(B, C, D, A,  X[0], 20, 0xE9B6C7AA);

      A = GG(A, B, C, D,  X[5],  5, 0xD62F105D);
      D = GG(D, A, B, C, X[10],  9, 0x02441453);
      C = GG(C, D, A, B, X[15], 14, 0xD8A1E681);
      B = GG(B, C, D, A,  X[4], 20, 0xE7D3FBC8);

      A = GG(A, B, C, D,  X[9],  5, 0x21E1CDE6);
      D = GG(D, A, B, C, X[14],  9, 0xC33707D6);
      C = GG(C, D, A, B,  X[3], 14, 0xF4D50D87);
      B = GG(B, C, D, A,  X[8], 20, 0x455A14ED);

      A = GG(A, B, C, D, X[13],  5, 0xA9E3E905);
      D = GG(D, A, B, C,  X[2],  9, 0xFCEFA3F8);
      C = GG(C, D, A, B,  X[7], 14, 0x676F02D9);
      B = GG(B, C, D, A, X[12], 20, 0x8D2A4C8A);

      // round 3
      A = HH(A, B, C, D,  X[5],  4, 0xFFFA3942);
      D = HH(D, A, B, C,  X[8], 11, 0x8771F681);
      C = HH(C, D, A, B, X[11], 16, 0x6D9D6122);
      B = HH(B, C, D, A, X[14], 23, 0xFDE5380C);

      A = HH(A, B, C, D,  X[1],  4, 0xA4BEEA44);
      D = HH(D, A, B, C,  X[4], 11, 0x4BDECFA9);
      C = HH(C, D, A, B,  X[7], 16, 0xF6BB4B60);
      B = HH(B, C, D, A, X[10], 23, 0xBEBFBC70);

      A = HH(A, B, C, D, X[13],  4, 0x289B7EC6);
      D = HH(D, A, B, C,  X[0], 11, 0xEAA127FA);
      C = HH(C, D, A, B,  X[3], 16, 0xD4EF3085);
      B = HH(B, C, D, A,  X[6], 23, 0x04881D05);

      A = HH(A, B, C, D,  X[9],  4, 0xD9D4D039);
      D = HH(D, A, B, C, X[12], 11, 0xE6DB99E5);
      C = HH(C, D, A, B, X[15], 16, 0x1FA27CF8);
      B = HH(B, C, D, A,  X[2], 23, 0xC4AC5665);

      // round 4
      A = II(A, B, C, D,  X[0],  6, 0xF4292244);
      D = II(D, A, B, C,  X[7], 10, 0x432AFF97);
      C = II(C, D, A, B, X[14], 15, 0xAB9423A7);
      B = II(B, C, D, A,  X[5], 21, 0xFC93A039);

      A = II(A, B, C, D, X[12],  6, 0x655B59C3);
      D = II(D, A, B, C,  X[3], 10, 0x8F0CCC92);
      C = II(C, D, A, B, X[10], 15, 0xFFEFF47D);
      B = II(B, C, D, A,  X[1], 21, 0x85845dd1);

      A = II(A, B, C, D,  X[8],  6, 0x6FA87E4F);
      D = II(D, A, B, C, X[15], 10, 0xFE2CE6E0);
      C = II(C, D, A, B,  X[6], 15, 0xA3014314);
      B = II(B, C, D, A, X[13], 21, 0x4E0811A1);

      A = II(A, B, C, D,  X[4],  6, 0xF7537E82);
      D = II(D, A, B, C, X[11], 10, 0xBD3AF235);
      C = II(C, D, A, B,  X[2], 15, 0x2AD7D2BB);
      B = II(B, C, D, A,  X[9], 21, 0xEB86D391);

      h0 += A;
      h1 += B;
      h2 += C;
      h3 += D;
   }

   protected byte[] padBuffer() {
      int n = (int)(count % BLOCK_SIZE);
      int padding = (n < 56) ? (56 - n) : (120 - n);
      byte[] result = new byte[padding + 8];

      // padding is always binary 1 followed by binary 0s
      result[0] = (byte) 0x80;

      // save number of bits, casting the long to an array of 8 bytes
      long bits = count << 3;
      result[padding++] = (byte) bits;
      result[padding++] = (byte)(bits >>>  8);
      result[padding++] = (byte)(bits >>> 16);
      result[padding++] = (byte)(bits >>> 24);
      result[padding++] = (byte)(bits >>> 32);
      result[padding++] = (byte)(bits >>> 40);
      result[padding++] = (byte)(bits >>> 48);
      result[padding  ] = (byte)(bits >>> 56);

      return result;
   }

   protected byte[] getResult() {
      byte[] result = new byte[] {
         (byte) h0, (byte)(h0 >>> 8), (byte)(h0 >>> 16), (byte)(h0 >>> 24),
         (byte) h1, (byte)(h1 >>> 8), (byte)(h1 >>> 16), (byte)(h1 >>> 24),
         (byte) h2, (byte)(h2 >>> 8), (byte)(h2 >>> 16), (byte)(h2 >>> 24),
         (byte) h3, (byte)(h3 >>> 8), (byte)(h3 >>> 16), (byte)(h3 >>> 24)
      };

      return result;
   }

   protected void resetContext() {
      // magic MD5/RIPEMD128 initialisation constants
      h0 = 0x67452301;
      h1 = 0xEFCDAB89;
      h2 = 0x98BADCFE;
      h3 = 0x10325476;
   }

   public boolean selfTest() {
      return DIGEST0.equals(Util.toString(new MD5().digest()));
   }

   // helper methods
   // -------------------------------------------------------------------------

   private static final int FF(int a, int b, int c, int d, int k, int s, int i) {
      a += ((b & c) | (~b & d)) + k + i;
      return b + (a << s | a >>> -s);
   }

   private static final int GG(int a, int b, int c, int d, int k, int s, int i) {
      a += ((b & d) | (c & ~d)) + k + i;
      return b + (a << s | a >>> -s);
   }

   private static final int HH(int a, int b, int c, int d, int k, int s, int i) {
      a += (b ^ c ^ d) + k + i;
      return b + (a << s | a >>> -s);
   }

   private static final int II(int a, int b, int c, int d, int k, int s, int i) {
      a += (c ^ (b | ~d)) + k + i;
      return b + (a << s | a >>> -s);
   }
}
