hopenssl-1.4: OpenSSL/Digest.hs
{-# LANGUAGE ForeignFunctionInterface #-}
{- |
Module : OpenSSL.Digest
Copyright : (c) 2010 by Peter Simons
License : BSD3
Maintainer : simons@cryp.to
Stability : provisional
Portability : portable
This module proivdes a high-level API to the message
digest algorithms found in OpenSSL's @crypto@ library.
Link with @-lcrypto@ when using this module.
Here is a short example program which runs all available
digests on a string:
> example :: (Enum a) => [a] -> IO [String]
> example input = mapM hash [minBound .. maxBound]
> where
> hash f = fmap (fmt f) (digest f (toWord input))
> fmt f = shows f . (": \t"++) . (>>=toHex)
> toWord = map (toEnum . fromEnum)
And when called, the function prints:
> *Digest> example "open sesame" >>= putStr . unlines
> Null:
> MD2: a22a0b245bdddb00b5293ad590297b25
> MD5: 54ef36ec71201fdf9d1423fd26f97f6b
> SHA: 2ccefef64c76ac0d42ca1657457977675890c42f
> SHA1: 5bcaff7f22ff533ca099b3408ead876c0ebba9a7
> DSS: 5bcaff7f22ff533ca099b3408ead876c0ebba9a7
> DSS1: 5bcaff7f22ff533ca099b3408ead876c0ebba9a7
> RIPEMD160: bdb2bba6ec93bd566dc1181cadbc92176aa78382
-}
module OpenSSL.Digest where
import Control.Exception ( bracket )
import Foreign
import Foreign.C
import Control.Monad.State
import Numeric ( showHex )
-- * High-level API
-- |The message digest algorithms we support.
data MessageDigest
= Null -- ^ 0 bit
| MD2 -- ^ 128 bit
| MD5 -- ^ 128 bit
| SHA -- ^ 160 bit
| SHA1 -- ^ 160 bit
| DSS -- ^ 160 bit (SHA1)
| DSS1 -- ^ 160 bit (SHA1)
| RIPEMD160 -- ^ 160 bit
deriving (Show, Eq, Enum, Bounded)
-- |A convenience wrapper which computes the given digest
-- over a list of 'Word8'. Unlike the monadic interface,
-- this function does not allow the computation to be
-- restarted.
digest :: MessageDigest -> [Word8] -> IO [Word8]
digest mdType xs =
mkDigest mdType $ evalStateT (update xs >> final)
-- |A monadic interface to the digest computation.
type Digest a = StateT DigestState IO a
-- |The internal EVP context.
newtype DigestState = DST (Ptr OpaqueContext)
-- |Run an 'IO' computation with an initialized
-- 'DigestState'. All resources will be freed when the
-- computation returns.
mkDigest :: MessageDigest -> (DigestState -> IO a) -> IO a
mkDigest mdType f =
bracket ctxCreate ctxDestroy $ \ctx -> do
when (ctx == nullPtr) (fail "Digest.mkDigest: ctxCreate failed")
md <- toMDEngine mdType
when (md == nullPtr) (fail ("Digest.mkDigest: can't access "++show mdType))
rc <- digestInit ctx md
when (rc == 0) (fail ("Digest.mkDigest: can't initialize "++show mdType))
f (DST ctx)
-- |Update the internal state with a block of data. This
-- function is just a wrapper for 'update'', which creates
-- an array in memory using 'withArray'.
update :: [Word8] -> Digest ()
update xs = do
st <- get
liftIO $
withArray xs $ \p ->
evalStateT (update' (p, length xs)) st
-- |Update the internal state with a block of data from
-- memory. This is the /faster/ version of 'update'.
update' :: (Ptr Word8, Int) -> Digest ()
update' (p,n) = do
DST ctx <- get
rc <- liftIO $ digestUpdate ctx p (toEnum (fromEnum n))
when (rc == 0) (fail "Digest.update failed")
-- |Wrap up the computation, add padding, do whatever has to
-- be done, and return the final hash. The length of the
-- result depends on the chosen 'MessageDigest'. Do not call
-- more than once!
final :: Digest [Word8]
final = do
DST ctx <- get
liftIO $
allocaArray maxMDSize $ \p ->
allocaArray (sizeOf (undefined :: CUInt)) $ \i -> do
rc <- digestFinal ctx p i
when (rc == 0) (fail "Digest.Final failed")
i' <- peek i
peekArray (fromEnum i') p
-- * Low-level API
-- |The EVP context used by OpenSSL is opaque for us; we
-- only access it through a 'Ptr'.
data OpaqueContext = OpaqueContext
type Context = Ptr OpaqueContext
-- |The message digest engines are opaque for us as well.
data OpaqueMDEngine = OpaqueMDEngine
type MDEngine = Ptr OpaqueMDEngine
-- |Maximum size of all message digests supported by
-- OpenSSL. Allocate a buffer of this size for 'digestFinal'
-- if you want to stay generic.
maxMDSize :: Int
maxMDSize = 36
-- |Create an EVP context. May be 'nullPtr'.
foreign import ccall unsafe "EVP_MD_CTX_create" ctxCreate ::
IO Context
-- |Initialize an EVP context.
foreign import ccall unsafe "EVP_MD_CTX_init" ctxInit ::
Context -> IO ()
-- |Destroy an EVP context and free the allocated resources.
foreign import ccall unsafe "EVP_MD_CTX_destroy" ctxDestroy ::
Context -> IO ()
-- |Set the message digest engine for 'digestUpdate' calls.
-- Returns @\/=0@ in case of an error.
foreign import ccall unsafe "EVP_DigestInit" digestInit ::
Context -> MDEngine -> IO CInt
-- |Update the internal context with a block of input.
-- Returns @\/=0@ in case of an error.
foreign import ccall unsafe "EVP_DigestUpdate" digestUpdate ::
Context -> Ptr Word8 -> CUInt -> IO CInt
-- |Wrap up the digest computation and return the final
-- digest. Do not call repeatedly on the same context!
-- Returns @\/=0@ in case of an error. The pointer to the
-- unsigned integer may be 'nullPtr'. If it is not,
-- 'digestFinal' will store the length of the computed
-- digest there.
foreign import ccall unsafe "EVP_DigestFinal" digestFinal ::
Context -> Ptr Word8 -> Ptr CUInt -> IO CInt
-- ** Message Digest Engines
foreign import ccall unsafe "EVP_md_null" mdNull :: IO MDEngine
foreign import ccall unsafe "EVP_md2" mdMD2 :: IO MDEngine
foreign import ccall unsafe "EVP_md5" mdMD5 :: IO MDEngine
foreign import ccall unsafe "EVP_sha" mdSHA :: IO MDEngine
foreign import ccall unsafe "EVP_sha1" mdSHA1 :: IO MDEngine
foreign import ccall unsafe "EVP_dss" mdDSS :: IO MDEngine
foreign import ccall unsafe "EVP_dss1" mdDSS1 :: IO MDEngine
foreign import ccall unsafe "EVP_ripemd160" mdRIPEMD160 :: IO MDEngine
-- |Map a 'MessageDigest' type into the the corresponding
-- 'MDEngine'.
toMDEngine :: MessageDigest -> IO MDEngine
toMDEngine Null = mdNull
toMDEngine MD2 = mdMD2
toMDEngine MD5 = mdMD5
toMDEngine SHA = mdSHA
toMDEngine SHA1 = mdSHA1
toMDEngine DSS = mdDSS
toMDEngine DSS1 = mdDSS1
toMDEngine RIPEMD160 = mdRIPEMD160
-- * Helper Functions
-- |Neat helper to print digests with:
-- @
-- \\ws :: [Word8] -> ws >>= toHex
-- @
toHex :: Word8 -> String
toHex w = case showHex w "" of
w1:w2:[] -> w1:w2:[]
w2:[] -> '0':w2:[]
_ -> error "showHex returned []"
-- ----- Configure Emacs -----
--
-- Local Variables: ***
-- haskell-program-name: "ghci -ignore-package hopenssl -Wall -lcrypto" ***
-- End: ***