pureMD5-0.1.2: src/Data/Digest/MD5/Rolled.hs
{-# OPTIONS_GHC -fbang-patterns -funbox-strict-fields -fvia-c -optc-funroll-all-loops -optc-O3 #-}
--
-- Module : Crypto.MD5
-- License : BSD3
-- Maintainer : Thomas.DuBuisson@gmail.com
-- Stability : experimental
-- Portability : portable, requires bang patterns and ByteString
-- Tested with : GHC-6.8.0
--
module Data.Digest.MD5.Rolled
(md5
,md5InitialContext
,md5Update
,md5Finalize
,MD5Context
,applyMD5Rounds
) where
import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as L
import Data.ByteString.Internal
import Data.Bits
import Data.List as T
import Data.Int (Int64)
import Data.Word
import Foreign.Storable
import Foreign.Ptr
import Foreign.ForeignPtr
import Numeric
import System.Environment
import System.IO
import Data.Binary
import Data.Binary.Get
import Data.Binary.Put
blockSize = 512 -- Block size in bits
blockSizeBytes = blockSize `div` 8
blockSizeBytesW64 = fromIntegral blockSizeBytes
blockSizeBits = (fromIntegral blockSize) :: Word64
sinConst :: [Word32]
sinConst = [floor(abs(sin(i + 1)) * (2 ^ 32)) | i <- [0..63]]
roundShift :: [Int]
roundShift = [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 ]
data MD5Partial = MD5Par !Word32 !Word32 !Word32 !Word32
data MD5Context = MD5Ctx { mdPartial :: MD5Partial,
mdLeftOver :: ByteString,
mdTotalLen :: Word64
}
md5InitialContext = MD5Ctx (MD5Par h0 h1 h2 h3) B.empty 0
h0 = 0x67452301
h1 = 0xEFCDAB89
h2 = 0x98BADCFE
h3 = 0x10325476
-- | Will read the lazy ByteString and return the md5 digest.
-- Some application might want to wrap this function for type safty.
md5 :: L.ByteString -> L.ByteString
md5 bs = md5Finalize $ md5Update md5InitialContext bs
md5Finalize :: MD5Context -> L.ByteString
md5Finalize !ctx@(MD5Ctx (MD5Par a b c d) rem !totLen) =
let totLen' = (totLen + 8*fromIntegral l) :: Word64
padBS = L.toChunks $ runPut ( do
putWord8 0x80
mapM_ putWord8 (replicate lenZeroPad 0)
putWord64le totLen' )
(MD5Ctx (MD5Par a' b' c' d') _ _) = md5Update ctx (L.fromChunks (rem:padBS))
in runPut ( do
putWord32le a'
putWord32le b'
putWord32le c'
putWord32le d' )
where
l = B.length rem
lenZeroPad = if (l+1) <= blockSizeBytes - 8
then (blockSizeBytes - 8) - (l+1)
else (2*blockSizeBytes - 8) - (l+1)
md5Update :: MD5Context -> L.ByteString -> MD5Context
md5Update ctx bsLazy =
let blks = block bsLazy
in foldl' performMD5Update ctx blks
block :: L.ByteString -> [ByteString]
block bs =
case L.toChunks bs of
[] -> []
otherwise -> (B.concat . L.toChunks) top : block rest
where
(top,rest) = L.splitAt blockSizeBytesW64 bs
{-# INLINE block #-}
-- Assumes ByteString length == blockSizeBytes, will fold the
-- context across calls to applyMD5Rounds.
performMD5Update :: MD5Context -> ByteString -> MD5Context
performMD5Update !ctx@(MD5Ctx !par@(MD5Par !a !b !c !d) _ !len) bs =
let MD5Par a' b' c' d' = applyMD5Rounds par bs
in if B.length bs == blockSizeBytes
then MD5Ctx {
mdPartial = MD5Par (a' + a) (b' + b) (c' + c) (d' + d),
mdLeftOver = B.empty,
mdTotalLen = len + blockSizeBits
}
else ctx { mdLeftOver = bs }
applyMD5Rounds :: MD5Partial -> ByteString -> MD5Partial
applyMD5Rounds par@(MD5Par a b c d) w =
foldl' (md5Round w) par [0..63]
md5Round :: ByteString -> MD5Partial -> Int -> MD5Partial
md5Round w par@(MD5Par a b c d) !r
| r <= 15 = let j = r
b' = ff a b c d (w!!j) rs sc
in MD5Par d b' b c
| r <= 31 = let j = (5*r + 1) `mod` 16
b' = gg a b c d (w!!j) rs sc
in MD5Par d b' b c
| r <= 47 = let j = (3*r + 5) `mod` 16
b' = hh a b c d (w!!j) rs sc
in MD5Par d b' b c
| otherwise = let j = (7*r) `mod` 16
b' = ii a b c d (w!!j) rs sc
in MD5Par d b' b c
where
rs = roundShift T.!! r
sc = sinConst T.!! r
f !x !y !z = (x .&. y) .|. ((complement x) .&. z)
{-# INLINE f #-}
g !x !y !z = (x .&. z) .|. (y .&. (complement z))
{-# INLINE g #-}
h !x !y !z = (x `xor` y `xor` z)
{-# INLINE h #-}
i !x !y !z = y `xor` (x .|. (complement z))
{-# INLINE i #-}
ff a b c d x s ac = {-# SCC "ff" #-}
let !a' = f b c d + x + ac + a
!a'' = rotateL a' s
in a'' + b
{-# INLINE ff #-}
gg a b c d x s ac = {-# SCC "gg" #-}
let !a' = g b c d + x + ac + a
!a'' = rotateL a' s
in a'' + b
{-# INLINE gg #-}
hh a b c d x s ac = {-# SCC "hh" #-}
let !a' = h b c d + x + ac + a
!a'' = rotateL a' s
in a'' + b
{-# INLINE hh #-}
ii a b c d x s ac = {-# SCC "ii" #-}
let !a' = i b c d + x + ac + a
!a'' = rotateL a' s
in a'' + b
{-# INLINE ii #-}
(!!) word32s pos = getNthWord pos word32s
{-# INLINE (!!) #-}
-- getNthWord n bs = runGet (skip (n*4) >> getWord32le) (L.fromChunks [bs])
getNthWord n bs@(PS ptr off len) =
inlinePerformIO $ withForeignPtr ptr $ \ptr' -> do
let p = castPtr $ plusPtr ptr' off
peekElemOff p n
{-# INLINE getNthWord #-}
{-# INLINE applyMD5Rounds #-}