packages feed

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 #-}