module Crypto.Hash.MD5 (
MD5
) where
import qualified Data.ByteString.Lazy.Char8 as LC
import qualified Data.ByteString.Lazy as LBS
import qualified Data.ByteString as B
import Data.ByteString (ByteString)
import Data.ByteString.Builder
import Data.Array.Unboxed
import Data.Int
import Data.Word
import Data.Bits
import Data.Monoid
import Data.List(foldl')
import Crypto.Hash.ADT
initSs :: UArray Int Int
initSs = listArray (0, 63) [
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 ]
initKs :: UArray Int Word32
initKs = listArray (0, 63) [
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 ]
data MD5 = MD5 !Word32
!Word32
!Word32
!Word32
deriving Eq
instance Show MD5 where
show = LC.unpack . toLazyByteString . foldMap (word32HexFixed . byteSwap32) . toList
where toList (MD5 a b c d) = a:b:c:[d]
initHash :: MD5
initHash = MD5 0x67452301 0xefcdab89 0x98badcfe 0x10325476
encodeInt64Helper :: Int64 -> [Word8]
encodeInt64Helper x_ = [w0, w1, w2, w3, w4, w5, w6, w7]
where x = x_ * 8
w7 = fromIntegral $ (x `shiftR` 56) .&. 0xff
w6 = fromIntegral $ (x `shiftR` 48) .&. 0xff
w5 = fromIntegral $ (x `shiftR` 40) .&. 0xff
w4 = fromIntegral $ (x `shiftR` 32) .&. 0xff
w3 = fromIntegral $ (x `shiftR` 24) .&. 0xff
w2 = fromIntegral $ (x `shiftR` 16) .&. 0xff
w1 = fromIntegral $ (x `shiftR` 8) .&. 0xff
w0 = fromIntegral $ (x `shiftR` 0) .&. 0xff
encodeInt64 :: Int64 -> ByteString
encodeInt64 = B.pack . encodeInt64Helper
md5BlockSize :: Int
md5BlockSize = 64
md5DigestSize :: Int
md5DigestSize = 16
lastChunk :: Int64 -> ByteString -> [ByteString]
lastChunk msglen s
| len < (md5BlockSize 8) = [s <> B.cons 0x80 (B.replicate (md5BlockSize 9 len) 0x0) <> encodedLen]
| len < (2*md5BlockSize 8) = helper (s <> B.cons 0x80 (B.replicate (2*md5BlockSize 9 len) 0x0) <> encodedLen)
where
len = B.length s
encodedLen = encodeInt64 msglen
helper bs = [s1, s2]
where (!s1, !s2) = B.splitAt 64 bs
readW32 :: ByteString -> Word32
readW32 = byteSwap32 . B.foldl' acc 0
where acc x c = x `shiftL` 8 + fromIntegral c
prepareBlock :: ByteString -> UArray Int Word32
prepareBlock = listArray (0, 15) . go
where go s
| B.null s = []
| otherwise = let !s1 = B.take 4 s
!s' = B.drop 4 s
in readW32 s1 : go s'
md5BlockUpdate :: MD5 -> UArray Int Word32 -> MD5
md5BlockUpdate h u = foldl' blkUpdate h [0..63]
where
blkUpdate (MD5 a b c d) i = MD5 d b' b c
where
!(!f, !g)
| i < 16 = ((d `xor` (b .&. (c `xor` d))), i)
| i < 32 = ((c `xor` (d .&. (b `xor` c))), (5*i+1) .&. 0xf)
| i < 48 = (b `xor` c `xor` d, (3*i+5) .&. 0xf)
| i < 64 = ((c `xor` (b .|. (complement d))), (7*i) .&. 0xf)
!b' = b + (a+f+(initKs!i)+(u!g)) `rotateL` (initSs!i)
blkUpdate :: MD5 -> Int -> MD5
encodeChunk :: MD5 -> ByteString -> MD5
encodeChunk hv@(MD5 a b c d) bs = MD5 (a+a') (b+b') (c+c') (d+d')
where
MD5 a' b' c' d' = md5BlockUpdate hv (prepareBlock bs)
md5Hash :: LBS.ByteString -> MD5
md5Hash = md5Final . LBS.foldlChunks md5Update md5Init
md5Init :: Context MD5
md5Init = Context 0 0 B.empty initHash
md5Update :: Context MD5 -> ByteString -> Context MD5
md5Update ctx@(Context n k w hv) s
| B.null s = ctx
| sizeRead < sizeToRead = Context (n + fromIntegral sizeRead) (k + sizeRead) (w <> s1) hv
| sizeRead >= sizeToRead = md5Update (Context (n + fromIntegral sizeToRead) 0 mempty (encodeChunk hv (w <> s1))) s'
where
!sizeToRead = md5BlockSize k
!s1 = B.take sizeToRead s
!s' = B.drop sizeToRead s
!sizeRead = B.length s1
md5Final :: Context MD5 -> MD5
md5Final (Context n _ w hv) = foldl' encodeChunk hv (lastChunk n w)
instance HashAlgorithm MD5 where
hashBlockSize = const md5BlockSize
hashDigestSize = const md5DigestSize
hashInit = md5Init
hashUpdate = md5Update
hashFinal = md5Final