import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.Security;

import org.apache.commons.codec.binary.Hex;

public class CryptoHashTest {

    private static final int   INIT_A     = 0x67452301;
    private static final int   INIT_B     = (int) 0xEFCDAB89L;
    private static final int   INIT_C     = (int) 0x98BADCFEL;
    private static final int   INIT_D     = 0x10325476;

    private static final int[] SHIFT_AMTS = { 7, 12, 17, 22, 5, 9, 14, 20, 4,  11, 16, 23, 6, 10, 15, 21    };
    private static final int[] TABLE_T    = new int[64];

    static {
        for (int i = 0; i < 64; i++)
            TABLE_T[i] = (int) (long) ((1L << 32) * Math.abs(Math.sin(i + 1)));
    }
;
    public static byte[] computeMD5(byte[] message)  {
        int messageLenBytes = message.length;
        int numBlocks = ((messageLenBytes + 8) >>> 6) + 1;
        int totalLen = numBlocks << 6;
        byte[] paddingBytes = new byte[totalLen - messageLenBytes];
        paddingBytes[0] = (byte) 0x80;

        long messageLenBits = (long) messageLenBytes << 3;
        for (int i = 0; i < 8; i++) {
            paddingBytes[paddingBytes.length - 8 + i] = (byte) messageLenBits;
            messageLenBits >>>= 8;
        }

        int a = INIT_A;
        int b = INIT_B;
        int c = INIT_C;
        int d = INIT_D;

        int[] buffer = new int[16];
        for (int i = 0; i < numBlocks; i++) {
            int index = i << 6;
            for (int j = 0; j < 64; j++, index++)
                buffer[j >>> 2] = ((int) ((index < messageLenBytes) ? message[index]
                        : paddingBytes[index - messageLenBytes]) << 24)
                        | (buffer[j >>> 2] >>> 8);
            int originalA = a;
            int originalB = b;
            int originalC = c;
            int originalD = d;

            for (int j = 0; j < 64; j++) {
                int div16 = j >>> 4;
                int f = 0;
                int bufferIndex = j;
                switch (div16) {
                    case 0:
                        f = (b & c) | (~b & d);
                        break;
                    case 1:
                        f = (b & d) | (c & ~d);
                        bufferIndex = (bufferIndex * 5 + 1) & 0x0F;
                        break;
                    case 2:
                        f = b ^ c ^ d;
                        bufferIndex = (bufferIndex * 3 + 5) & 0x0F;
                        break;
                    case 3:
                        f = c ^ (b | ~d);
                        bufferIndex = (bufferIndex * 7) & 0x0F;
                        break;
                }
                int temp = b
                        + Integer.rotateLeft(a + f + buffer[bufferIndex]
                                + TABLE_T[j],
                                SHIFT_AMTS[(div16 << 2) | (j & 3)]);
                a = d;
                d = c;
                c = b;
                b = temp;
            }
            a += originalA;
            b += originalB;
            c += originalC;
            d += originalD;
        }

        byte[] md5 = new byte[16];
        int count = 0;
        for (int i = 0; i < 4; i++) {
            int n = (i == 0) ? a : ((i == 1) ? b : ((i == 2) ? c : d));
            for (int j = 0; j < 4; j++) {
                md5[count++] = (byte) n;
                n >>>= 8;
            }
        }
        return md5;
    }
	
	public static byte[] md5(byte[] input) {
		//Note: All variables are unsigned 32 bit and wrap modulo 2^32 when calculating
		//s specifies the per-round shift amounts
		int[] s = { 7, 12, 17, 22,  7, 12, 17, 22,  7, 12, 17, 22,  7, 12, 17, 22 ,
					5,  9, 14, 20,  5,  9, 14, 20,  5,  9, 14, 20,  5,  9, 14, 20 ,
					4, 11, 16, 23,  4, 11, 16, 23,  4, 11, 16, 23,  4, 11, 16, 23 ,
					6, 10, 15, 21,  6, 10, 15, 21,  6, 10, 15, 21,  6, 10, 15, 21 };

		//Use binary integer part of the sines of integers (Radians) as constants:
		int[] K = new int[64];
		for (int i=0; i<64; i++) {
		    K[i] = (int) Math.floor(232*Math.abs(Math.sin(i + 1d)));
		}

		//Initialize variables:
		int a0 = 0x67452301;   //A
		int b0 = 0xefcdab89;   //B
		int c0 = 0x98badcfe;   //C
		int d0 = 0x10325476;   //D

		//Pre-processing: adding a single 1 bit
		append "1" bit to message    
		// Notice: the input bytes are considered as bits strings,
		//  where the first bit is the most significant bit of the byte.[49]

		//Pre-processing: padding with zeros
		append "0" bit until message length in bits ≡ 448 (mod 512)
		append original length in bits mod 264 to message

		//Process the message in successive 512-bit chunks:
		for each 512-bit chunk of padded message
		    break chunk into sixteen 32-bit words M[j], 0 ≤ j ≤ 15
		    //Initialize hash value for this chunk:
		    var int A := a0
		    var int B := b0
		    var int C := c0
		    var int D := d0
		    //Main loop:
		    for i from 0 to 63
		        var int F, g
		        if 0 ≤ i ≤ 15 then
		            F := (B and C) or ((not B) and D)
		            g := i
		        else if 16 ≤ i ≤ 31 then
		            F := (D and B) or ((not D) and C)
		            g := (5×i + 1) mod 16
		        else if 32 ≤ i ≤ 47 then
		            F := B xor C xor D
		            g := (3×i + 5) mod 16
		        else if 48 ≤ i ≤ 63 then
		            F := C xor (B or (not D))
		            g := (7×i) mod 16
		        //Be wary of the below definitions of a,b,c,d
		        F := F + A + K[i] + M[g]
		        A := D
		        D := C
		        C := B
		        B := B + leftrotate(F, s[i])
		    end for
		    //Add this chunk's hash to result so far:
		    a0 := a0 + A
		    b0 := b0 + B
		    c0 := c0 + C
		    d0 := d0 + D
		end for

		var char digest[16] := a0 append b0 append c0 append d0 //(Output is in little-endian)

		//leftrotate function definition
		leftrotate (x, c)
		    return (x << c) binary or (x >> (32-c));
	}
	
	public static void main(String[] args) throws NoSuchAlgorithmException {

		for (String s : Security.getAlgorithms("MessageDigest")) {
			System.out.println(s);
		}

		MessageDigest md5 = MessageDigest.getInstance("MD5");
		byte[] digest = md5.digest("Hello Digest".getBytes());
		System.out.println(Hex.encodeHex(digest));

		MessageDigest sha = MessageDigest.getInstance("SHA");
		digest = sha.digest("Hello Digest".getBytes());
		System.out.println(Hex.encodeHex(digest));
	}
}
