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