import Control.Monad (replicateM)

import qualified Data.ByteString.Lazy as BL
import qualified Data.ByteString.Lazy.Char8 as BLC
import Data.Binary.Get
import Data.Binary.Put
import Data.Bits

import Data.Array (Array, listArray, (!))
import Data.List (foldl)
import Data.Word (Word32)

import Numeric (showHex)

-- functions
type Fun = Word32 -> Word32 -> Word32 -> Word32

funF, funG, funH, funI :: Fun
funF x y z = (x .&. y) .|. (complement x .&. z)
funG x y z = (x .&. z) .|. (complement z .&. y)
funH x y z = x `xor` y `xor` z
funI x y z = y `xor` (complement z .|. x)

idxF, idxG, idxH, idxI :: Int -> Int
idxF i = i
idxG i = (5 * i + 1) `mod` 16
idxH i = (3 * i + 5) `mod` 16
idxI i = 7 * i `mod` 16

-- arrays
funA :: Array Int Fun
funA = listArray (1,64) $ replicate 16 =<< [funF, funG, funH, funI]

idxA :: Array Int Int
idxA = listArray (1,64) $ zipWith ($) (replicate 16 =<< [idxF, idxG, idxH, idxI]) [0..63]

rotA :: Array Int Int
rotA = listArray (1,64) $ concat . replicate 4 =<<
       [[7, 12, 17, 22], [5, 9, 14, 20], [4, 11, 16, 23], [6, 10, 15, 21]]

sinA :: Array Int Word32
sinA = listArray (1,64) $ map (floor . (*mult) . abs . sin) [1..64]
    where mult = 2 ** 32 :: Double

-- to lazily calculate MD5 sum for standart input:
-- main = putStrLn . md5sum =<< BL.getContents

main :: IO ()
main = mapM_ (putStrLn . md5sum . BLC.pack)
        [ ""
        , "a"
        , "abc"
        , "message digest"
        , "abcdefghijklmnopqrstuvwxyz"
        , "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
        , "12345678901234567890123456789012345678901234567890123456789012345678901234567890"
        ]

md5sum :: BL.ByteString -> String
md5sum input =
    let MD5 a b c d = getMD5 initial `runGet` input
    in  foldr hex [] . BL.unpack . runPut $ mapM_ putWord32le [a,b,c,d]
    where
      initial = MD5 0x67452301 0xEFCDAB89 0x98BADCFE 0x10325476

      hex x s | x < 16    = '0' : showHex x s -- quick hack: like "%02x"
              | otherwise =       showHex x s

data MD5 = MD5
    { a :: {-# UNPACK #-} !Word32
    , b :: {-# UNPACK #-} !Word32
    , c :: {-# UNPACK #-} !Word32
    , d :: {-# UNPACK #-} !Word32
    }

getMD5 :: MD5 -> Get MD5
getMD5 md5 = do
  chunk <- getLazyByteString 64
  let len = BL.length chunk

  if len == 64
  then getMD5 $! md5 <+> chunk  -- apply and process next chunk

  else do                       -- input is totally eaten, finalize
    bytes <- bytesRead
    let fin   = runPut . putWord64le $ fromIntegral (bytes - 64 + len) * 8
        pad n = chunk `BL.append` (0x80 `BL.cons` BL.replicate (n - 1) 0x00)

    return $ if len >= 56
        then md5 <+> pad (64 - len) <+> BL.replicate 56 0x00 `BL.append` fin
        else md5 <+> pad (56 - len) `BL.append` fin

(<+>) :: MD5 -> BL.ByteString -> MD5
infixl 5  <+>
md5@(MD5 a b c d) <+> bs =
    let datA = listArray (0,15) $ replicateM 16 getWord32le `runGet` bs
        MD5 a' b' c' d' = foldl' (md5round datA) md5 [1..64]
    in MD5 (a + a') (b + b') (c + c') (d + d')

md5round :: Array Int Word32 -> MD5 -> Int -> MD5
md5round datA (MD5 a b c d) i =
    let f  =  funA ! i
        w  =  datA ! (idxA ! i)
        a' =  b + (a + f b c d + w + sinA ! i) `rotateL` rotA ! i
    in MD5 d a' b c


procedure main()  # validate against the RFC test strings and more
   testMD5("The quick brown fox jumps over the lazy dog", 16r9e107d9d372bb6826bd81d3542a419d6)
   testMD5("The quick brown fox jumps over the lazy dog.", 16re4d909c290d0fb1ca068ffaddf22cbd0)
   testMD5("", 16rd41d8cd98f00b204e9800998ecf8427e)    #R = MD5 test suite from RFC
   testMD5("a", 16r0cc175b9c0f1b6a831c399e269772661)   #R
   testMD5("abc", 16r900150983cd24fb0d6963f7d28e17f72) #R
   testMD5("message digest", 16rf96b697d7cb7938d525a2f31aaf161d0) #R
   testMD5("abcdefghijklmnopqrstuvwxyz", 16rc3fcd3d76192e4007dfb496cca67e13b) #R
   testMD5("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 16rd174ab98d277d9f5a5611c2c9f419d9f) #R
   testMD5("12345678901234567890123456789012345678901234567890123456789012345678901234567890", 16r57edf4a22be3c955ac49da2e2107b67a) #R
end

procedure testMD5(s,rh)  # compute the MD5 hash and compare it to reference value
   write("Message(length=",*s,") = ",image(s))
   write("Digest = ",hexstring(h := MD5(s)),if h = rh then " matches reference hash" else (" does not match reference hash = " || hexstring(rh)),"\n")
end

link hexcvt # for testMD5

$define B32                     4   #  32 bits
$define B64                     8   #  64 bits in bytes
$define B512                   64   # 512 bits in bytes
$define M32          16r100000000   # 2^32
$define M64  16r10000000000000000   # 2^64

procedure MD5(s)                                    #: return MD5 hash of message s
local w,a,b,c,d,i,t,m
local mlength,message,hash
static rs,ks,istate,maxpad,g

initial {
   every (rs := []) |||:=
      (t := [ 7, 12, 17, 22] | [ 5,  9, 14, 20] | [ 4, 11, 16, 23] | [ 6, 10, 15, 21]) ||| t ||| t ||| t
   every put(ks := [],integer(M32 * abs(sin(i := 1 to 64))))
   istate := [ 16r67452301, 16rEFCDAB89, 16r98BADCFE, 16r10325476 ]  # "Magic" IV
   maxpad := left(char(16r80),B512+B64,char(16r00)) # maximum possible padding
   g := []
   every i := 0 to 63 do                            # precompute offsets
      case round := i/16 of {
         0 : put(g,i + 1)
         1 : put(g,(5*i+1) % 16 + 1)
	 2 : put(g,(3*i+5) % 16 + 1)
         3 : put(g,(7*i) % 16 + 1)
         }
   if not (*rs = *ks = 64) then runerr(500,"MD5 setup error")
   }
                                                    # 1. Construct prefix
   t := (*s*8)%M64                                  # original message length
   s ||:= maxpad                                    # append maximum padding
   s[0-:*s%B512] := ""                              # trim to final length
   s[0-:B64] := reverse(unsigned2string(t,B64) )    # as little endian length

   message := []                                    # 2. Subdivide message
   s ? while put(message,move(B512))                #  into 512 bit blocks

                                                    # 3. Transform message ...
   state := copy(istate)                            # Initialize hashes
   every m := !message do {                         # For each message block
      w := []
      m ? while put(w,unsigned(reverse(move(B32)))) # break into little-endian words

      a := state[1]                                 # pick up hashes
      b := state[2]
      c := state[3]
      d := state[4]

      every i := 1 to 64 do  {                      # Process 4 rounds of hashes
         case round := (i-1)/16 of {
	    0 : a +:= ixor(d, iand(b,ixor(c,d)))          # 0..15  - alternate F
            1 : a +:= ixor(c,iand(d,ixor(b,c)))           # 16..31 - alternate G
            2 : a +:= ixor(b,ixor(c,d))                   # 32..47 - H
            3 : a +:= ixor(c,ior(b,ixor(d,16rffffffff)))  # 48..64 - alternate I
	    }                                       # Core of FF, GG, HH, II
         a +:= ks[i] + w[g[i]]                      # and the rest
         a %:= M32
         a := ior( ishift(a,rs[i]), ishift(a,-(32-rs[i]))) # 32bit rotate
         a +:= b
         a :=: b :=: c :=: d                        # rotate variables
	 }

      state[1] +:= a                                # Add back new hashes
      state[2] +:= b
      state[3] +:= c
      state[4] +:= d
      every !state %:= M32                          # mod 2^32
   }
   every (hash := "") ||:= reverse(unsigned2string(!state,4)) # little-endian digest
   return unsigned(hash)
end

procedure unsigned2string(i,w)                      # uint to string pad to w bytes
local s
   if i < 0 then runerr(500,i)
   s := ""
   while (0 < i) | (*s < \w) do {
      s ||:= char(i % 256)
      i /:= 256
      }
   return reverse(s)
end

link unsigned                                       # string to unsigned integer


class MD5
{

  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 String toHexString(byte[] b)
  {
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < b.length; i++)
    {
      sb.append(String.format("%02X", b[i] & 0xFF));
    }
    return sb.toString();
  }

  public static void main(String[] args)
  {
    String[] testStrings = { "", "a", "abc", "message digest", "abcdefghijklmnopqrstuvwxyz", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", "12345678901234567890123456789012345678901234567890123456789012345678901234567890" };
    for (String s : testStrings)
      System.out.println("0x" + toHexString(computeMD5(s.getBytes())) + " <== \"" + s + "\"");
    return;
  }

}


// version 1.1.3

object MD5 {

    private val INIT_A = 0x67452301
    private val INIT_B = 0xEFCDAB89L.toInt()
    private val INIT_C = 0x98BADCFEL.toInt()
    private val INIT_D = 0x10325476

    private val SHIFT_AMTS = intArrayOf(
        7, 12, 17, 22,
        5,  9, 14, 20,
        4, 11, 16, 23,
        6, 10, 15, 21
    )

    private val TABLE_T = IntArray(64) {
        ((1L shl 32) * Math.abs(Math.sin(it + 1.0))).toLong().toInt()
    }

    fun compute(message: ByteArray): ByteArray {
        val messageLenBytes = message.size
        val numBlocks = ((messageLenBytes + 8) ushr 6) + 1
        val totalLen = numBlocks shl 6
        val paddingBytes = ByteArray(totalLen - messageLenBytes)
        paddingBytes[0] = 0x80.toByte()
        var messageLenBits = (messageLenBytes shl 3).toLong()

        for (i in 0..7) {
            paddingBytes[paddingBytes.size - 8 + i] = messageLenBits.toByte()
            messageLenBits = messageLenBits ushr 8
        }

        var a = INIT_A
        var b = INIT_B
        var c = INIT_C
        var d = INIT_D
        val buffer = IntArray(16)

        for (i in 0 until numBlocks) {
            var index = i shl 6

            for (j in 0..63) {
                val temp = if (index < messageLenBytes) message[index] else
                               paddingBytes[index - messageLenBytes]
                buffer[j ushr 2] = (temp.toInt() shl 24) or (buffer[j ushr 2] ushr 8)
                index++
            }

            val originalA = a
            val originalB = b
            val originalC = c
            val originalD = d

            for (j in 0..63) {
                val div16 = j ushr 4
                var f = 0
                var bufferIndex = j
                when (div16) {
                    0 -> {
                        f = (b and c) or (b.inv() and d)
                    }

                    1 -> {
                        f = (b and d) or (c and d.inv())
                        bufferIndex = (bufferIndex * 5 + 1) and 0x0F
                    }

                    2 -> {
                        f = b xor c xor d;
                        bufferIndex = (bufferIndex * 3 + 5) and 0x0F
                    }

                    3 -> {
                        f = c xor (b or d.inv());
                        bufferIndex = (bufferIndex * 7) and 0x0F
                    }
                }

                val temp = b + Integer.rotateLeft(a + f + buffer[bufferIndex] +
                           TABLE_T[j], SHIFT_AMTS[(div16 shl 2) or (j and 3)])
                a = d
                d = c
                c = b
                b = temp
            }

            a += originalA
            b += originalB
            c += originalC
            d += originalD
        }

        val md5 = ByteArray(16)
        var count = 0

        for (i in 0..3) {
            var n = if (i == 0) a else (if (i == 1) b else (if (i == 2) c else d))

            for (j in 0..3) {
                md5[count++] = n.toByte()
                n = n ushr 8
            }
        }
        return md5
    }
}

fun ByteArray.toHexString(): String {
    val sb = StringBuilder()
    for (b in this) sb.append(String.format("%02x", b.toInt() and 0xFF))
    return sb.toString()
}

fun main(args: Array<String>) {
    val testStrings = arrayOf(
        "",
        "a",
        "abc",
        "message digest",
        "abcdefghijklmnopqrstuvwxyz",
        "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
        "12345678901234567890123456789012345678901234567890123456789012345678901234567890"
    )

    println("${"hash code".padStart(34)} <== string")
    for (s in testStrings) {
        println("0x${MD5.compute(s.toByteArray()).toHexString()} <== \"$s\"")
    }
}


----------------------------------------
-- Calculates MD5 hash of string or bytearray
-- @param {bytearray|string} input
-- @return {bytearray} (16 bytes)
----------------------------------------
on md5 (input)
    if stringP(input) then input = bytearray(input)

    -- Convert string to list of little-endian words...
    t_iLen = input.length * 8
    t_iCnt = (t_iLen + 64) / 512 * 16 + 16

    -- Create list, fill with zeros...
    x = []
    x[t_iCnt] = 0

    t_fArr = [1, 256, 65536, 16777216]
    i = 0
    j = 0
    repeat while i < t_iLen
        j = j + 1
        t_iNext = i / 32 + 1
        t_iTemp = bitAnd(input[i/8+1], 255) * t_fArr[j]
        x[t_iNext] = bitOr(x[t_iNext], t_iTemp)
        i = i + 8
        j = j mod 4
    end repeat

    -- Append padding...
    t_iNext = t_iLen / 32 + 1
    x[t_iNext] = bitOr(x[t_iNext], 128 * t_fArr[j + 1])
    x[(t_iLen + 64) / 512 * 16 + 15] = t_iLen

    -- Actual algorithm starts here...
    a = 1732584193
    b = -271733879
    c = -1732584194
    d = 271733878
    i = 1
    t_iWrap = the maxInteger + 1
    t_iCount = x.count + 1
    repeat while i < t_iCount
        olda = a
        oldb = b
        oldc = c
        oldd = d

        -- Round(1) --
        n = bitOr(bitAnd(b, c), bitAnd(bitNot(b), d)) + a + x[i] - 680876936
        if(n < 0) then a = bitOr(n * 128, bitOr((n + t_iWrap) / 33554432, 64)) + b
        else a = bitOr(n * 128, n / 33554432) + b
        n = bitOr(bitAnd(a, b), bitAnd(bitNot(a), c)) + d + x[i + 1] - 389564586
        if(n < 0) then d = bitOr(n * 4096, bitOr((n + t_iWrap) / 1048576, 2048)) + a
        else d = bitOr(n * 4096, n / 1048576) + a
        n = bitOr(bitAnd(d, a), bitAnd(bitNot(d), b)) + c + x[i + 2] + 606105819
        if(n < 0) then c = bitOr(n * 131072, bitOr((n + t_iWrap) / 32768, 65536)) + d
        else c = bitOr(n * 131072, n / 32768) + d
        n = bitOr(bitAnd(c, d), bitAnd(bitNot(c), a)) + b + x[i + 3] - 1044525330
        if(n < 0) then b = bitOr(n * 4194304, bitOr((n + t_iWrap) / 1024, 2097152)) + c
        else b = bitOr(n * 4194304, n / 1024) + c
        n = bitOr(bitAnd(b, c), bitAnd(bitNot(b), d)) + a + x[i + 4] - 176418897
        if(n < 0) then a = bitOr(n * 128, bitOr((n + t_iWrap) / 33554432, 64)) + b
        else a = bitOr(n * 128, n / 33554432) + b
        n = bitOr(bitAnd(a, b), bitAnd(bitNot(a), c)) + d + x[i + 5] + 1200080426
        if(n < 0) then d = bitOr(n * 4096, bitOr((n + t_iWrap) / 1048576, 2048)) + a
        else d = bitOr(n * 4096, n / 1048576) + a
        n = bitOr(bitAnd(d, a), bitAnd(bitNot(d), b)) + c + x[i + 6] - 1473231341
        if(n < 0) then c = bitOr(n * 131072, bitOr((n + t_iWrap) / 32768, 65536)) + d
        else c = bitOr(n * 131072, n / 32768) + d
        n = bitOr(bitAnd(c, d), bitAnd(bitNot(c), a)) + b + x[i + 7] - 45705983
        if(n < 0) then b = bitOr(n * 4194304, bitOr((n + t_iWrap) / 1024, 2097152)) + c
        else b = bitOr(n * 4194304, n / 1024) + c
        n = bitOr(bitAnd(b, c), bitAnd(bitNot(b), d)) + a + x[i + 8] + 1770035416
        if(n < 0) then a = bitOr(n * 128, bitOr((n + t_iWrap) / 33554432, 64)) + b
        else a = bitOr(n * 128, n / 33554432) + b
        n = bitOr(bitAnd(a, b), bitAnd(bitNot(a), c)) + d + x[i + 9] - 1958414417
        if(n < 0) then d = bitOr(n * 4096, bitOr((n + t_iWrap) / 1048576, 2048)) + a
        else d = bitOr(n * 4096, n / 1048576) + a
        n = bitOr(bitAnd(d, a), bitAnd(bitNot(d), b)) + c + x[i + 10] - 42063
        if(n < 0) then c = bitOr(n * 131072, bitOr((n + t_iWrap) / 32768, 65536)) + d
        else c = bitOr(n * 131072, n / 32768) + d
        n = bitOr(bitAnd(c, d), bitAnd(bitNot(c), a)) + b + x[i + 11] - 1990404162
        if(n < 0) then b = bitOr(n * 4194304, bitOr((n + t_iWrap) / 1024, 2097152)) + c
        else b = bitOr(n * 4194304, n / 1024) + c
        n = bitOr(bitAnd(b, c), bitAnd(bitNot(b), d)) + a + x[i + 12] + 1804603682
        if(n < 0) then a = bitOr(n * 128, bitOr((n + t_iWrap) / 33554432, 64)) + b
        else a = bitOr(n * 128, n / 33554432) + b
        n = bitOr(bitAnd(a, b), bitAnd(bitNot(a), c)) + d + x[i + 13] - 40341101
        if(n < 0) then d = bitOr(n * 4096, bitOr((n + t_iWrap) / 1048576, 2048)) + a
        else d = bitOr(n * 4096, n / 1048576) + a
        n = bitOr(bitAnd(d, a), bitAnd(bitNot(d), b)) + c + x[i + 14] - 1502002290
        if(n < 0) then c = bitOr(n * 131072, bitOr((n + t_iWrap) / 32768, 65536)) + d
        else c = bitOr(n * 131072, n / 32768) + d
        n = bitOr(bitAnd(c, d), bitAnd(bitNot(c), a)) + b + x[i + 15] + 1236535329
        if(n < 0) then b = bitOr(n * 4194304, bitOr((n + t_iWrap) / 1024, 2097152)) + c
        else b = bitOr(n * 4194304, n / 1024) + c

        -- Round(2) --
        n = bitOr(bitAnd(b, d), bitAnd(c, bitNot(d))) + a + x[i + 1] - 165796510
        if(n < 0) then a = bitOr(n * 32, bitOr((n + t_iWrap) / 134217728, 16)) + b
        else a = bitOr(n * 32, n / 134217728) + b
        n = bitOr(bitAnd(a, c), bitAnd(b, bitNot(c))) + d + x[i + 6] - 1069501632
        if(n < 0) then d = bitOr(n * 512, bitOr((n + t_iWrap) / 8388608, 256)) + a
        else d = bitOr(n * 512, n / 8388608) + a
        n = bitOr(bitAnd(d, b), bitAnd(a, bitNot(b))) + c + x[i + 11] + 643717713
        if(n < 0) then c = bitOr(n * 16384, bitOr((n + t_iWrap) / 262144, 8192)) + d
        else c = bitOr(n * 16384, n / 262144) + d
        n = bitOr(bitAnd(c, a), bitAnd(d, bitNot(a))) + b + x[i] - 373897302
        if(n < 0) then b = bitOr(n * 1048576, bitOr((n + t_iWrap) / 4096, 524288)) + c
        else b = bitOr(n * 1048576, n / 4096) + c
        n = bitOr(bitAnd(b, d), bitAnd(c, bitNot(d))) + a + x[i + 5] - 701558691
        if(n < 0) then a = bitOr(n * 32, bitOr((n + t_iWrap) / 134217728, 16)) + b
        else a = bitOr(n * 32, n / 134217728) + b
        n = bitOr(bitAnd(a, c), bitAnd(b, bitNot(c))) + d + x[i + 10] + 38016083
        if(n < 0) then d = bitOr(n * 512, bitOr((n + t_iWrap) / 8388608, 256)) + a
        else d = bitOr(n * 512, n / 8388608) + a
        n = bitOr(bitAnd(d, b), bitAnd(a, bitNot(b))) + c + x[i + 15] - 660478335
        if(n < 0) then c = bitOr(n * 16384, bitOr((n + t_iWrap) / 262144, 8192)) + d
        else c = bitOr(n * 16384, n / 262144) + d
        n = bitOr(bitAnd(c, a), bitAnd(d, bitNot(a))) + b + x[i + 4] - 405537848
        if(n < 0) then b = bitOr(n * 1048576, bitOr((n + t_iWrap) / 4096, 524288)) + c
        else b = bitOr(n * 1048576, n / 4096) + c
        n = bitOr(bitAnd(b, d), bitAnd(c, bitNot(d))) + a + x[i + 9] + 568446438
        if(n < 0) then a = bitOr(n * 32, bitOr((n + t_iWrap) / 134217728, 16)) + b
        else a = bitOr(n * 32, n / 134217728) + b
        n = bitOr(bitAnd(a, c), bitAnd(b, bitNot(c))) + d + x[i + 14] - 1019803690
        if(n < 0) then d = bitOr(n * 512, bitOr((n + t_iWrap) / 8388608, 256)) + a
        else d = bitOr(n * 512, n / 8388608) + a
        n = bitOr(bitAnd(d, b), bitAnd(a, bitNot(b))) + c + x[i + 3] - 187363961
        if(n < 0) then c = bitOr(n * 16384, bitOr((n + t_iWrap) / 262144, 8192)) + d
        else c = bitOr(n * 16384, n / 262144) + d
        n = bitOr(bitAnd(c, a), bitAnd(d, bitNot(a))) + b + x[i + 8] + 1163531501
        if(n < 0) then b = bitOr(n * 1048576, bitOr((n + t_iWrap) / 4096, 524288)) + c
        else b = bitOr(n * 1048576, n / 4096) + c
        n = bitOr(bitAnd(b, d), bitAnd(c, bitNot(d))) + a + x[i + 13] - 1444681467
        if(n < 0) then a = bitOr(n * 32, bitOr((n + t_iWrap) / 134217728, 16)) + b
        else a = bitOr(n * 32, n / 134217728) + b
        n = bitOr(bitAnd(a, c), bitAnd(b, bitNot(c))) + d + x[i + 2] - 51403784
        if(n < 0) then d = bitOr(n * 512, bitOr((n + t_iWrap) / 8388608, 256)) + a
        else d = bitOr(n * 512, n / 8388608) + a
        n = bitOr(bitAnd(d, b), bitAnd(a, bitNot(b))) + c + x[i + 7] + 1735328473
        if(n < 0) then c = bitOr(n * 16384, bitOr((n + t_iWrap) / 262144, 8192)) + d
        else c = bitOr(n * 16384, n / 262144) + d
        n = bitOr(bitAnd(c, a), bitAnd(d, bitNot(a))) + b + x[i + 12] - 1926607734
        if(n < 0) then b = bitOr(n * 1048576, bitOr((n + t_iWrap) / 4096, 524288)) + c
        else b = bitOr(n * 1048576, n / 4096) + c

        -- Round(3) --
        n = bitXor(bitXor(b, c), d) + a + x[i + 5] - 378558
        if(n < 0) then a = bitOr(n * 16, bitOr((n + t_iWrap) / 268435456, 8)) + b
        else a = bitOr(n * 16, n / 268435456) + b
        n = bitXor(bitXor(a, b), c) + d + x[i + 8] - 2022574463
        if(n < 0) then d = bitOr(n * 2048, bitOr((n + t_iWrap) / 2097152, 1024)) + a
        else d = bitOr(n * 2048, n / 2097152) + a
        n = bitXor(bitXor(d, a), b) + c + x[i + 11] + 1839030562
        if(n < 0) then c = bitOr(n * 65536, bitOr((n + t_iWrap) / 65536, 32768)) + d
        else c = bitOr(n * 65536, n / 65536) + d
        n = bitXor(bitXor(c, d), a) + b + x[i + 14] - 35309556
        if(n < 0) then b = bitOr(n * 8388608, bitOr((n + t_iWrap) / 512, 4194304)) + c
        else b = bitOr(n * 8388608, n / 512) + c
        n = bitXor(bitXor(b, c), d) + a + x[i + 1] - 1530992060
        if(n < 0) then a = bitOr(n * 16, bitOr((n + t_iWrap) / 268435456, 8)) + b
        else a = bitOr(n * 16, n / 268435456) + b
        n = bitXor(bitXor(a, b), c) + d + x[i + 4] + 1272893353
        if(n < 0) then d = bitOr(n * 2048, bitOr((n + t_iWrap) / 2097152, 1024)) + a
        else d = bitOr(n * 2048, n / 2097152) + a
        n = bitXor(bitXor(d, a), b) + c + x[i + 7] - 155497632
        if(n < 0) then c = bitOr(n * 65536, bitOr((n + t_iWrap) / 65536, 32768)) + d
        else c = bitOr(n * 65536, n / 65536) + d
        n = bitXor(bitXor(c, d), a) + b + x[i + 10] - 1094730640
        if(n < 0) then b = bitOr(n * 8388608, bitOr((n + t_iWrap) / 512, 4194304)) + c
        else b = bitOr(n * 8388608, n / 512) + c
        n = bitXor(bitXor(b, c), d) + a + x[i + 13] + 681279174
        if(n < 0) then a = bitOr(n * 16, bitOr((n + t_iWrap) / 268435456, 8)) + b
        else a = bitOr(n * 16, n / 268435456) + b
        n = bitXor(bitXor(a, b), c) + d + x[i] - 358537222
        if(n < 0) then d = bitOr(n * 2048, bitOr((n + t_iWrap) / 2097152, 1024)) + a
        else d = bitOr(n * 2048, n / 2097152) + a
        n = bitXor(bitXor(d, a), b) + c + x[i + 3] - 722521979
        if(n < 0) then c = bitOr(n * 65536, bitOr((n + t_iWrap) / 65536, 32768)) + d
        else c = bitOr(n * 65536, n / 65536) + d
        n = bitXor(bitXor(c, d), a) + b + x[i + 6] + 76029189
        if(n < 0) then b = bitOr(n * 8388608, bitOr((n + t_iWrap) / 512, 4194304)) + c
        else b = bitOr(n * 8388608, n / 512) + c
        n = bitXor(bitXor(b, c), d) + a + x[i + 9] - 640364487
        if(n < 0) then a = bitOr(n * 16, bitOr((n + t_iWrap) / 268435456, 8)) + b
        else a = bitOr(n * 16, n / 268435456) + b
        n = bitXor(bitXor(a, b), c) + d + x[i + 12] - 421815835
        if(n < 0) then d = bitOr(n * 2048, bitOr((n + t_iWrap) / 2097152, 1024)) + a
        else d = bitOr(n * 2048, n / 2097152) + a
        n = bitXor(bitXor(d, a), b) + c + x[i + 15] + 530742520
        if(n < 0) then c = bitOr(n * 65536, bitOr((n + t_iWrap) / 65536, 32768)) + d
        else c = bitOr(n * 65536, n / 65536) + d
        n = bitXor(bitXor(c, d), a) + b + x[i + 2] - 995338651
        if(n < 0) then b = bitOr(n * 8388608, bitOr((n + t_iWrap) / 512, 4194304)) + c
        else b = bitOr(n * 8388608, n / 512) + c

        -- Round(4) --
        n = bitXor(c, bitOr(b, bitNot(d))) + a + x[i] - 198630844
        if(n < 0) then a = bitOr(n * 64, bitOr((n + t_iWrap) / 67108864, 32)) + b
        else a = bitOr(n * 64, n / 67108864) + b
        n = bitXor(b, bitOr(a, bitNot(c))) + d + x[i + 7] + 1126891415
        if(n < 0) then d = bitOr(n * 1024, bitOr((n + t_iWrap) / 4194304, 512)) + a
        else d = bitOr(n * 1024, n / 4194304) + a
        n = bitXor(a, bitOr(d, bitNot(b))) + c + x[i + 14] - 1416354905
        if(n < 0) then c = bitOr(n * 32768, bitOr((n + t_iWrap) / 131072, 16384)) + d
        else c = bitOr(n * 32768, n / 131072) + d
        n = bitXor(d, bitOr(c, bitNot(a))) + b + x[i + 5] - 57434055
        if(n < 0) then b = bitOr(n * 2097152, bitOr((n + t_iWrap) / 2048, 1048576)) + c
        else b = bitOr(n * 2097152, n / 2048) + c
        n = bitXor(c, bitOr(b, bitNot(d))) + a + x[i + 12] + 1700485571
        if(n < 0) then a = bitOr(n * 64, bitOr((n + t_iWrap) / 67108864, 32)) + b
        else a = bitOr(n * 64, n / 67108864) + b
        n = bitXor(b, bitOr(a, bitNot(c))) + d + x[i + 3] - 1894986606
        if(n < 0) then d = bitOr(n * 1024, bitOr((n + t_iWrap) / 4194304, 512)) + a
        else d = bitOr(n * 1024, n / 4194304) + a
        n = bitXor(a, bitOr(d, bitNot(b))) + c + x[i + 10] - 1051523
        if(n < 0) then c = bitOr(n * 32768, bitOr((n + t_iWrap) / 131072, 16384)) + d
        else c = bitOr(n * 32768, n / 131072) + d
        n = bitXor(d, bitOr(c, bitNot(a))) + b + x[i + 1] - 2054922799
        if(n < 0) then b = bitOr(n * 2097152, bitOr((n + t_iWrap) / 2048, 1048576)) + c
        else b = bitOr(n * 2097152, n / 2048) + c
        n = bitXor(c, bitOr(b, bitNot(d))) + a + x[i + 8] + 1873313359
        if(n < 0) then a = bitOr(n * 64, bitOr((n + t_iWrap) / 67108864, 32)) + b
        else a = bitOr(n * 64, n / 67108864) + b
        n = bitXor(b, bitOr(a, bitNot(c))) + d + x[i + 15] - 30611744
        if(n < 0) then d = bitOr(n * 1024, bitOr((n + t_iWrap) / 4194304, 512)) + a
        else d = bitOr(n * 1024, n / 4194304) + a
        n = bitXor(a, bitOr(d, bitNot(b))) + c + x[i + 6] - 1560198380
        if(n < 0) then c = bitOr(n * 32768, bitOr((n + t_iWrap) / 131072, 16384)) + d
        else c = bitOr(n * 32768, n / 131072) + d
        n = bitXor(d, bitOr(c, bitNot(a))) + b + x[i + 13] + 1309151649
        if(n < 0) then b = bitOr(n * 2097152, bitOr((n + t_iWrap) / 2048, 1048576)) + c
        else b = bitOr(n * 2097152, n / 2048) + c
        n = bitXor(c, bitOr(b, bitNot(d))) + a + x[i + 4] - 145523070
        if(n < 0) then a = bitOr(n * 64, bitOr((n + t_iWrap) / 67108864, 32)) + b
        else a = bitOr(n * 64, n / 67108864) + b
        n = bitXor(b, bitOr(a, bitNot(c))) + d + x[i + 11] - 1120210379
        if(n < 0) then d = bitOr(n * 1024, bitOr((n + t_iWrap) / 4194304, 512)) + a
        else d = bitOr(n * 1024, n / 4194304) + a
        n = bitXor(a, bitOr(d, bitNot(b))) + c + x[i + 2] + 718787259
        if(n < 0) then c = bitOr(n * 32768, bitOr((n + t_iWrap) / 131072, 16384)) + d
        else c = bitOr(n * 32768, n / 131072) + d
        n = bitXor(d, bitOr(c, bitNot(a))) + b + x[i + 9] - 343485551
        if(n < 0) then b = bitOr(n * 2097152, bitOr((n + t_iWrap) / 2048, 1048576)) + c
        else b = bitOr(n * 2097152, n / 2048) + c
        a = a + olda
        b = b + oldb
        c = c + oldc
        d = d + oldd
        i = i + 16
    end repeat

    t_iArr = [a, b, c, d]
    ba = bytearray()
    p = 1
    repeat with i in t_iArr
        if(i > 0) then
            repeat with n = 1 to 4
                ba[p] = (i mod 256)
                i = i / 256
                p = p+1
            end repeat
        else
            i = bitNot(i)
            repeat with n = 1 to 4
                ba[p] = 255-(i mod 256)
                i = i / 256
                p = p+1
            end repeat
        end if
    end repeat
    ba.position = 1
    return ba
end


INTERFACE MD5;

IMPORT Word;

TYPE Digest = ARRAY [0..15] OF CHAR;
TYPE Buffer = ARRAY [0..63] OF CHAR;

TYPE T = RECORD
  state: ARRAY [0..3] OF Word.T;
  count: ARRAY [0..1] OF Word.T;
  buffer: Buffer;
END;

PROCEDURE Init(VAR md5ctx: T);
PROCEDURE Update(VAR md5ctx: T; input: TEXT);
PROCEDURE Final(VAR md5ctx: T): Digest;
PROCEDURE ToText(hash: Digest): TEXT;

END MD5.


#!/usr/bin/rexx

/* Expected results:
   0xd41d8cd98f00b204e9800998ecf8427e <== ""
   0x0cc175b9c0f1b6a831c399e269772661 <== "a"
   0x900150983cd24fb0d6963f7d28e17f72 <== "abc"
   0xf96b697d7cb7938d525a2f31aaf161d0 <== "message digest"
   0xc3fcd3d76192e4007dfb496cca67e13b <== "abcdefghijklmnopqrstuvwxyz"
   0xd174ab98d277d9f5a5611c2c9f419d9f <== "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
   0x57edf4a22be3c955ac49da2e2107b67a <== "12345678901234567890123456789012345678901234567890123456789012345678901234567890"
*/

md5 = .md5~new; md5~update(""); say md5~digest
md5 = .md5~new; md5~update("a"); say md5~digest
md5 = .md5~new; md5~update("abc"); say md5~digest
md5 = .md5~new; md5~update("message digest"); say md5~digest
md5 = .md5~new("abcdefghijklmnopqrstuvwxyz"); say md5~digest
md5 = .md5~new("ABCDEFGHIJKLMNOPQRSTUVWXYZ"); md5~update("abcdefghijklmnopqrstuvwxyz0123456789"); say md5~digest
md5 = .md5~new; md5~update("12345678901234567890123456789012345678901234567890123456789012345678901234567890"); say md5~digest

-- requires OORexx 4.2.0 or later
-- standard numeric digits of 9 is not enough in this case
::options digits 20

-- Implementation mainly based on pseudocode in https://en.wikipedia.org/wiki/MD5
::class md5 public

::method init
expose a0 b0 c0 d0 count buffer index K. s  -- instance variables
use strict arg chunk=""
-- Initialize message digest
a0 = .int32~new('67452301'x,"C")   -- A
b0 = .int32~new('efcdab89'x,"C")   -- B
c0 = .int32~new('98badcfe'x,"C")   -- C
d0 = .int32~new('10325476'x,"C")   -- D
-- The 512 bit chunk buffer
buffer = .mutablebuffer~new('00'x~copies(64),64)
-- The position in the buffer to insert new input
index = 1
-- message bytecount
count = 0
-- initialize leftrotate amounts
nrs = .array~of(7,12,17,22)
s = nrs~union(nrs)~union(nrs)~union(nrs)
nrs = .array~of(5,9,14,20)
s = s~union(nrs)~union(nrs)~union(nrs)~union(nrs)
nrs = .array~of(4,11,16,23)
s = s~union(nrs)~union(nrs)~union(nrs)~union(nrs)
nrs = .array~of(6,10,15,21)
s = s~union(nrs)~union(nrs)~union(nrs)~union(nrs)
-- initialize sinus derived constants.
-- sin function from RXMath Library shipped with OORexx
-- see ::routine directive at the end of the code
do i=0 to 63
  K.i = .int32~new(((2**32)*(sin(i+1,16,R)~abs))~floor)
end
-- process initial string if any
self~update(chunk)
exit

::method update
  expose a0 b0 c0 d0 count buffer index K. s  -- instance variables
  use strict arg chunk
  count += chunk~length
  if chunk~length<65-index then do
    buffer~overlay(chunk,index)
    index += chunk~length
  end
  else do
    split = 65-index+1
    parse var chunk part =(split) chunk
    buffer~overlay(part,index)
    index = 65
  end
  -- Only proces completely filled buffer
  do while index=65
    A = a0
    B = b0
    C = c0
    D = d0
    do i=0 to 63
      select
        when i<16 then do
          F = D~xor(B~and(C~xor(D)))
          g = i
        end
        when i<32 then do
          F = C~xor(D~and(B~xor(C)))
          g = (5*i+1)//16
        end
        when i<48 then do
          F = B~xor(C)~xor(D)
          g = (3*i+5)//16
        end
        otherwise do
          F = C~xor(B~or(D~xor(.int32~new('ffffffff'x,"C"))))
          g = (7*i)//16
        end
      end
      M = .int32~new(buffer~substr(g*4+1,4)~reverse,"C")  -- 32bit word in little-endian
      dTemp = D
      D = C
      C = B
      B = (B + (A+F+K.i+M)~bitrotate(s[i+1]))
      A = dTemp
    end
    a0 = a0+A
    b0 = b0+B
    c0 = c0+C
    d0 = d0+D
    parse var chunk part 65 chunk
    index = part~length+1
    buffer~overlay(part,1,part~length)
  end
exit

::method digest
  expose a0 b0 c0 d0 count buffer index K s -- instance variables
  padlen = 64
  if index<57 then padlen = 57-index
  if index>57 then padlen = 121-index
  padding = '00'x~copies(padlen)~bitor('80'x)
  bitcount = count*8//2**64
  lowword = bitcount//2**32
  hiword = bitcount%2**32
  lowcount = lowword~d2c(4)~reverse -- make it little-endian
  hicount = hiword~d2c(4)~reverse   -- make it little-endian
  self~update(padding || lowcount || hicount)
return a0~string || b0~string || c0~string || d0~string

-- A convenience class to encapsulate operations on non OORexx-like
-- things as little-endian 32-bit words
::class int32 public

::attribute arch class

::method init class
  self~arch = "little-endian"   -- can be adapted for multiple architectures

-- Method to create an int32 like object
-- Input can be a OORexx whole number (type="I") or
-- a character string of 4 bytes (type="C")
-- input truncated or padded to 32-bit word/string
::method init
  expose char4 int32
  use strict arg input, type="Integer"
  -- type must be one of "I"nteger or "C"haracter
  t = type~subchar(1)~upper
  select
    when t=='I' then do
      char4 = input~d2c(4)
      int32 = char4~c2d
    end
    when t=='C' then do
      char4 = input~right(4,'00'x)
      int32 = char4~c2d
    end
    otherwise do
      raise syntax 93.915 array("IC",type)
    end
  end
exit

::method xor  -- wrapper for OORexx bitxor method
  expose char4
  use strict arg other
return .int32~new(char4~bitxor(other~char),"C")

::method and  -- wrapper for OORexx bitand method
  expose char4
  use strict arg other
return .int32~new(char4~bitand(other~char),"C")

::method or   -- wrapper for OORexx bitor method
  expose char4
  use strict arg other
return .int32~new(char4~bitor(other~char),"C")

::method bitleft -- OORexx shift (<<) implementation
  expose char4
  use strict arg bits
  bstring = char4~c2x~x2b
  bstring = bstring~substr(bits+1)~left(bstring~length,'0')
return .int32~new(bstring~b2x~x2d)

::method bitright -- OORexx shift (>>) implementation
  expose char4
  use strict arg bits, signed=.false
  bstring = char4~c2x~x2b
  fill = '0'
  if signed then fill = bstring~subchar(1)
  bstring = bstring~left(bstring~length-bits)~right(bstring~length,fill)
return .int32~new(bstring~b2x~x2d)

::method bitnot   -- OORexx not implementation
  expose char4
return .int32~new(char4~bitxor('ffffffff'x)~c2d,"C")

::method bitrotate  -- OORexx (left) rotate method
  expose char4
  use strict arg bits, direction='left'
  d = direction~subchar(1)~upper
  if d=='L' then do
    leftpart = self~bitleft(bits)
    rightpart = self~bitright(32-bits)
  end
  else do
    leftpart = self~bitleft(32-bits)
    rightpart = self~bitright(bits)
  end
return rightpart~or(leftpart)

::method int  -- retrieve integer as number
  expose int32
return int32

::method char -- retrieve integer as characters
  expose char4
return char4

::method '+'  -- OORexx method to add 2 .int32 instances
  expose int32
  use strict arg other
return .int32~new(int32+other~int)

::method string -- retrieve integer as hexadecimal string
  expose char4
return char4~reverse~c2x~lower

-- Simplify function names for the necessary 'RxMath' functions
::routine sin EXTERNAL "LIBRARY rxmath RxCalcSin"


-- demo\rosetta\md5.exw
with javascript_semantics
function uxor(atom data1,atom data2)
atom result = xor_bits(data1,data2)
    if result<0 then result += #100000000 end if
    return result
end function

function uor(atom data1,atom data2)
atom result = or_bits(data1,data2)
    if result<0 then result += #100000000 end if
    return result
end function

function r32(atom a)
    return remainder(a,#100000000)
end function

function rol(atom word,integer bits)
-- left rotate the bits of a 32-bit number by the specified number of bits
    return r32(word*power(2,bits))+floor(word/power(2,32-bits))
end function

constant K =
{#d76aa478, #e8c7b756, #242070db, #c1bdceee, #f57c0faf, #4787c62a, #a8304613, #fd469501,
 #698098d8, #8b44f7af, #ffff5bb1, #895cd7be, #6b901122, #fd987193, #a679438e, #49b40821,
 #f61e2562, #c040b340, #265e5a51, #e9b6c7aa, #d62f105d, #02441453, #d8a1e681, #e7d3fbc8,
 #21e1cde6, #c33707d6, #f4d50d87, #455a14ed, #a9e3e905, #fcefa3f8, #676f02d9, #8d2a4c8a,
 #fffa3942, #8771f681, #6d9d6122, #fde5380c, #a4beea44, #4bdecfa9, #f6bb4b60, #bebfbc70,
 #289b7ec6, #eaa127fa, #d4ef3085, #04881d05, #d9d4d039, #e6db99e5, #1fa27cf8, #c4ac5665,
 #f4292244, #432aff97, #ab9423a7, #fc93a039, #655b59c3, #8f0ccc92, #ffeff47d, #85845dd1,
 #6fa87e4f, #fe2ce6e0, #a3014314, #4e0811a1, #f7537e82, #bd3af235, #2ad7d2bb, #eb86d391}

constant m_block = {1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,16,
                    2, 7,12, 1, 6,11,16, 5,10,15, 4, 9,14, 3, 8,13,
                    6, 9,12,15, 2, 5, 8,11,14, 1, 4, 7,10,13,16, 3,
                    1, 8,15, 6,13, 4,11, 2, 9,16, 7,14, 5,12, 3,10}

constant c_words = {#67452301,#efcdab89,#98badcfe,#10325476}

sequence words

function divide_in_words(sequence message)
-- Divides the string into words (32-bit numbers)
    integer l = floor(length(message)/4)
    sequence res = repeat(0,l)
    for word=1 to l do
        res[word] = bytes_to_int(message[word*4-3..word*4])
    end for
    return res
end function

procedure process_block(sequence block)
-- Updates the words according to the contents of the block
atom a,b,c,d

    block = divide_in_words(block)

    a = words[1]
    b = words[2]
    c = words[3]
    d = words[4]

    -- Round 1
    for step=1 to 16 by 4 do
        a = r32(b+rol(r32(a+block[m_block[step  ]]+K[step  ]+uor(and_bits(b,c),and_bits(not_bits(b),d))), 7))
        d = r32(a+rol(r32(d+block[m_block[step+1]]+K[step+1]+uor(and_bits(a,b),and_bits(not_bits(a),c))),12))
        c = r32(d+rol(r32(c+block[m_block[step+2]]+K[step+2]+uor(and_bits(d,a),and_bits(not_bits(d),b))),17))
        b = r32(c+rol(r32(b+block[m_block[step+3]]+K[step+3]+uor(and_bits(c,d),and_bits(not_bits(c),a))),22))
    end for

    -- Round 2
    for step=17 to 32 by 4 do
        a = r32(b+rol(r32(a+block[m_block[step  ]]+K[step  ]+uor(and_bits(b,d),and_bits(c,not_bits(d)))), 5))
        d = r32(a+rol(r32(d+block[m_block[step+1]]+K[step+1]+uor(and_bits(a,c),and_bits(b,not_bits(c)))), 9))
        c = r32(d+rol(r32(c+block[m_block[step+2]]+K[step+2]+uor(and_bits(d,b),and_bits(a,not_bits(b)))),14))
        b = r32(c+rol(r32(b+block[m_block[step+3]]+K[step+3]+uor(and_bits(c,a),and_bits(d,not_bits(a)))),20))
    end for

    -- Round 3
    for step=33 to 48 by 4 do
        a = r32(b+rol(r32(a+block[m_block[step  ]]+K[step  ]+uxor(b,xor_bits(c,d))), 4))
        d = r32(a+rol(r32(d+block[m_block[step+1]]+K[step+1]+uxor(a,xor_bits(b,c))),11))
        c = r32(d+rol(r32(c+block[m_block[step+2]]+K[step+2]+uxor(d,xor_bits(a,b))),16))
        b = r32(c+rol(r32(b+block[m_block[step+3]]+K[step+3]+uxor(c,xor_bits(d,a))),23))
    end for

    -- Round 4
    for step=49 to 64 by 4 do
        a = r32(b+rol(r32(a+block[m_block[step  ]]+K[step  ]+uxor(c,or_bits(b,not_bits(d)))), 6))
        d = r32(a+rol(r32(d+block[m_block[step+1]]+K[step+1]+uxor(b,or_bits(a,not_bits(c)))),10))
        c = r32(d+rol(r32(c+block[m_block[step+2]]+K[step+2]+uxor(a,or_bits(d,not_bits(b)))),15))
        b = r32(c+rol(r32(b+block[m_block[step+3]]+K[step+3]+uxor(d,or_bits(c,not_bits(a)))),21))
    end for

    -- Update the words
    words = deep_copy(words)
    words[1] = r32(words[1]+a)
    words[2] = r32(words[2]+b)
    words[3] = r32(words[3]+c)
    words[4] = r32(words[4]+d)
end procedure

function pad_message(sequence message)
-- Add bytes to the end of the message so it can be divided
-- in an exact number of 64-byte blocks.
    integer l = length(message),
            bytes_to_add = 64-remainder(l+9,64)
    if bytes_to_add=64 then bytes_to_add = 0 end if

    message = deep_copy(message)
    message &= #80
    message &= repeat(0,bytes_to_add)
    message &= int_to_bytes(l*8)&{0,0,0,0}

    return message
end function

global function md5(sequence message)
-- Given a string, returns a 16-byte hash of it.

    words = c_words -- Initialize the H words

    message = pad_message(message)  -- Add bytes to the message

    -- Process each 64-byte block
    for block=1 to length(message) by 64 do
        process_block(message[block..block+63])
    end for

    -- Convert hash into bytes
    return int_to_bytes(words[1])&    -- Return the hash
           int_to_bytes(words[2])&
           int_to_bytes(words[3])&
           int_to_bytes(words[4])

end function

if platform()=JS or include_file()=1 then
    string fmt = "0x%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X\n"

    printf(1,fmt,md5(""))
    printf(1,fmt,md5("a"))
    printf(1,fmt,md5("abc"))
    printf(1,fmt,md5("message digest"))
    printf(1,fmt,md5("abcdefghijklmnopqrstuvwxyz"))
    printf(1,fmt,md5("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"))
    printf(1,fmt,md5("12345678901234567890123456789012345678901234567890123456789012345678901234567890"))
end if


import math

rotate_amounts = [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]

constants = [int(abs(math.sin(i+1)) * 2**32) & 0xFFFFFFFF for i in range(64)]

init_values = [0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476]

functions = 16*[lambda b, c, d: (b & c) | (~b & d)] + \
            16*[lambda b, c, d: (d & b) | (~d & c)] + \
            16*[lambda b, c, d: b ^ c ^ d] + \
            16*[lambda b, c, d: c ^ (b | ~d)]

index_functions = 16*[lambda i: i] + \
                  16*[lambda i: (5*i + 1)%16] + \
                  16*[lambda i: (3*i + 5)%16] + \
                  16*[lambda i: (7*i)%16]

def left_rotate(x, amount):
    x &= 0xFFFFFFFF
    return ((x<<amount) | (x>>(32-amount))) & 0xFFFFFFFF

def md5(message):

    message = bytearray(message) #copy our input into a mutable buffer
    orig_len_in_bits = (8 * len(message)) & 0xffffffffffffffff
    message.append(0x80)
    while len(message)%64 != 56:
        message.append(0)
    message += orig_len_in_bits.to_bytes(8, byteorder='little')

    hash_pieces = init_values[:]

    for chunk_ofst in range(0, len(message), 64):
        a, b, c, d = hash_pieces
        chunk = message[chunk_ofst:chunk_ofst+64]
        for i in range(64):
            f = functions[i](b, c, d)
            g = index_functions[i](i)
            to_rotate = a + f + constants[i] + int.from_bytes(chunk[4*g:4*g+4], byteorder='little')
            new_b = (b + left_rotate(to_rotate, rotate_amounts[i])) & 0xFFFFFFFF
            a, b, c, d = d, new_b, b, c
        for i, val in enumerate([a, b, c, d]):
            hash_pieces[i] += val
            hash_pieces[i] &= 0xFFFFFFFF

    return sum(x<<(32*i) for i, x in enumerate(hash_pieces))

def md5_to_hex(digest):
    raw = digest.to_bytes(16, byteorder='little')
    return '{:032x}'.format(int.from_bytes(raw, byteorder='big'))

if __name__=='__main__':
    demo = [b"", b"a", b"abc", b"message digest", b"abcdefghijklmnopqrstuvwxyz",
            b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
            b"12345678901234567890123456789012345678901234567890123456789012345678901234567890"]
    for message in demo:
        print(md5_to_hex(md5(message)),' <= "',message.decode('ascii'),'"', sep='')


#lang racket
(require file/md5)
(md5 #"Rosetta Code")


/*REXX program tests the MD5 procedure (below) as per a test suite from IETF RFC (1321).*/
@.1 =                                            /*─────MD5 test suite [from above doc].*/
@.2 = 'a'
@.3 = 'abc'
@.4 = 'message digest'
@.5 = 'abcdefghijklmnopqrstuvwxyz'
@.6 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
@.7 =  12345678901234567890123456789012345678901234567890123456789012345678901234567890
@.0 = 7                                          /* [↑]  last value doesn't need quotes.*/
                do m=1  for  @.0;         say    /*process each of the seven messages.  */
                say ' in ='  @.m                 /*display the      in      message.    */
                say 'out ='  MD5(@.m)            /*   "     "       out        "        */
                end   /*m*/
exit                                             /*stick a fork in it,  we're all done. */
/*──────────────────────────────────────────────────────────────────────────────────────*/
MD5: procedure; parse arg !;  numeric digits 20  /*insure there's enough decimal digits.*/
     a= '67452301'x;         b= "efcdab89"x;         c= '98badcfe'x;        d= "10325476"x
     #= length(!)                                /*length in bytes of the input message.*/
     L= #*8//512;   if L<448  then plus= 448 - L /*is the length  less   than  448 ?    */
                    if L>448  then plus= 960 - L /* "  "     "   greater   "    "       */
                    if L=448  then plus= 512     /* "  "     "    equal   to    "       */
                                                 /* [↓]  a little of this, ···          */
     $=! || "80"x || copies('0'x,plus%8-1)reverse(right(d2c(8*#), 4, '0'x)) || '00000000'x
                                                 /* [↑]       ···  and a little of that.*/
         do j=0  for length($) % 64              /*process the message  (lots of steps).*/
         a_= a;     b_= b;     c_= c;      d_= d /*save the  original values  for later.*/
         chunk= j * 64                           /*calculate the  size  of the chunks.  */
                       do k=1  for 16            /*process the message in chunks.       */
                       !.k= reverse( substr($, chunk + 1 + 4*(k-1), 4) )  /*magic stuff.*/
                       end   /*k*/                                        /*────step────*/
         a  =  .p1( a,   b,   c,   d,    0,    7,   3614090360)           /*■■■■  1 ■■■■*/
         d  =  .p1( d,   a,   b,   c,    1,   12,   3905402710)           /*■■■■  2 ■■■■*/
         c  =  .p1( c,   d,   a,   b,    2,   17,    606105819)           /*■■■■  3 ■■■■*/
         b  =  .p1( b,   c,   d,   a,    3,   22,   3250441966)           /*■■■■  4 ■■■■*/
         a  =  .p1( a,   b,   c,   d,    4,    7,   4118548399)           /*■■■■  5 ■■■■*/
         d  =  .p1( d,   a,   b,   c,    5,   12,   1200080426)           /*■■■■  6 ■■■■*/
         c  =  .p1( c,   d,   a,   b,    6,   17,   2821735955)           /*■■■■  7 ■■■■*/
         b  =  .p1( b,   c,   d,   a,    7,   22,   4249261313)           /*■■■■  8 ■■■■*/
         a  =  .p1( a,   b,   c,   d,    8,    7,   1770035416)           /*■■■■  9 ■■■■*/
         d  =  .p1( d,   a,   b,   c,    9,   12,   2336552879)           /*■■■■ 10 ■■■■*/
         c  =  .p1( c,   d,   a,   b,   10,   17,   4294925233)           /*■■■■ 11 ■■■■*/
         b  =  .p1( b,   c,   d,   a,   11,   22,   2304563134)           /*■■■■ 12 ■■■■*/
         a  =  .p1( a,   b,   c,   d,   12,    7,   1804603682)           /*■■■■ 13 ■■■■*/
         d  =  .p1( d,   a,   b,   c,   13,   12,   4254626195)           /*■■■■ 14 ■■■■*/
         c  =  .p1( c,   d,   a,   b,   14,   17,   2792965006)           /*■■■■ 15 ■■■■*/
         b  =  .p1( b,   c,   d,   a,   15,   22,   1236535329)           /*■■■■ 16 ■■■■*/
         a  =  .p2( a,   b,   c,   d,    1,    5,   4129170786)           /*■■■■ 17 ■■■■*/
         d  =  .p2( d,   a,   b,   c,    6,    9,   3225465664)           /*■■■■ 18 ■■■■*/
         c  =  .p2( c,   d,   a,   b,   11,   14,    643717713)           /*■■■■ 19 ■■■■*/
         b  =  .p2( b,   c,   d,   a,    0,   20,   3921069994)           /*■■■■ 20 ■■■■*/
         a  =  .p2( a,   b,   c,   d,    5,    5,   3593408605)           /*■■■■ 21 ■■■■*/
         d  =  .p2( d,   a,   b,   c,   10,    9,     38016083)           /*■■■■ 22 ■■■■*/
         c  =  .p2( c,   d,   a,   b,   15,   14,   3634488961)           /*■■■■ 23 ■■■■*/
         b  =  .p2( b,   c,   d,   a,    4,   20,   3889429448)           /*■■■■ 24 ■■■■*/
         a  =  .p2( a,   b,   c,   d,    9,    5,    568446438)           /*■■■■ 25 ■■■■*/
         d  =  .p2( d,   a,   b,   c,   14,    9,   3275163606)           /*■■■■ 26 ■■■■*/
         c  =  .p2( c,   d,   a,   b,    3,   14,   4107603335)           /*■■■■ 27 ■■■■*/
         b  =  .p2( b,   c,   d,   a,    8,   20,   1163531501)           /*■■■■ 28 ■■■■*/
         a  =  .p2( a,   b,   c,   d,   13,    5,   2850285829)           /*■■■■ 29 ■■■■*/
         d  =  .p2( d,   a,   b,   c,    2,    9,   4243563512)           /*■■■■ 30 ■■■■*/
         c  =  .p2( c,   d,   a,   b,    7,   14,   1735328473)           /*■■■■ 31 ■■■■*/
         b  =  .p2( b,   c,   d,   a,   12,   20,   2368359562)           /*■■■■ 32 ■■■■*/
         a  =  .p3( a,   b,   c,   d,    5,    4,   4294588738)           /*■■■■ 33 ■■■■*/
         d  =  .p3( d,   a,   b,   c,    8,   11,   2272392833)           /*■■■■ 34 ■■■■*/
         c  =  .p3( c,   d,   a,   b,   11,   16,   1839030562)           /*■■■■ 35 ■■■■*/
         b  =  .p3( b,   c,   d,   a,   14,   23,   4259657740)           /*■■■■ 36 ■■■■*/
         a  =  .p3( a,   b,   c,   d,    1,    4,   2763975236)           /*■■■■ 37 ■■■■*/
         d  =  .p3( d,   a,   b,   c,    4,   11,   1272893353)           /*■■■■ 38 ■■■■*/
         c  =  .p3( c,   d,   a,   b,    7,   16,   4139469664)           /*■■■■ 39 ■■■■*/
         b  =  .p3( b,   c,   d,   a,   10,   23,   3200236656)           /*■■■■ 40 ■■■■*/
         a  =  .p3( a,   b,   c,   d,   13,    4,    681279174)           /*■■■■ 41 ■■■■*/
         d  =  .p3( d,   a,   b,   c,    0,   11,   3936430074)           /*■■■■ 42 ■■■■*/
         c  =  .p3( c,   d,   a,   b,    3,   16,   3572445317)           /*■■■■ 43 ■■■■*/
         b  =  .p3( b,   c,   d,   a,    6,   23,     76029189)           /*■■■■ 44 ■■■■*/
         a  =  .p3( a,   b,   c,   d,    9,    4,   3654602809)           /*■■■■ 45 ■■■■*/
         d  =  .p3( d,   a,   b,   c,   12,   11,   3873151461)           /*■■■■ 46 ■■■■*/
         c  =  .p3( c,   d,   a,   b,   15,   16,    530742520)           /*■■■■ 47 ■■■■*/
         b  =  .p3( b,   c,   d,   a,    2,   23,   3299628645)           /*■■■■ 48 ■■■■*/
         a  =  .p4( a,   b,   c,   d,    0,    6,   4096336452)           /*■■■■ 49 ■■■■*/
         d  =  .p4( d,   a,   b,   c,    7,   10,   1126891415)           /*■■■■ 50 ■■■■*/
         c  =  .p4( c,   d,   a,   b,   14,   15,   2878612391)           /*■■■■ 51 ■■■■*/
         b  =  .p4( b,   c,   d,   a,    5,   21,   4237533241)           /*■■■■ 52 ■■■■*/
         a  =  .p4( a,   b,   c,   d,   12,    6,   1700485571)           /*■■■■ 53 ■■■■*/
         d  =  .p4( d,   a,   b,   c,    3,   10,   2399980690)           /*■■■■ 54 ■■■■*/
         c  =  .p4( c,   d,   a,   b,   10,   15,   4293915773)           /*■■■■ 55 ■■■■*/
         b  =  .p4( b,   c,   d,   a,    1,   21,   2240044497)           /*■■■■ 56 ■■■■*/
         a  =  .p4( a,   b,   c,   d,    8,    6,   1873313359)           /*■■■■ 57 ■■■■*/
         d  =  .p4( d,   a,   b,   c,   15,   10,   4264355552)           /*■■■■ 58 ■■■■*/
         c  =  .p4( c,   d,   a,   b,    6,   15,   2734768916)           /*■■■■ 59 ■■■■*/
         b  =  .p4( b,   c,   d,   a,   13,   21,   1309151649)           /*■■■■ 60 ■■■■*/
         a  =  .p4( a,   b,   c,   d,    4,    6,   4149444226)           /*■■■■ 61 ■■■■*/
         d  =  .p4( d,   a,   b,   c,   11,   10,   3174756917)           /*■■■■ 62 ■■■■*/
         c  =  .p4( c,   d,   a,   b,    2,   15,    718787259)           /*■■■■ 63 ■■■■*/
         b  =  .p4( b,   c,   d,   a,    9,   21,   3951481745)           /*■■■■ 64 ■■■■*/
         a  =  .a(a_, a);         b=.a(b_, b);          c=.a(c_, c);        d=.a(d_, d)
         end   /*j*/
     return .rx(a).rx(b).rx(c).rx(d)             /*same as:  .rx(a) || .rx(b) ||  ···   */
/*──────────────────────────────────────────────────────────────────────────────────────*/
.a:  return  right( d2c( c2d( arg(1) )   +   c2d( arg(2) ) ),  4, '0'x)
.h:  return  bitxor( bitxor( arg(1), arg(2) ), arg(3) )
.i:  return  bitxor( arg(2), bitor(arg(1),  bitxor(arg(3),        'ffffffff'x) ) )
.f:  return  bitor( bitand(arg(1), arg(2)), bitand(bitxor(arg(1), 'ffffffff'x), arg(3) ) )
.g:  return  bitor( bitand(arg(1), arg(3)), bitand(arg(2), bitxor(arg(3), 'ffffffff'x) ) )
.rx: return  c2x( reverse( arg(1) ) )
.Lr: procedure;  parse arg _,#;    if #==0  then return _             /*left bit rotate.*/
                 ?=x2b(c2x(_));    return x2c( b2x( right(? || left(?, #), length(?) ) ) )
.p1: procedure expose !.;   parse arg w,x,y,z,n,m,_;             n=n + 1
               return .a(.Lr(right(d2c(_+c2d(w) + c2d(.f(x,y,z)) + c2d(!.n)),4,'0'x),m),x)
.p2: procedure expose !.;   parse arg w,x,y,z,n,m,_;             n=n + 1
               return .a(.Lr(right(d2c(_+c2d(w) + c2d(.g(x,y,z)) + c2d(!.n)),4,'0'x),m),x)
.p3: procedure expose !.;   parse arg w,x,y,z,n,m,_;             n=n + 1
               return .a(.Lr(right(d2c(_+c2d(w) + c2d(.h(x,y,z)) + c2d(!.n)),4,'0'x),m),x)
.p4: procedure expose !.;   parse arg w,x,y,z,n,m,_;             n=n + 1
               return .a(.Lr(right(d2c(c2d(w) + c2d(.i(x,y,z)) + c2d(!.n)+_),4,'0'x),m),x)


**FREE
Ctl-opt MAIN(Main);
Ctl-opt DFTACTGRP(*NO) ACTGRP(*NEW);

dcl-pr QDCXLATE EXTPGM('QDCXLATE');
  dataLen         packed(5 : 0) CONST;
  data            char(32767) options(*VARSIZE);
  conversionTable char(10) CONST;
end-pr;

dcl-c MASK32 CONST(4294967295);
dcl-s SHIFT_AMTS int(3) dim(16) CTDATA PERRCD(16);
dcl-s MD5_TABLE_T int(20) dim(64) CTDATA PERRCD(4);

dcl-proc Main;
  dcl-s inputData char(45);
  dcl-s inputDataLen int(10) INZ(0);
  dcl-s outputHash char(16);
  dcl-s outputHashHex char(32);

  DSPLY 'Input: ' '' inputData;
  inputData = %trim(inputData);
  inputDataLen = %len(%trim(inputData));
  DSPLY ('Input=' + inputData);
  DSPLY ('InputLen=' + %char(inputDataLen));

  // Convert from EBCDIC to ASCII
  if inputDataLen > 0;
    QDCXLATE(inputDataLen : inputData : 'QTCPASC');
  endif;
  CalculateMD5(inputData : inputDataLen : outputHash);
  // Convert to hex
  ConvertToHex(outputHash : 16 : outputHashHex);
  DSPLY ('MD5: ' + outputHashHex);
  return;
end-proc;

dcl-proc CalculateMD5;
  dcl-pi *N;
    message    char(65535) options(*VARSIZE) CONST;
    messageLen int(10) value;
    outputHash char(16);
  end-pi;
  dcl-s numBlocks int(10);
  dcl-s padding char(72);
  dcl-s a int(20) INZ(1732584193);
  dcl-s b int(20) INZ(4023233417);
  dcl-s c int(20) INZ(2562383102);
  dcl-s d int(20) INZ(271733878);
  dcl-s buffer int(20) dim(16) INZ(0);
  dcl-s i int(10);
  dcl-s j int(10);
  dcl-s k int(10);
  dcl-s multiplier int(20);
  dcl-s index int(10);
  dcl-s originalA int(20);
  dcl-s originalB int(20);
  dcl-s originalC int(20);
  dcl-s originalD int(20);
  dcl-s div16 int(10);
  dcl-s f int(20);
  dcl-s tempInt int(20);
  dcl-s bufferIndex int(10);
  dcl-ds byteToInt QUALIFIED;
    n int(5) INZ(0);
    c char(1) OVERLAY(n : 2);
  end-ds;

  numBlocks = (messageLen + 8) / 64 + 1;
  MD5_FillPadding(messageLen : numBlocks : padding);
  for i = 0 to numBlocks - 1;
    index = i * 64;

    // Read message as little-endian 32-bit words
    for j = 1 to 16;
      multiplier = 1;
      for k = 1 to 4;
        index += 1;
        if index <= messageLen;
          byteToInt.c = %subst(message : index : 1);
        else;
          byteToInt.c = %subst(padding : index - messageLen : 1);
        endif;
        buffer(j) += multiplier * byteToInt.n;
        multiplier *= 256;
      endfor;
    endfor;

    originalA = a;
    originalB = b;
    originalC = c;
    originalD = d;

    for j = 0 to 63;
      div16 = j / 16;
      select;
        when div16 = 0;
          f = %bitor(%bitand(b : c) : %bitand(%bitnot(b) : d));
          bufferIndex = j;

        when div16 = 1;
          f = %bitor(%bitand(b : d) : %bitand(c : %bitnot(d)));
          bufferIndex = %bitand(j * 5 + 1 : 15);

        when div16 = 2;
          f = %bitxor(b : %bitxor(c : d));
          bufferIndex = %bitand(j * 3 + 5 : 15);

        when div16 = 3;
          f = %bitxor(c : %bitor(b : Mask32Bit(%bitnot(d))));
          bufferIndex = %bitand(j * 7 : 15);
      endsl;
      tempInt = Mask32Bit(b + RotateLeft32Bit(a + f + buffer(bufferIndex + 1) + MD5_TABLE_T(j + 1) :
                                              SHIFT_AMTS(div16 * 4 + %bitand(j : 3) + 1)));
      a = d;
      d = c;
      c = b;
      b = tempInt;
    endfor;
    a = Mask32Bit(a + originalA);
    b = Mask32Bit(b + originalB);
    c = Mask32Bit(c + originalC);
    d = Mask32Bit(d + originalD);
  endfor;

  for i = 0 to 3;
    if i = 0;
      tempInt = a;
    elseif i = 1;
      tempInt = b;
    elseif i = 2;
      tempInt = c;
    else;
      tempInt = d;
    endif;

    for j = 0 to 3;
      byteToInt.n = %bitand(tempInt : 255);
      %subst(outputHash : i * 4 + j + 1 : 1) = byteToInt.c;
      tempInt /= 256;
    endfor;
  endfor;
  return;
end-proc;

dcl-proc MD5_FillPadding;
  dcl-pi *N;
    messageLen int(10);
    numBlocks int(10);
    padding char(72);
  end-pi;
  dcl-s totalLen int(10);
  dcl-s paddingSize int(10);
  dcl-ds *N;
    messageLenBits int(20);
    mlb_bytes char(8) OVERLAY(messageLenBits);
  end-ds;
  dcl-s i int(10);

  %subst(padding : 1 : 1) = X'80';
  totalLen = numBlocks * 64;
  paddingSize = totalLen - messageLen; // 9 to 72
  messageLenBits = messageLen;
  messageLenBits *= 8;
  for i = 1 to 8;
    %subst(padding : paddingSize - i + 1 : 1) = %subst(mlb_bytes : i : 1);
  endfor;
  for i = 2 to paddingSize - 8;
    %subst(padding : i : 1) = X'00';
  endfor;
  return;
end-proc;

dcl-proc RotateLeft32Bit;
  dcl-pi *N int(20);
    n int(20) value;
    amount int(3) value;
  end-pi;
  dcl-s i int(3);

  n = Mask32Bit(n);
  for i = 1 to amount;
    n *= 2;
    if n >= 4294967296;
      n -= MASK32;
    endif;
  endfor;
  return n;
end-proc;

dcl-proc Mask32Bit;
  dcl-pi *N int(20);
    n int(20) value;
  end-pi;
  return %bitand(n : MASK32);
end-proc;

dcl-proc ConvertToHex;
  dcl-pi *N;
    inputData    char(32767) options(*VARSIZE) CONST;
    inputDataLen int(10) value;
    outputData   char(65534) options(*VARSIZE);
  end-pi;
  dcl-c HEX_CHARS CONST('0123456789ABCDEF');
  dcl-s i int(10);
  dcl-s outputOffset int(10) INZ(1);
  dcl-ds dataStruct QUALIFIED;
    numField int(5) INZ(0);
    // IBM i is big-endian
    charField char(1) OVERLAY(numField : 2);
  end-ds;

  for i = 1 to inputDataLen;
    dataStruct.charField = %BitAnd(%subst(inputData : i : 1) : X'F0');
    dataStruct.numField /= 16;
    %subst(outputData : outputOffset : 1) = %subst(HEX_CHARS : dataStruct.numField + 1 : 1);
    outputOffset += 1;
    dataStruct.charField = %BitAnd(%subst(inputData : i : 1) : X'0F');
    %subst(outputData : outputOffset : 1) = %subst(HEX_CHARS : dataStruct.numField + 1 : 1);
    outputOffset += 1;
  endfor;
  return;
end-proc;

**CTDATA SHIFT_AMTS
  7 12 17 22  5  9 14 20  4 11 16 23  6 10 15 21
**CTDATA MD5_TABLE_T
          3614090360          3905402710           606105819          3250441966
          4118548399          1200080426          2821735955          4249261313
          1770035416          2336552879          4294925233          2304563134
          1804603682          4254626195          2792965006          1236535329
          4129170786          3225465664           643717713          3921069994
          3593408605            38016083          3634488961          3889429448
           568446438          3275163606          4107603335          1163531501
          2850285829          4243563512          1735328473          2368359562
          4294588738          2272392833          1839030562          4259657740
          2763975236          1272893353          4139469664          3200236656
           681279174          3936430074          3572445317            76029189
          3654602809          3873151461           530742520          3299628645
          4096336452          1126891415          2878612391          4237533241
          1700485571          2399980690          4293915773          2240044497
          1873313359          4264355552          2734768916          1309151649
          4149444226          3174756917           718787259          3951481745


#![allow(non_snake_case)] // RFC 1321 uses many capitalized variables
use std::mem;

fn md5(mut msg: Vec<u8>) -> (u32, u32, u32, u32) {
    let bitcount = msg.len().saturating_mul(8) as u64;

    // Step 1: Append Padding Bits
    msg.push(0b10000000);
    while (msg.len() * 8) % 512 != 448 {
        msg.push(0u8);
    }

    // Step 2. Append Length  (64 bit integer)
    msg.extend(&[
        bitcount as u8,
        (bitcount >> 8) as u8,
        (bitcount >> 16) as u8,
        (bitcount >> 24) as u8,
        (bitcount >> 32) as u8,
        (bitcount >> 40) as u8,
        (bitcount >> 48) as u8,
        (bitcount >> 56) as u8,
    ]);

    // Step 3. Initialize MD Buffer
    /*A four-word buffer (A,B,C,D) is used to compute the message digest.
    Here each of A, B, C, D is a 32-bit register.*/
    let mut A = 0x67452301u32;
    let mut B = 0xefcdab89u32;
    let mut C = 0x98badcfeu32;
    let mut D = 0x10325476u32;

    // Step 4. Process Message in 16-Word Blocks
    /* We first define four auxiliary functions */
    let F = |X: u32, Y: u32, Z: u32| -> u32 { X & Y | !X & Z };
    let G = |X: u32, Y: u32, Z: u32| -> u32 { X & Z | Y & !Z };
    let H = |X: u32, Y: u32, Z: u32| -> u32 { X ^ Y ^ Z };
    let I = |X: u32, Y: u32, Z: u32| -> u32 { Y ^ (X | !Z) };

    /* This step uses a 64-element table T[1 ... 64] constructed from the sine function.  */
    let T = [
        0x00000000, // enable use as a 1-indexed table
        0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, 0xa8304613,
        0xfd469501, 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 0x6b901122, 0xfd987193,
        0xa679438e, 0x49b40821, 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 0xd62f105d,
        0x02441453, 0xd8a1e681, 0xe7d3fbc8, 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed,
        0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, 0xfffa3942, 0x8771f681, 0x6d9d6122,
        0xfde5380c, 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, 0x289b7ec6, 0xeaa127fa,
        0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, 0xf4292244,
        0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
        0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 0xf7537e82, 0xbd3af235, 0x2ad7d2bb,
        0xeb86d391,
    ];

    /* Process each 16-word block. (since 1 word is 4 bytes, then 16 words is 64 bytes) */
    for mut block in msg.chunks_exact_mut(64) {
        /* Copy block into X. */
        #![allow(unused_mut)]
        let mut X = unsafe { mem::transmute::<&mut [u8], &mut [u32]>(&mut block) };
        #[cfg(target_endian = "big")]
        for j in 0..16 {
            X[j] = X[j].swap_bytes();
        }

        /* Save Registers A,B,C,D */
        let AA = A;
        let BB = B;
        let CC = C;
        let DD = D;

        /* Round 1.  Let [abcd k s i] denote the operation
        a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s). */
        macro_rules! op1 {
            ($a:ident,$b:ident,$c:ident,$d:ident,$k:expr,$s:expr,$i:expr) => {
                $a = $b.wrapping_add(
                    ($a.wrapping_add(F($b, $c, $d))
                        .wrapping_add(X[$k])
                        .wrapping_add(T[$i]))
                    .rotate_left($s),
                )
            };
        }

        /* Do the following 16 operations. */
        op1!(A, B, C, D, 0, 7, 1);
        op1!(D, A, B, C, 1, 12, 2);
        op1!(C, D, A, B, 2, 17, 3);
        op1!(B, C, D, A, 3, 22, 4);

        op1!(A, B, C, D, 4, 7, 5);
        op1!(D, A, B, C, 5, 12, 6);
        op1!(C, D, A, B, 6, 17, 7);
        op1!(B, C, D, A, 7, 22, 8);

        op1!(A, B, C, D, 8, 7, 9);
        op1!(D, A, B, C, 9, 12, 10);
        op1!(C, D, A, B, 10, 17, 11);
        op1!(B, C, D, A, 11, 22, 12);

        op1!(A, B, C, D, 12, 7, 13);
        op1!(D, A, B, C, 13, 12, 14);
        op1!(C, D, A, B, 14, 17, 15);
        op1!(B, C, D, A, 15, 22, 16);

        /* Round 2. Let [abcd k s i] denote the operation
        a = b + ((a + G(b,c,d) + X[k] + T[i]) <<< s). */
        macro_rules! op2 {
            ($a:ident,$b:ident,$c:ident,$d:ident,$k:expr,$s:expr,$i:expr) => {
                $a = $b.wrapping_add(
                    ($a.wrapping_add(G($b, $c, $d))
                        .wrapping_add(X[$k])
                        .wrapping_add(T[$i]))
                    .rotate_left($s),
                )
            };
        }

        /* Do the following 16 operations. */
        op2!(A, B, C, D, 1, 5, 17);
        op2!(D, A, B, C, 6, 9, 18);
        op2!(C, D, A, B, 11, 14, 19);
        op2!(B, C, D, A, 0, 20, 20);

        op2!(A, B, C, D, 5, 5, 21);
        op2!(D, A, B, C, 10, 9, 22);
        op2!(C, D, A, B, 15, 14, 23);
        op2!(B, C, D, A, 4, 20, 24);

        op2!(A, B, C, D, 9, 5, 25);
        op2!(D, A, B, C, 14, 9, 26);
        op2!(C, D, A, B, 3, 14, 27);
        op2!(B, C, D, A, 8, 20, 28);

        op2!(A, B, C, D, 13, 5, 29);
        op2!(D, A, B, C, 2, 9, 30);
        op2!(C, D, A, B, 7, 14, 31);
        op2!(B, C, D, A, 12, 20, 32);

        /* Round 3. Let [abcd k s t] denote the operation
        a = b + ((a + H(b,c,d) + X[k] + T[i]) <<< s). */
        macro_rules! op3 {
            ($a:ident,$b:ident,$c:ident,$d:ident,$k:expr,$s:expr,$i:expr) => {
                $a = $b.wrapping_add(
                    ($a.wrapping_add(H($b, $c, $d))
                        .wrapping_add(X[$k])
                        .wrapping_add(T[$i]))
                    .rotate_left($s),
                )
            };
        }

        /* Do the following 16 operations. */
        op3!(A, B, C, D, 5, 4, 33);
        op3!(D, A, B, C, 8, 11, 34);
        op3!(C, D, A, B, 11, 16, 35);
        op3!(B, C, D, A, 14, 23, 36);

        op3!(A, B, C, D, 1, 4, 37);
        op3!(D, A, B, C, 4, 11, 38);
        op3!(C, D, A, B, 7, 16, 39);
        op3!(B, C, D, A, 10, 23, 40);

        op3!(A, B, C, D, 13, 4, 41);
        op3!(D, A, B, C, 0, 11, 42);
        op3!(C, D, A, B, 3, 16, 43);
        op3!(B, C, D, A, 6, 23, 44);

        op3!(A, B, C, D, 9, 4, 45);
        op3!(D, A, B, C, 12, 11, 46);
        op3!(C, D, A, B, 15, 16, 47);
        op3!(B, C, D, A, 2, 23, 48);

        /* Round 4. Let [abcd k s t] denote the operation
        a = b + ((a + I(b,c,d) + X[k] + T[i]) <<< s). */
        macro_rules! op4 {
            ($a:ident,$b:ident,$c:ident,$d:ident,$k:expr,$s:expr,$i:expr) => {
                $a = $b.wrapping_add(
                    ($a.wrapping_add(I($b, $c, $d))
                        .wrapping_add(X[$k])
                        .wrapping_add(T[$i]))
                    .rotate_left($s),
                )
            };
        }

        /* Do the following 16 operations. */
        op4!(A, B, C, D, 0, 6, 49);
        op4!(D, A, B, C, 7, 10, 50);
        op4!(C, D, A, B, 14, 15, 51);
        op4!(B, C, D, A, 5, 21, 52);

        op4!(A, B, C, D, 12, 6, 53);
        op4!(D, A, B, C, 3, 10, 54);
        op4!(C, D, A, B, 10, 15, 55);
        op4!(B, C, D, A, 1, 21, 56);

        op4!(A, B, C, D, 8, 6, 57);
        op4!(D, A, B, C, 15, 10, 58);
        op4!(C, D, A, B, 6, 15, 59);
        op4!(B, C, D, A, 13, 21, 60);

        op4!(A, B, C, D, 4, 6, 61);
        op4!(D, A, B, C, 11, 10, 62);
        op4!(C, D, A, B, 2, 15, 63);
        op4!(B, C, D, A, 9, 21, 64);

        /* . . . increment each of the four registers by the value
        it had before this block was started.) */

        A = A.wrapping_add(AA);
        B = B.wrapping_add(BB);
        C = C.wrapping_add(CC);
        D = D.wrapping_add(DD);
    }
    (
        A.swap_bytes(),
        B.swap_bytes(),
        C.swap_bytes(),
        D.swap_bytes(),
    )
}

fn md5_utf8(smsg: &str) -> String {
    let mut msg = vec![0u8; 0];
    msg.extend(smsg.as_bytes());
    let (A, B, C, D) = md5(msg);
    format!("{:08x}{:08x}{:08x}{:08x}", A, B, C, D)
}

fn main() {
    assert!(md5_utf8("") == "d41d8cd98f00b204e9800998ecf8427e");
    assert!(md5_utf8("a") == "0cc175b9c0f1b6a831c399e269772661");
    assert!(md5_utf8("abc") == "900150983cd24fb0d6963f7d28e17f72");
    assert!(md5_utf8("message digest") == "f96b697d7cb7938d525a2f31aaf161d0");
    assert!(md5_utf8("abcdefghijklmnopqrstuvwxyz") == "c3fcd3d76192e4007dfb496cca67e13b");
    assert!(md5_utf8("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789") == "d174ab98d277d9f5a5611c2c9f419d9f");
    assert!(md5_utf8("12345678901234567890123456789012345678901234567890123456789012345678901234567890") == "57edf4a22be3c955ac49da2e2107b67a");
}


object MD5 extends App {

  def hash(s: String) = {
    def b = s.getBytes("UTF-8")

    def m = java.security.MessageDigest.getInstance("MD5").digest(b)

    BigInt(1, m).toString(16).reverse.padTo(32, "0").reverse.mkString
  }

  assert("d41d8cd98f00b204e9800998ecf8427e" == hash(""))
  assert("0000045c5e2b3911eb937d9d8c574f09" == hash("iwrupvqb346386"))
  assert("0cc175b9c0f1b6a831c399e269772661" == hash("a"))
  assert("900150983cd24fb0d6963f7d28e17f72" == hash("abc"))
  assert("f96b697d7cb7938d525a2f31aaf161d0" == hash("message digest"))
  assert("c3fcd3d76192e4007dfb496cca67e13b" == hash("abcdefghijklmnopqrstuvwxyz"))
  assert("d174ab98d277d9f5a5611c2c9f419d9f" == hash("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"))
  assert("57edf4a22be3c955ac49da2e2107b67a" == hash("12345678901234567890123456789012345678901234567890123456789012345678901234567890"))

}


$ include "seed7_05.s7i";
  include "bytedata.s7i";
  include "bin32.s7i";
  include "float.s7i";
  include "math.s7i";

# Use binary integer part of the sines of integers (Radians) as constants:
const func array integer: createMd5Table is func
  result
    var array integer: k is 64 times 0;
  local
    var integer: index is 0;
  begin
    for index range 1 to 64 do
      k[index] := trunc(abs(sin(flt(index))) * 2.0 ** 32);
    end for;
  end func;

const func string: md5 (in var string: message) is func
  result
    var string: digest is "";
  local
    # Specify the per-round shift amounts
    const array integer: shiftAmount is [] (
        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);
    const array integer: k is createMd5Table;
    var integer: length is 0;
    var integer: chunkIndex is 0;
    var integer: index is 0;
    var array bin32: m is 16 times bin32.value;
    var integer: a0 is 16#67452301;   # a
    var integer: b0 is 16#efcdab89;   # b
    var integer: c0 is 16#98badcfe;   # c
    var integer: d0 is 16#10325476;   # d
    var bin32: a is bin32(0);
    var bin32: b is bin32(0);
    var bin32: c is bin32(0);
    var bin32: d is bin32(0);
    var bin32: f is bin32(0);
    var integer: g is 0;
    var bin32: temp is bin32(0);
  begin
    length := length(message);
    # Append the bit '1' to the message.
    message &:= '\16#80;';
    # Append '0' bits, so that the resulting bit length is congruent to 448 (mod 512).
    message &:= "\0;" mult 63 - (length + 8) mod 64;
    # Append length of message (before pre-processing), in bits, as 64-bit little-endian integer.
    message &:= int64AsEightBytesLe(8 * length);

    # Process the message in successive 512-bit chunks:
    for chunkIndex range 1 to length(message) step 64 do
      # Break chunk into sixteen 32-bit little-endian words.
      for index range 1 to 16 do
        m[index] := bin32(bytes2Int(message[chunkIndex + 4 * pred(index) len 4], UNSIGNED, LE));
      end for;

      a := bin32(a0 mod 16#100000000);
      b := bin32(b0 mod 16#100000000);
      c := bin32(c0 mod 16#100000000);
      d := bin32(d0 mod 16#100000000);

      for index range 1 to 64 do
        if index <= 16 then
          f := d >< (b & (c >< d));
          g := index;
        elsif index <= 32 then
          f := c >< (d & (b >< c));
          g := (5 * index - 4) mod 16 + 1;
        elsif index <= 48 then
          f := b >< c >< d;
          g := (3 * index + 2) mod 16 + 1;
        else
          f := c >< (b | (bin32(16#ffffffff) >< d));
          g := (7 * pred(index)) mod 16 + 1;
        end if;

        temp := d;
        d := c;
        c := b;
        b := bin32((ord(b) +
             ord(rotLeft(bin32((ord(a) + ord(f) + k[index] + ord(m[g])) mod 16#100000000),
                         shiftAmount[index]))) mod 16#100000000);
        a := temp;
      end for;

      # Add this chunk's hash to result so far:
      a0 +:= ord(a);
      b0 +:= ord(b);
      c0 +:= ord(c);
      d0 +:= ord(d);
    end for;

    # Produce the final hash value:
    digest := int32AsFourBytesLe(a0) &
              int32AsFourBytesLe(b0) &
              int32AsFourBytesLe(c0) &
              int32AsFourBytesLe(d0);
  end func;

const func boolean: checkMd5 (in string: message, in string: hexMd5) is
  return hex(md5(message)) = hexMd5;

const proc: main is func
  begin
    if  checkMd5("", "d41d8cd98f00b204e9800998ecf8427e") and
        checkMd5("a", "0cc175b9c0f1b6a831c399e269772661") and
        checkMd5("abc", "900150983cd24fb0d6963f7d28e17f72") and
        checkMd5("message digest", "f96b697d7cb7938d525a2f31aaf161d0") and
        checkMd5("abcdefghijklmnopqrstuvwxyz", "c3fcd3d76192e4007dfb496cca67e13b") and
        checkMd5("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", "d174ab98d277d9f5a5611c2c9f419d9f") and
        checkMd5("12345678901234567890123456789012345678901234567890123456789012345678901234567890", "57edf4a22be3c955ac49da2e2107b67a") then
      writeln("md5 is computed correct");
    else
      writeln("There is an error in the md5 function");
    end if;
  end func;


class MD5(String msg) {

    method init {
        msg = msg.bytes
    }

    const FGHI = [
        {|a,b,c| (a & b) | (~a & c) },
        {|a,b,c| (a & c) | (b & ~c) },
        {|a,b,c| (a ^ b ^ c)        },
        {|a,b,c| (b ^ (a | ~c))     },
    ]

    const S = [
        [7, 12, 17, 22] * 4,
        [5,  9, 14, 20] * 4,
        [4, 11, 16, 23] * 4,
        [6, 10, 15, 21] * 4,
    ].flat

    const T = 64.of {|i| floor(abs(sin(i+1)) * 1<<32) }

    const K = [
        ^16 -> map {|n|    n           },
        ^16 -> map {|n| (5*n + 1) % 16 },
        ^16 -> map {|n| (3*n + 5) % 16 },
        ^16 -> map {|n| (7*n    ) % 16 },
    ].flat

    func radix(Number b, Array a) {
        ^a -> sum {|i| b**i * a[i] }
    }

    func little_endian(Number w, Number n, Array v) {
        var step1 = (^n »*» w)
        var step2 = (v ~X>> step1)
        step2 »%» (1 << w)
    }

    func block(Number a, Number b) { (a  + b) & 0xffffffff }
    func srble(Number a, Number n) { (a << n) & 0xffffffff | (a >> (32-n)) }

    func md5_pad(msg) {
        var bits = 8*msg.len
        var padded = [msg..., 128, [0] * (-(floor(bits / 8) + 1 + 8) % 64)].flat

        gather {
            padded.each_slice(4, {|*a|
                take(radix(256, a))
            })
            take(little_endian(32, 2, [bits]))
        }.flat
    }

    func md5_block(Array H, Array X) {
        var (A, B, C, D) = H...

        for i in ^64 {
            (A, B, C, D) = (D,
                block(B, srble(
                    block(
                        block(
                            block(A, FGHI[floor(i / 16)](B, C, D)), T[i]
                        ), X[K[i]]
                    ), S[i])
                ), B, C)
        }

        for k,v in ([A, B, C, D].kv) {
            H[k] = block(H[k], v)
        }

        return H
    }

    method md5_hex {
        self.md5.map {|n| '%02x' % n }.join
    }

    method md5 {
        var M = md5_pad(msg)
        var H = [0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476]

        for i in (range(0, M.end, 16)) {
            md5_block(H, M.ft(i, i+15))
        }

        little_endian(8, 4, H)
    }
}

var tests = [
    ['d41d8cd98f00b204e9800998ecf8427e', ''],
    ['0cc175b9c0f1b6a831c399e269772661', 'a'],
    ['900150983cd24fb0d6963f7d28e17f72', 'abc'],
    ['f96b697d7cb7938d525a2f31aaf161d0', 'message digest'],
    ['c3fcd3d76192e4007dfb496cca67e13b', 'abcdefghijklmnopqrstuvwxyz'],
    ['d174ab98d277d9f5a5611c2c9f419d9f', 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'],
    ['57edf4a22be3c955ac49da2e2107b67a', '12345678901234567890123456789012345678901234567890123456789012345678901234567890'],
]

for md5,msg in tests {
    var hash = MD5(msg).md5_hex
    say "#{hash} : #{msg}"

    if (hash != md5) {
        say "\tHowever, that is incorrect (expected: #{md5})"
    }
}


    import Foundation
    public class MD5 {
        /** specifies the per-round shift amounts */
        private let s: [UInt32] = [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]

        /** binary integer part of the sines of integers (Radians) */
        private let K: [UInt32] = (0 ..< 64).map { UInt32(0x100000000 * abs(sin(Double($0 + 1)))) }

        let a0: UInt32 = 0x67452301
        let b0: UInt32 = 0xefcdab89
        let c0: UInt32 = 0x98badcfe
        let d0: UInt32 = 0x10325476

        private var message: NSData

        //MARK: Public

        public init(_ message: NSData) {
            self.message = message
        }

        public func calculate() -> NSData? {
            var tmpMessage: NSMutableData = NSMutableData(data: message)
            let wordSize = sizeof(UInt32)

            var aa = a0
            var bb = b0
            var cc = c0
            var dd = d0

            // Step 1. Append Padding Bits
            tmpMessage.appendBytes([0x80]) // append one bit (Byte with one bit) to message

            // append "0" bit until message length in bits ≡ 448 (mod 512)
            while tmpMessage.length % 64 != 56 {
                tmpMessage.appendBytes([0x00])
            }

            // Step 2. Append Length a 64-bit representation of lengthInBits
            var lengthInBits = (message.length * 8)
            var lengthBytes = lengthInBits.bytes(64 / 8)
            tmpMessage.appendBytes(reverse(lengthBytes));

            // Process the message in successive 512-bit chunks:
            let chunkSizeBytes = 512 / 8
            var leftMessageBytes = tmpMessage.length
            for var i = 0; i < tmpMessage.length; i = i + chunkSizeBytes, leftMessageBytes -= chunkSizeBytes {
                let chunk = tmpMessage.subdataWithRange(NSRange(location: i, length: min(chunkSizeBytes,leftMessageBytes)))

                // break chunk into sixteen 32-bit words M[j], 0 ≤ j ≤ 15
                // println("wordSize \(wordSize)");
                var M:[UInt32] = [UInt32](count: 16, repeatedValue: 0)
                for x in 0..<M.count {
                    var range = NSRange(location:x * wordSize, length: wordSize)
                    chunk.getBytes(&M[x], range:range);
                }

                // Initialize hash value for this chunk:
                var A:UInt32 = a0
                var B:UInt32 = b0
                var C:UInt32 = c0
                var D:UInt32 = d0

                var dTemp:UInt32 = 0

                // Main loop
                for j in 0...63 {
                    var g = 0
                    var F:UInt32 = 0

                    switch (j) {
                    case 0...15:
                        F = (B & C) | ((~B) & D)
                        g = j
                        break
                    case 16...31:
                        F = (D & B) | (~D & C)
                        g = (5 * j + 1) % 16
                        break
                    case 32...47:
                        F = B ^ C ^ D
                        g = (3 * j + 5) % 16
                        break
                    case 48...63:
                        F = C ^ (B | (~D))
                        g = (7 * j) % 16
                        break
                    default:
                        break
                    }
                    dTemp = D
                    D = C
                    C = B
                    B = B &+ rotateLeft((A &+ F &+ K[j] &+ M[g]), s[j])
                    A = dTemp
                }

                aa = aa &+ A
                bb = bb &+ B
                cc = cc &+ C
                dd = dd &+ D
            }

            var buf: NSMutableData = NSMutableData();
            buf.appendBytes(&aa, length: wordSize)
            buf.appendBytes(&bb, length: wordSize)
            buf.appendBytes(&cc, length: wordSize)
            buf.appendBytes(&dd, length: wordSize)

            return buf.copy() as? NSData;
        }

        //MARK: Class
        class func calculate(message: NSData) -> NSData?
        {
            return MD5(message).calculate();
        }

        //MARK: Private
        private func rotateLeft(x:UInt32, _ n:UInt32) -> UInt32 {
            return (x &<< n) | (x &>> (32 - n))
        }
    }


# We just define the body of md5::md5 here; later we regsub to inline a few
# function calls for speed
variable ::md5::md5body {
    ### Step 1. Append Padding Bits

    set msgLen [string length $msg]

    set padLen [expr {56 - $msgLen%64}]
    if {$msgLen % 64 > 56} {
	incr padLen 64
    }

    # pad even if no padding required
    if {$padLen == 0} {
	incr padLen 64
    }

    # append single 1b followed by 0b's
    append msg [binary format "a$padLen" \200]

    ### Step 2. Append Length

    # RFC doesn't say whether to use little- or big-endian; code demonstrates
    # little-endian.
    # This step limits our input to size 2^32b or 2^24B
    append msg [binary format "i1i1" [expr {8*$msgLen}] 0]

    ### Step 3. Initialize MD Buffer

    set A [expr 0x67452301]
    set B [expr 0xefcdab89]
    set C [expr 0x98badcfe]
    set D [expr 0x10325476]

    ### Step 4. Process Message in 16-Word Blocks

    # process each 16-word block
    # RFC doesn't say whether to use little- or big-endian; code says
    # little-endian.
    binary scan $msg i* blocks

    # loop over the message taking 16 blocks at a time

    foreach {X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15} $blocks {
	# Save A as AA, B as BB, C as CC, and D as DD.
	set AA $A
	set BB $B
	set CC $C
	set DD $D

	# Round 1.
	# Let [abcd k s i] denote the operation
	#      a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s).
	# [ABCD  0  7  1]  [DABC  1 12  2]  [CDAB  2 17  3]  [BCDA  3 22  4]
	set A [expr {$B + [<<< [expr {$A + [F $B $C $D] + $X0  + $T01}]  7]}]
	set D [expr {$A + [<<< [expr {$D + [F $A $B $C] + $X1  + $T02}] 12]}]
	set C [expr {$D + [<<< [expr {$C + [F $D $A $B] + $X2  + $T03}] 17]}]
	set B [expr {$C + [<<< [expr {$B + [F $C $D $A] + $X3  + $T04}] 22]}]
	# [ABCD  4  7  5]  [DABC  5 12  6]  [CDAB  6 17  7]  [BCDA  7 22  8]
	set A [expr {$B + [<<< [expr {$A + [F $B $C $D] + $X4  + $T05}]  7]}]
	set D [expr {$A + [<<< [expr {$D + [F $A $B $C] + $X5  + $T06}] 12]}]
	set C [expr {$D + [<<< [expr {$C + [F $D $A $B] + $X6  + $T07}] 17]}]
	set B [expr {$C + [<<< [expr {$B + [F $C $D $A] + $X7  + $T08}] 22]}]
	# [ABCD  8  7  9]  [DABC  9 12 10]  [CDAB 10 17 11]  [BCDA 11 22 12]
	set A [expr {$B + [<<< [expr {$A + [F $B $C $D] + $X8  + $T09}]  7]}]
	set D [expr {$A + [<<< [expr {$D + [F $A $B $C] + $X9  + $T10}] 12]}]
	set C [expr {$D + [<<< [expr {$C + [F $D $A $B] + $X10 + $T11}] 17]}]
	set B [expr {$C + [<<< [expr {$B + [F $C $D $A] + $X11 + $T12}] 22]}]
	# [ABCD 12  7 13]  [DABC 13 12 14]  [CDAB 14 17 15]  [BCDA 15 22 16]
	set A [expr {$B + [<<< [expr {$A + [F $B $C $D] + $X12 + $T13}]  7]}]
	set D [expr {$A + [<<< [expr {$D + [F $A $B $C] + $X13 + $T14}] 12]}]
	set C [expr {$D + [<<< [expr {$C + [F $D $A $B] + $X14 + $T15}] 17]}]
	set B [expr {$C + [<<< [expr {$B + [F $C $D $A] + $X15 + $T16}] 22]}]

	# Round 2.
	# Let [abcd k s i] denote the operation
	#      a = b + ((a + G(b,c,d) + X[k] + T[i]) <<< s).
	# Do the following 16 operations.
	# [ABCD  1  5 17]  [DABC  6  9 18]  [CDAB 11 14 19]  [BCDA  0 20 20]
	set A [expr {$B + [<<< [expr {$A + [G $B $C $D] + $X1  + $T17}]  5]}]
	set D [expr {$A + [<<< [expr {$D + [G $A $B $C] + $X6  + $T18}]  9]}]
	set C [expr {$D + [<<< [expr {$C + [G $D $A $B] + $X11 + $T19}] 14]}]
	set B [expr {$C + [<<< [expr {$B + [G $C $D $A] + $X0  + $T20}] 20]}]
	# [ABCD  5  5 21]  [DABC 10  9 22]  [CDAB 15 14 23]  [BCDA  4 20 24]
	set A [expr {$B + [<<< [expr {$A + [G $B $C $D] + $X5  + $T21}]  5]}]
	set D [expr {$A + [<<< [expr {$D + [G $A $B $C] + $X10 + $T22}]  9]}]
	set C [expr {$D + [<<< [expr {$C + [G $D $A $B] + $X15 + $T23}] 14]}]
	set B [expr {$C + [<<< [expr {$B + [G $C $D $A] + $X4  + $T24}] 20]}]
	# [ABCD  9  5 25]  [DABC 14  9 26]  [CDAB  3 14 27]  [BCDA  8 20 28]
	set A [expr {$B + [<<< [expr {$A + [G $B $C $D] + $X9  + $T25}]  5]}]
	set D [expr {$A + [<<< [expr {$D + [G $A $B $C] + $X14 + $T26}]  9]}]
	set C [expr {$D + [<<< [expr {$C + [G $D $A $B] + $X3  + $T27}] 14]}]
	set B [expr {$C + [<<< [expr {$B + [G $C $D $A] + $X8  + $T28}] 20]}]
	# [ABCD 13  5 29]  [DABC  2  9 30]  [CDAB  7 14 31]  [BCDA 12 20 32]
	set A [expr {$B + [<<< [expr {$A + [G $B $C $D] + $X13 + $T29}]  5]}]
	set D [expr {$A + [<<< [expr {$D + [G $A $B $C] + $X2  + $T30}]  9]}]
	set C [expr {$D + [<<< [expr {$C + [G $D $A $B] + $X7  + $T31}] 14]}]
	set B [expr {$C + [<<< [expr {$B + [G $C $D $A] + $X12 + $T32}] 20]}]

	# Round 3.
	# Let [abcd k s t] [sic] denote the operation
	#     a = b + ((a + H(b,c,d) + X[k] + T[i]) <<< s).
	# Do the following 16 operations.
	# [ABCD  5  4 33]  [DABC  8 11 34]  [CDAB 11 16 35]  [BCDA 14 23 36]
	set A [expr {$B + [<<< [expr {$A + [H $B $C $D] + $X5  + $T33}]  4]}]
	set D [expr {$A + [<<< [expr {$D + [H $A $B $C] + $X8  + $T34}] 11]}]
	set C [expr {$D + [<<< [expr {$C + [H $D $A $B] + $X11 + $T35}] 16]}]
	set B [expr {$C + [<<< [expr {$B + [H $C $D $A] + $X14 + $T36}] 23]}]
	# [ABCD  1  4 37]  [DABC  4 11 38]  [CDAB  7 16 39]  [BCDA 10 23 40]
	set A [expr {$B + [<<< [expr {$A + [H $B $C $D] + $X1  + $T37}]  4]}]
	set D [expr {$A + [<<< [expr {$D + [H $A $B $C] + $X4  + $T38}] 11]}]
	set C [expr {$D + [<<< [expr {$C + [H $D $A $B] + $X7  + $T39}] 16]}]
	set B [expr {$C + [<<< [expr {$B + [H $C $D $A] + $X10 + $T40}] 23]}]
	# [ABCD 13  4 41]  [DABC  0 11 42]  [CDAB  3 16 43]  [BCDA  6 23 44]
	set A [expr {$B + [<<< [expr {$A + [H $B $C $D] + $X13 + $T41}]  4]}]
	set D [expr {$A + [<<< [expr {$D + [H $A $B $C] + $X0  + $T42}] 11]}]
	set C [expr {$D + [<<< [expr {$C + [H $D $A $B] + $X3  + $T43}] 16]}]
	set B [expr {$C + [<<< [expr {$B + [H $C $D $A] + $X6  + $T44}] 23]}]
	# [ABCD  9  4 45]  [DABC 12 11 46]  [CDAB 15 16 47]  [BCDA  2 23 48]
	set A [expr {$B + [<<< [expr {$A + [H $B $C $D] + $X9  + $T45}]  4]}]
	set D [expr {$A + [<<< [expr {$D + [H $A $B $C] + $X12 + $T46}] 11]}]
	set C [expr {$D + [<<< [expr {$C + [H $D $A $B] + $X15 + $T47}] 16]}]
	set B [expr {$C + [<<< [expr {$B + [H $C $D $A] + $X2  + $T48}] 23]}]

	# Round 4.
	# Let [abcd k s t] [sic] denote the operation
	#     a = b + ((a + I(b,c,d) + X[k] + T[i]) <<< s).
	# Do the following 16 operations.
	# [ABCD  0  6 49]  [DABC  7 10 50]  [CDAB 14 15 51]  [BCDA  5 21 52]
	set A [expr {$B + [<<< [expr {$A + [I $B $C $D] + $X0  + $T49}]  6]}]
	set D [expr {$A + [<<< [expr {$D + [I $A $B $C] + $X7  + $T50}] 10]}]
	set C [expr {$D + [<<< [expr {$C + [I $D $A $B] + $X14 + $T51}] 15]}]
	set B [expr {$C + [<<< [expr {$B + [I $C $D $A] + $X5  + $T52}] 21]}]
	# [ABCD 12  6 53]  [DABC  3 10 54]  [CDAB 10 15 55]  [BCDA  1 21 56]
	set A [expr {$B + [<<< [expr {$A + [I $B $C $D] + $X12 + $T53}]  6]}]
	set D [expr {$A + [<<< [expr {$D + [I $A $B $C] + $X3  + $T54}] 10]}]
	set C [expr {$D + [<<< [expr {$C + [I $D $A $B] + $X10 + $T55}] 15]}]
	set B [expr {$C + [<<< [expr {$B + [I $C $D $A] + $X1  + $T56}] 21]}]
	# [ABCD  8  6 57]  [DABC 15 10 58]  [CDAB  6 15 59]  [BCDA 13 21 60]
	set A [expr {$B + [<<< [expr {$A + [I $B $C $D] + $X8  + $T57}]  6]}]
	set D [expr {$A + [<<< [expr {$D + [I $A $B $C] + $X15 + $T58}] 10]}]
	set C [expr {$D + [<<< [expr {$C + [I $D $A $B] + $X6  + $T59}] 15]}]
	set B [expr {$C + [<<< [expr {$B + [I $C $D $A] + $X13 + $T60}] 21]}]
	# [ABCD  4  6 61]  [DABC 11 10 62]  [CDAB  2 15 63]  [BCDA  9 21 64]
	set A [expr {$B + [<<< [expr {$A + [I $B $C $D] + $X4  + $T61}]  6]}]
	set D [expr {$A + [<<< [expr {$D + [I $A $B $C] + $X11 + $T62}] 10]}]
	set C [expr {$D + [<<< [expr {$C + [I $D $A $B] + $X2  + $T63}] 15]}]
	set B [expr {$C + [<<< [expr {$B + [I $C $D $A] + $X9  + $T64}] 21]}]

	# Then perform the following additions. (That is increment each of the
	#   four registers by the value it had before this block was started.)
	incr A $AA
	incr B $BB
	incr C $CC
	incr D $DD
    }

    ### Step 5. Output

    # ... begin with the low-order byte of A, and end with the high-order byte
    # of D.

    return [bytes $A][bytes $B][bytes $C][bytes $D]
}

### Here we inline/regsub the functions F, G, H, I and <<<

namespace eval ::md5 {
    #proc md5pure::F {x y z} {expr {(($x & $y) | ((~$x) & $z))}}
    regsub -all -- {\[ *F +(\$.) +(\$.) +(\$.) *\]} $md5body {((\1 \& \2) | ((~\1) \& \3))} md5body

    #proc md5pure::G {x y z} {expr {(($x & $z) | ($y & (~$z)))}}
    regsub -all -- {\[ *G +(\$.) +(\$.) +(\$.) *\]} $md5body {((\1 \& \3) | (\2 \& (~\3)))} md5body

    #proc md5pure::H {x y z} {expr {$x ^ $y ^ $z}}
    regsub -all -- {\[ *H +(\$.) +(\$.) +(\$.) *\]} $md5body {(\1 ^ \2 ^ \3)} md5body

    #proc md5pure::I {x y z} {expr {$y ^ ($x | (~$z))}}
    regsub -all -- {\[ *I +(\$.) +(\$.) +(\$.) *\]} $md5body {(\2 ^ (\1 | (~\3)))} md5body

    # inline <<< (bitwise left-rotate)
    regsub -all -- {\[ *<<< +\[ *expr +({[^\}]*})\] +([0-9]+) *\]} $md5body {(([set x [expr \1]] << \2) |  (($x >> R\2) \& S\2))} md5body

    # now replace the R and S
    variable map {}
    variable i
    foreach i {
	7 12 17 22
	5  9 14 20
	4 11 16 23
	6 10 15 21
    } {
	lappend map R$i [expr {32 - $i}] S$i [expr {0x7fffffff >> (31-$i)}]
    }

    # inline the values of T
    variable tName
    variable tVal
    foreach tName {
	T01 T02 T03 T04 T05 T06 T07 T08 T09 T10
	T11 T12 T13 T14 T15 T16 T17 T18 T19 T20
	T21 T22 T23 T24 T25 T26 T27 T28 T29 T30
	T31 T32 T33 T34 T35 T36 T37 T38 T39 T40
	T41 T42 T43 T44 T45 T46 T47 T48 T49 T50
	T51 T52 T53 T54 T55 T56 T57 T58 T59 T60
	T61 T62 T63 T64
    } tVal {
	0xd76aa478 0xe8c7b756 0x242070db 0xc1bdceee
	0xf57c0faf 0x4787c62a 0xa8304613 0xfd469501
	0x698098d8 0x8b44f7af 0xffff5bb1 0x895cd7be
	0x6b901122 0xfd987193 0xa679438e 0x49b40821

	0xf61e2562 0xc040b340 0x265e5a51 0xe9b6c7aa
	0xd62f105d 0x2441453  0xd8a1e681 0xe7d3fbc8
	0x21e1cde6 0xc33707d6 0xf4d50d87 0x455a14ed
	0xa9e3e905 0xfcefa3f8 0x676f02d9 0x8d2a4c8a

	0xfffa3942 0x8771f681 0x6d9d6122 0xfde5380c
	0xa4beea44 0x4bdecfa9 0xf6bb4b60 0xbebfbc70
	0x289b7ec6 0xeaa127fa 0xd4ef3085 0x4881d05
	0xd9d4d039 0xe6db99e5 0x1fa27cf8 0xc4ac5665

	0xf4292244 0x432aff97 0xab9423a7 0xfc93a039
	0x655b59c3 0x8f0ccc92 0xffeff47d 0x85845dd1
	0x6fa87e4f 0xfe2ce6e0 0xa3014314 0x4e0811a1
	0xf7537e82 0xbd3af235 0x2ad7d2bb 0xeb86d391
    } {
	lappend map \$$tName $tVal
    }
    set md5body [string map $map $md5body]

    # Finally, define the proc
    proc md5 {msg} $md5body

    # unset auxiliary variables
    unset md5body tName tVal map

    proc byte0 {i} {expr {0xff & $i}}
    proc byte1 {i} {expr {(0xff00 & $i) >> 8}}
    proc byte2 {i} {expr {(0xff0000 & $i) >> 16}}
    proc byte3 {i} {expr {((0xff000000 & $i) >> 24) & 0xff}}
    proc bytes {i} {
        format %0.2x%0.2x%0.2x%0.2x [byte0 $i] [byte1 $i] [byte2 $i] [byte3 $i]
    }
}


section .text
org 0x100
	mov	di, md5_for_display
	mov	si, test_input_1
	mov	cx, test_input_1_len
	call	compute_md5
	call	display_md5
	mov	si, test_input_2
	mov	cx, test_input_2_len
	call	compute_md5
	call	display_md5
	mov	si, test_input_3
	mov	cx, test_input_3_len
	call	compute_md5
	call	display_md5
	mov	si, test_input_4
	mov	cx, test_input_4_len
	call	compute_md5
	call	display_md5
	mov	si, test_input_5
	mov	cx, test_input_5_len
	call	compute_md5
	call	display_md5
	mov	si, test_input_6
	mov	cx, test_input_6_len
	call	compute_md5
	call	display_md5
	mov	si, test_input_7
	mov	cx, test_input_7_len
	call	compute_md5
	call	display_md5
	mov	ax, 0x4c00
	int	21h

md5_for_display times 16 db 0
HEX_CHARS db '0123456789ABCDEF'

display_md5:
	mov	ah, 9
	mov	dx, display_str_1
	int	0x21
	push	cx
	push	si
	mov	cx, 16
	mov	si, di
	xor	bx, bx
.loop:
	lodsb
	mov	bl, al
	and	bl, 0x0F
	push	bx
	mov	bl, al
	shr	bx, 4
	mov	ah, 2
	mov	dl, [HEX_CHARS + bx]
	int	0x21
	pop	bx
	mov	dl, [HEX_CHARS + bx]
	int	0x21
	dec	cx
	jnz	.loop
	mov	ah, 9
	mov	dx, display_str_2
	int	0x21
	pop	si
	pop	cx
	test	cx, cx
	jz	do_newline
	mov	ah, 2
display_string:
	lodsb
	mov	dl, al
	int	0x21
	dec	cx
	jnz	display_string
do_newline:
	mov	ah, 9
	mov	dx, display_str_3
	int	0x21
	ret;

compute_md5:
	; si --> input bytes, cx = input len, di --> 16-byte output buffer
	; assumes all in the same segment
	cld
	pusha
	push	di
	push	si
	mov	[message_len], cx

	mov	bx, cx
	shr	bx, 6
	mov	[ending_bytes_block_num], bx
	mov	[num_blocks], bx
	inc	word [num_blocks]
	shl	bx, 6
	add	si, bx
	and	cx, 0x3f
	push	cx
	mov	di, ending_bytes
	rep	movsb
	mov	al, 0x80
	stosb
	pop	cx
	sub	cx, 55
	neg	cx
	jge	add_padding
	add	cx, 64
	inc	word [num_blocks]
add_padding:
	mov	al, 0
	rep	stosb
	xor	eax, eax
	mov	ax, [message_len]
	shl	eax, 3
	mov	cx, 8
store_message_len:
	stosb
	shr	eax, 8
	dec	cx
	jnz	store_message_len
	pop	si
	mov	[md5_a], dword INIT_A
	mov	[md5_b], dword INIT_B
	mov	[md5_c], dword INIT_C
	mov	[md5_d], dword INIT_D
block_loop:
	push	cx
	cmp	cx, [ending_bytes_block_num]
	jne	backup_abcd
	; switch buffers if towards the end where padding needed
	mov	si, ending_bytes
backup_abcd:
	push	dword [md5_d]
	push	dword [md5_c]
	push	dword [md5_b]
	push	dword [md5_a]
	xor	cx, cx
	xor	eax, eax
main_loop:
	push	cx
	mov	ax, cx
	shr	ax, 4
	test	al, al
	jz	pass0
	cmp	al, 1
	je	pass1
	cmp	al, 2
	je	pass2
	; pass3
	mov	eax, [md5_c]
	mov	ebx, [md5_d]
	not	ebx
	or	ebx, [md5_b]
	xor	eax, ebx
	jmp	do_rotate

pass0:
	mov	eax, [md5_b]
	mov	ebx, eax
	and	eax, [md5_c]
	not	ebx
	and	ebx, [md5_d]
	or	eax, ebx
	jmp	do_rotate

pass1:
	mov	eax, [md5_d]
	mov	edx, eax
	and	eax, [md5_b]
	not	edx
	and	edx, [md5_c]
	or	eax, edx
	jmp	do_rotate

pass2:
	mov	eax, [md5_b]
	xor	eax, [md5_c]
	xor	eax, [md5_d]
do_rotate:
	add	eax, [md5_a]
	mov	bx, cx
	shl	bx, 1
	mov	bx, [BUFFER_INDEX_TABLE + bx]
	add	eax, [si + bx]
	mov	bx, cx
	shl	bx, 2
	add	eax, dword [TABLE_T + bx]
	mov	bx, cx
	ror	bx, 2
	shr	bl, 2
	rol	bx, 2
	mov	cl, [SHIFT_AMTS + bx]
	rol	eax, cl
	add	eax, [md5_b]
	push	eax
	push	dword [md5_b]
	push	dword [md5_c]
	push	dword [md5_d]
	pop	dword [md5_a]
	pop	dword [md5_d]
	pop	dword [md5_c]
	pop	dword [md5_b]
	pop	cx
	inc	cx
	cmp	cx, 64
	jb	main_loop
	; add to original values
	pop	eax
	add	[md5_a], eax
	pop	eax
	add	[md5_b], eax
	pop	eax
	add	[md5_c], eax
	pop	eax
	add	[md5_d], eax
	; advance pointers
	add	si, 64
	pop	cx
	inc	cx
	cmp	cx, [num_blocks]
	jne	block_loop
	mov	cx, 4
	mov	si, md5_a
	pop	di
	rep	movsd
	popa
	ret

section .data

INIT_A equ 0x67452301
INIT_B equ 0xEFCDAB89
INIT_C equ 0x98BADCFE
INIT_D equ 0x10325476

SHIFT_AMTS db 7, 12, 17, 22, 5,  9, 14, 20, 4, 11, 16, 23, 6, 10, 15, 21

TABLE_T dd 0xD76AA478, 0xE8C7B756, 0x242070DB, 0xC1BDCEEE, 0xF57C0FAF, 0x4787C62A, 0xA8304613, 0xFD469501, 0x698098D8, 0x8B44F7AF, 0xFFFF5BB1, 0x895CD7BE, 0x6B901122, 0xFD987193, 0xA679438E, 0x49B40821, 0xF61E2562, 0xC040B340, 0x265E5A51, 0xE9B6C7AA, 0xD62F105D, 0x02441453, 0xD8A1E681, 0xE7D3FBC8, 0x21E1CDE6, 0xC33707D6, 0xF4D50D87, 0x455A14ED, 0xA9E3E905, 0xFCEFA3F8, 0x676F02D9, 0x8D2A4C8A, 0xFFFA3942, 0x8771F681, 0x6D9D6122, 0xFDE5380C, 0xA4BEEA44, 0x4BDECFA9, 0xF6BB4B60, 0xBEBFBC70, 0x289B7EC6, 0xEAA127FA, 0xD4EF3085, 0x04881D05, 0xD9D4D039, 0xE6DB99E5, 0x1FA27CF8, 0xC4AC5665, 0xF4292244, 0x432AFF97, 0xAB9423A7, 0xFC93A039, 0x655B59C3, 0x8F0CCC92, 0xFFEFF47D, 0x85845DD1, 0x6FA87E4F, 0xFE2CE6E0, 0xA3014314, 0x4E0811A1, 0xF7537E82, 0xBD3AF235, 0x2AD7D2BB, 0xEB86D391
BUFFER_INDEX_TABLE dw 0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 4, 24, 44, 0, 20, 40, 60, 16, 36, 56, 12, 32, 52, 8, 28, 48, 20, 32, 44, 56, 4, 16, 28, 40, 52, 0, 12, 24, 36, 48, 60, 8, 0, 28, 56, 20, 48, 12, 40, 4, 32, 60, 24, 52, 16, 44, 8, 36
ending_bytes_block_num dw 0
ending_bytes times 128 db 0
message_len dw 0
num_blocks dw 0
md5_a dd 0
md5_b dd 0
md5_c dd 0
md5_d dd 0

display_str_1 db '0x$'
display_str_2 db ' <== "$'
display_str_3 db '"', 13, 10, '$'

test_input_1:
test_input_1_len equ $ - test_input_1
test_input_2 db 'a'
test_input_2_len equ $ - test_input_2
test_input_3 db 'abc'
test_input_3_len equ $ - test_input_3
test_input_4 db 'message digest'
test_input_4_len equ $ - test_input_4
test_input_5 db 'abcdefghijklmnopqrstuvwxyz'
test_input_5_len equ $ - test_input_5
test_input_6 db 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
test_input_6_len equ $ - test_input_6
test_input_7 db '12345678901234567890123456789012345678901234567890123456789012345678901234567890'
test_input_7_len equ $ - test_input_7