module Crypto.Hash.SHA256
(
Ctx(..)
, init
, update
, updates
, finalize
, finalizeAndLength
, hash
, hashlazy
, hashlazyAndLength
, hmac
, hmaclazy
, hmaclazyAndLength
, hkdf
) where
import Data.Bits (xor)
import Data.ByteString (ByteString)
import qualified Data.ByteString as B
import Data.ByteString.Internal (ByteString (PS), create,
createAndTrim, mallocByteString,
memcpy, toForeignPtr)
import qualified Data.ByteString.Lazy as L
import Data.ByteString.Unsafe (unsafeUseAsCStringLen)
import Data.Word
import Foreign.C.Types
import Foreign.ForeignPtr (withForeignPtr)
import Foreign.Marshal.Alloc
import Foreign.Ptr
import Prelude hiding (init)
import System.IO.Unsafe (unsafeDupablePerformIO)
import Crypto.Hash.SHA256.FFI
unsafeDoIO :: IO a -> a
unsafeDoIO = unsafeDupablePerformIO
digestSize :: Int
digestSize = 32
sizeCtx :: Int
sizeCtx = 104
withByteStringPtr :: ByteString -> (Ptr Word8 -> IO a) -> IO a
withByteStringPtr b f =
withForeignPtr fptr $ \ptr -> f (ptr `plusPtr` off)
where (fptr, off, _) = toForeignPtr b
create' :: Int -> (Ptr Word8 -> IO a) -> IO (ByteString,a)
create' l f = do
fp <- mallocByteString l
x <- withForeignPtr fp $ \p -> f p
let bs = PS fp 0 l
return $! x `seq` bs `seq` (bs,x)
copyCtx :: Ptr Ctx -> Ptr Ctx -> IO ()
copyCtx dst src = memcpy (castPtr dst) (castPtr src) (fromIntegral sizeCtx)
withCtxCopy :: Ctx -> (Ptr Ctx -> IO ()) -> IO Ctx
withCtxCopy (Ctx ctxB) f = Ctx `fmap` createCtx
where
createCtx = create sizeCtx $ \dstPtr ->
withByteStringPtr ctxB $ \srcPtr -> do
copyCtx (castPtr dstPtr) (castPtr srcPtr)
f (castPtr dstPtr)
withCtxThrow :: Ctx -> (Ptr Ctx -> IO a) -> IO a
withCtxThrow (Ctx ctxB) f =
allocaBytes sizeCtx $ \dstPtr ->
withByteStringPtr ctxB $ \srcPtr -> do
copyCtx (castPtr dstPtr) (castPtr srcPtr)
f (castPtr dstPtr)
withCtxNew :: (Ptr Ctx -> IO ()) -> IO Ctx
withCtxNew f = Ctx `fmap` create sizeCtx (f . castPtr)
withCtxNewThrow :: (Ptr Ctx -> IO a) -> IO a
withCtxNewThrow f = allocaBytes sizeCtx (f . castPtr)
c_sha256_update :: Ptr Ctx -> Ptr Word8 -> CSize -> IO ()
c_sha256_update pctx pbuf sz
| sz < 4096 = c_sha256_update_unsafe pctx pbuf sz
| otherwise = c_sha256_update_safe pctx pbuf sz
c_sha256_hash :: Ptr Word8 -> CSize -> Ptr Word8 -> IO ()
c_sha256_hash pbuf sz pout
| sz < 4096 = c_sha256_hash_unsafe pbuf sz pout
| otherwise = c_sha256_hash_safe pbuf sz pout
updateInternalIO :: Ptr Ctx -> ByteString -> IO ()
updateInternalIO ptr d =
unsafeUseAsCStringLen d (\(cs, len) -> c_sha256_update ptr (castPtr cs) (fromIntegral len))
finalizeInternalIO :: Ptr Ctx -> IO ByteString
finalizeInternalIO ptr = create digestSize (c_sha256_finalize ptr)
finalizeInternalIO' :: Ptr Ctx -> IO (ByteString,Word64)
finalizeInternalIO' ptr = create' digestSize (c_sha256_finalize_len ptr)
init :: Ctx
init = unsafeDoIO $ withCtxNew c_sha256_init
validCtx :: Ctx -> Bool
validCtx (Ctx b) = B.length b == sizeCtx
update :: Ctx -> ByteString -> Ctx
update ctx d
| validCtx ctx = unsafeDoIO $ withCtxCopy ctx $ \ptr -> updateInternalIO ptr d
| otherwise = error "SHA256.update: invalid Ctx"
updates :: Ctx -> [ByteString] -> Ctx
updates ctx d
| validCtx ctx = unsafeDoIO $ withCtxCopy ctx $ \ptr -> mapM_ (updateInternalIO ptr) d
| otherwise = error "SHA256.updates: invalid Ctx"
finalize :: Ctx -> ByteString
finalize ctx
| validCtx ctx = unsafeDoIO $ withCtxThrow ctx finalizeInternalIO
| otherwise = error "SHA256.finalize: invalid Ctx"
finalizeAndLength :: Ctx -> (ByteString,Word64)
finalizeAndLength ctx
| validCtx ctx = unsafeDoIO $ withCtxThrow ctx finalizeInternalIO'
| otherwise = error "SHA256.finalize: invalid Ctx"
hash :: ByteString -> ByteString
hash d = unsafeDoIO $ unsafeUseAsCStringLen d $ \(cs, len) -> create digestSize (c_sha256_hash (castPtr cs) (fromIntegral len))
hashlazy :: L.ByteString -> ByteString
hashlazy l = unsafeDoIO $ withCtxNewThrow $ \ptr ->
c_sha256_init ptr >> mapM_ (updateInternalIO ptr) (L.toChunks l) >> finalizeInternalIO ptr
hashlazyAndLength :: L.ByteString -> (ByteString,Word64)
hashlazyAndLength l = unsafeDoIO $ withCtxNewThrow $ \ptr ->
c_sha256_init ptr >> mapM_ (updateInternalIO ptr) (L.toChunks l) >> finalizeInternalIO' ptr
hmac :: ByteString
-> ByteString
-> ByteString
hmac secret msg = hash $ B.append opad (hashlazy $ L.fromChunks [ipad,msg])
where
opad = B.map (xor 0x5c) k'
ipad = B.map (xor 0x36) k'
k' = B.append kt pad
kt = if B.length secret > 64 then hash secret else secret
pad = B.replicate (64 B.length kt) 0
hmaclazy :: ByteString
-> L.ByteString
-> ByteString
hmaclazy secret msg = hash $ B.append opad (hashlazy $ L.append ipad msg)
where
opad = B.map (xor 0x5c) k'
ipad = L.fromChunks [B.map (xor 0x36) k']
k' = B.append kt pad
kt = if B.length secret > 64 then hash secret else secret
pad = B.replicate (64 B.length kt) 0
hmaclazyAndLength :: ByteString
-> L.ByteString
-> (ByteString,Word64)
hmaclazyAndLength secret msg =
(hash (B.append opad htmp), sz' fromIntegral ipadLen)
where
(htmp, sz') = hashlazyAndLength (L.append ipad msg)
opad = B.map (xor 0x5c) k'
ipad = L.fromChunks [B.map (xor 0x36) k']
ipadLen = B.length k'
k' = B.append kt pad
kt = if B.length secret > 64 then hash secret else secret
pad = B.replicate (64 B.length kt) 0
hkdf :: ByteString
-> ByteString
-> ByteString
-> Int
-> ByteString
hkdf ikm salt info l
| l == 0 = B.empty
| 0 > l || l > 255*32 = error "hkdf: invalid L parameter"
| otherwise = unsafeDoIO $ createAndTrim (32*fromIntegral cnt) (go 0 B.empty)
where
prk = hmac salt ikm
cnt = fromIntegral ((l+31) `div` 32) :: Word8
go :: Word8 -> ByteString -> Ptr Word8 -> IO Int
go !i t !p | i == cnt = return l
| otherwise = do
let t' = hmaclazy prk (L.fromChunks [t,info,B.singleton (i+1)])
withByteStringPtr t' $ \tptr' -> memcpy p tptr' 32
go (i+1) t' (p `plusPtr` 32)