Blame src/Crypto/Hash/SHA256.hs

Packit f46cda
{-# LANGUAGE BangPatterns #-}
Packit f46cda
{-# LANGUAGE Trustworthy  #-}
Packit f46cda
Packit f46cda
-- |
Packit f46cda
-- Module      : Crypto.Hash.SHA256
Packit f46cda
-- License     : BSD-3
Packit f46cda
-- Maintainer  : Herbert Valerio Riedel <hvr@gnu.org>
Packit f46cda
-- Stability   : stable
Packit f46cda
--
Packit f46cda
-- A module containing <https://en.wikipedia.org/wiki/SHA-2 SHA-256> bindings
Packit f46cda
--
Packit f46cda
module Crypto.Hash.SHA256
Packit f46cda
    (
Packit f46cda
Packit f46cda
    -- * Incremental API
Packit f46cda
    --
Packit f46cda
    -- | This API is based on 4 different functions, similar to the
Packit f46cda
    -- lowlevel operations of a typical hash:
Packit f46cda
    --
Packit f46cda
    --  - 'init': create a new hash context
Packit f46cda
    --  - 'update': update non-destructively a new hash context with a strict bytestring
Packit f46cda
    --  - 'updates': same as update, except that it takes a list of strict bytestrings
Packit f46cda
    --  - 'finalize': finalize the context and returns a digest bytestring.
Packit f46cda
    --
Packit f46cda
    -- all those operations are completely pure, and instead of
Packit f46cda
    -- changing the context as usual in others language, it
Packit f46cda
    -- re-allocates a new context each time.
Packit f46cda
    --
Packit f46cda
    -- Example:
Packit f46cda
    --
Packit f46cda
    -- > import qualified Data.ByteString
Packit f46cda
    -- > import qualified Crypto.Hash.SHA256 as SHA256
Packit f46cda
    -- >
Packit f46cda
    -- > main = print digest
Packit f46cda
    -- >   where
Packit f46cda
    -- >     digest = SHA256.finalize ctx
Packit f46cda
    -- >     ctx    = foldl SHA256.update ctx0 (map Data.ByteString.pack [ [1,2,3], [4,5,6] ])
Packit f46cda
    -- >     ctx0   = SHA256.init
Packit f46cda
Packit f46cda
      Ctx(..)
Packit f46cda
    , init     -- :: Ctx
Packit f46cda
    , update   -- :: Ctx -> ByteString -> Ctx
Packit f46cda
    , updates  -- :: Ctx -> [ByteString] -> Ctx
Packit f46cda
    , finalize -- :: Ctx -> ByteString
Packit f46cda
    , finalizeAndLength -- :: Ctx -> (ByteString,Word64)
Packit f46cda
Packit f46cda
    -- * Single Pass API
Packit f46cda
    --
Packit f46cda
    -- | This API use the incremental API under the hood to provide
Packit f46cda
    -- the common all-in-one operations to create digests out of a
Packit f46cda
    -- 'ByteString' and lazy 'L.ByteString'.
Packit f46cda
    --
Packit f46cda
    --  - 'hash': create a digest ('init' + 'update' + 'finalize') from a strict 'ByteString'
Packit f46cda
    --  - 'hashlazy': create a digest ('init' + 'update' + 'finalize') from a lazy 'L.ByteString'
Packit f46cda
    --  - 'hashlazyAndLength': create a digest ('init' + 'update' + 'finalizeAndLength') from a lazy 'L.ByteString'
Packit f46cda
    --
Packit f46cda
    -- Example:
Packit f46cda
    --
Packit f46cda
    -- > import qualified Data.ByteString
Packit f46cda
    -- > import qualified Crypto.Hash.SHA256 as SHA256
Packit f46cda
    -- >
Packit f46cda
    -- > main = print $ SHA256.hash (Data.ByteString.pack [0..255])
Packit f46cda
    --
Packit f46cda
    -- __NOTE__: The returned digest is a binary 'ByteString'. For
Packit f46cda
    -- converting to a base16/hex encoded digest the
Packit f46cda
    -- <https://hackage.haskell.org/package/base16-bytestring base16-bytestring>
Packit f46cda
    -- package is recommended.
Packit f46cda
Packit f46cda
    , hash     -- :: ByteString -> ByteString
Packit f46cda
    , hashlazy -- :: L.ByteString -> ByteString
Packit f46cda
    , hashlazyAndLength -- :: L.ByteString -> (ByteString,Int64)
Packit f46cda
Packit f46cda
    -- ** HMAC-SHA-256
Packit f46cda
    --
Packit f46cda
    -- | <https://tools.ietf.org/html/rfc2104 RFC2104>-compatible
Packit f46cda
    -- <https://en.wikipedia.org/wiki/HMAC HMAC>-SHA-256 digests
Packit f46cda
Packit f46cda
    , hmac     -- :: ByteString -> ByteString -> ByteString
Packit f46cda
    , hmaclazy -- :: ByteString -> L.ByteString -> ByteString
Packit f46cda
    , hmaclazyAndLength -- :: ByteString -> L.ByteString -> (ByteString,Word64)
Packit f46cda
Packit f46cda
    -- ** HKDF-SHA-256
Packit f46cda
    --
Packit f46cda
    -- | <https://tools.ietf.org/html/rfc5869 RFC5869>-compatible
Packit f46cda
    -- <https://en.wikipedia.org/wiki/HKDF HKDF>-SHA-256 key derivation function
Packit f46cda
Packit f46cda
    , hkdf
Packit f46cda
    ) where
Packit f46cda
Packit f46cda
import           Data.Bits                (xor)
Packit f46cda
import           Data.ByteString          (ByteString)
Packit f46cda
import qualified Data.ByteString          as B
Packit f46cda
import           Data.ByteString.Internal (ByteString (PS), create,
Packit f46cda
                                           createAndTrim, mallocByteString,
Packit f46cda
                                           memcpy, toForeignPtr)
Packit f46cda
import qualified Data.ByteString.Lazy     as L
Packit f46cda
import           Data.ByteString.Unsafe   (unsafeUseAsCStringLen)
Packit f46cda
import           Data.Word
Packit f46cda
import           Foreign.C.Types
Packit f46cda
import           Foreign.ForeignPtr       (withForeignPtr)
Packit f46cda
import           Foreign.Marshal.Alloc
Packit f46cda
import           Foreign.Ptr
Packit f46cda
import           Prelude                  hiding (init)
Packit f46cda
import           System.IO.Unsafe         (unsafeDupablePerformIO)
Packit f46cda
Packit f46cda
import           Crypto.Hash.SHA256.FFI
Packit f46cda
Packit f46cda
-- | perform IO for hashes that do allocation and ffi.
Packit f46cda
-- unsafeDupablePerformIO is used when possible as the
Packit f46cda
-- computation is pure and the output is directly linked
Packit f46cda
-- to the input. we also do not modify anything after it has
Packit f46cda
-- been returned to the user.
Packit f46cda
unsafeDoIO :: IO a -> a
Packit f46cda
unsafeDoIO = unsafeDupablePerformIO
Packit f46cda
Packit f46cda
-- keep this synchronised with cbits/sha256.h
Packit f46cda
{-# INLINE digestSize #-}
Packit f46cda
digestSize :: Int
Packit f46cda
digestSize = 32
Packit f46cda
Packit f46cda
{-# INLINE sizeCtx #-}
Packit f46cda
sizeCtx :: Int
Packit f46cda
sizeCtx = 104
Packit f46cda
Packit f46cda
{-# INLINE withByteStringPtr #-}
Packit f46cda
withByteStringPtr :: ByteString -> (Ptr Word8 -> IO a) -> IO a
Packit f46cda
withByteStringPtr b f =
Packit f46cda
    withForeignPtr fptr $ \ptr -> f (ptr `plusPtr` off)
Packit f46cda
    where (fptr, off, _) = toForeignPtr b
Packit f46cda
Packit f46cda
{-# INLINE create' #-}
Packit f46cda
-- | Variant of 'create' which allows to return an argument
Packit f46cda
create' :: Int -> (Ptr Word8 -> IO a) -> IO (ByteString,a)
Packit f46cda
create' l f = do
Packit f46cda
    fp <- mallocByteString l
Packit f46cda
    x <- withForeignPtr fp $ \p -> f p
Packit f46cda
    let bs = PS fp 0 l
Packit f46cda
    return $! x `seq` bs `seq` (bs,x)
Packit f46cda
Packit f46cda
copyCtx :: Ptr Ctx -> Ptr Ctx -> IO ()
Packit f46cda
copyCtx dst src = memcpy (castPtr dst) (castPtr src) (fromIntegral sizeCtx)
Packit f46cda
Packit f46cda
withCtxCopy :: Ctx -> (Ptr Ctx -> IO ()) -> IO Ctx
Packit f46cda
withCtxCopy (Ctx ctxB) f = Ctx `fmap` createCtx
Packit f46cda
  where
Packit f46cda
    createCtx = create sizeCtx $ \dstPtr ->
Packit f46cda
                withByteStringPtr ctxB $ \srcPtr -> do
Packit f46cda
                    copyCtx (castPtr dstPtr) (castPtr srcPtr)
Packit f46cda
                    f (castPtr dstPtr)
Packit f46cda
Packit f46cda
withCtxThrow :: Ctx -> (Ptr Ctx -> IO a) -> IO a
Packit f46cda
withCtxThrow (Ctx ctxB) f =
Packit f46cda
    allocaBytes sizeCtx $ \dstPtr ->
Packit f46cda
    withByteStringPtr ctxB $ \srcPtr -> do
Packit f46cda
        copyCtx (castPtr dstPtr) (castPtr srcPtr)
Packit f46cda
        f (castPtr dstPtr)
Packit f46cda
Packit f46cda
withCtxNew :: (Ptr Ctx -> IO ()) -> IO Ctx
Packit f46cda
withCtxNew f = Ctx `fmap` create sizeCtx (f . castPtr)
Packit f46cda
Packit f46cda
withCtxNewThrow :: (Ptr Ctx -> IO a) -> IO a
Packit f46cda
withCtxNewThrow f = allocaBytes sizeCtx (f . castPtr)
Packit f46cda
Packit f46cda
-- 'safe' call overhead neglible for 4KiB and more
Packit f46cda
c_sha256_update :: Ptr Ctx -> Ptr Word8 -> CSize -> IO ()
Packit f46cda
c_sha256_update pctx pbuf sz
Packit f46cda
  | sz < 4096 = c_sha256_update_unsafe pctx pbuf sz
Packit f46cda
  | otherwise = c_sha256_update_safe   pctx pbuf sz
Packit f46cda
Packit f46cda
-- 'safe' call overhead neglible for 4KiB and more
Packit f46cda
c_sha256_hash :: Ptr Word8 -> CSize -> Ptr Word8 -> IO ()
Packit f46cda
c_sha256_hash pbuf sz pout
Packit f46cda
  | sz < 4096 = c_sha256_hash_unsafe pbuf sz pout
Packit f46cda
  | otherwise = c_sha256_hash_safe   pbuf sz pout
Packit f46cda
Packit f46cda
updateInternalIO :: Ptr Ctx -> ByteString -> IO ()
Packit f46cda
updateInternalIO ptr d =
Packit f46cda
    unsafeUseAsCStringLen d (\(cs, len) -> c_sha256_update ptr (castPtr cs) (fromIntegral len))
Packit f46cda
Packit f46cda
finalizeInternalIO :: Ptr Ctx -> IO ByteString
Packit f46cda
finalizeInternalIO ptr = create digestSize (c_sha256_finalize ptr)
Packit f46cda
Packit f46cda
finalizeInternalIO' :: Ptr Ctx -> IO (ByteString,Word64)
Packit f46cda
finalizeInternalIO' ptr = create' digestSize (c_sha256_finalize_len ptr)
Packit f46cda
Packit f46cda
Packit f46cda
{-# NOINLINE init #-}
Packit f46cda
-- | create a new hash context
Packit f46cda
init :: Ctx
Packit f46cda
init = unsafeDoIO $ withCtxNew c_sha256_init
Packit f46cda
Packit f46cda
validCtx :: Ctx -> Bool
Packit f46cda
validCtx (Ctx b) = B.length b == sizeCtx
Packit f46cda
Packit f46cda
{-# NOINLINE update #-}
Packit f46cda
-- | update a context with a bytestring
Packit f46cda
update :: Ctx -> ByteString -> Ctx
Packit f46cda
update ctx d
Packit f46cda
  | validCtx ctx = unsafeDoIO $ withCtxCopy ctx $ \ptr -> updateInternalIO ptr d
Packit f46cda
  | otherwise    = error "SHA256.update: invalid Ctx"
Packit f46cda
Packit f46cda
{-# NOINLINE updates #-}
Packit f46cda
-- | updates a context with multiple bytestrings
Packit f46cda
updates :: Ctx -> [ByteString] -> Ctx
Packit f46cda
updates ctx d
Packit f46cda
  | validCtx ctx = unsafeDoIO $ withCtxCopy ctx $ \ptr -> mapM_ (updateInternalIO ptr) d
Packit f46cda
  | otherwise    = error "SHA256.updates: invalid Ctx"
Packit f46cda
Packit f46cda
{-# NOINLINE finalize #-}
Packit f46cda
-- | finalize the context into a digest bytestring (32 bytes)
Packit f46cda
finalize :: Ctx -> ByteString
Packit f46cda
finalize ctx
Packit f46cda
  | validCtx ctx = unsafeDoIO $ withCtxThrow ctx finalizeInternalIO
Packit f46cda
  | otherwise    = error "SHA256.finalize: invalid Ctx"
Packit f46cda
Packit f46cda
{-# NOINLINE finalizeAndLength #-}
Packit f46cda
-- | Variant of 'finalize' also returning length of hashed content
Packit f46cda
--
Packit f46cda
-- @since 0.11.101.0
Packit f46cda
finalizeAndLength :: Ctx -> (ByteString,Word64)
Packit f46cda
finalizeAndLength ctx
Packit f46cda
  | validCtx ctx = unsafeDoIO $ withCtxThrow ctx finalizeInternalIO'
Packit f46cda
  | otherwise    = error "SHA256.finalize: invalid Ctx"
Packit f46cda
Packit f46cda
{-# NOINLINE hash #-}
Packit f46cda
-- | hash a strict bytestring into a digest bytestring (32 bytes)
Packit f46cda
hash :: ByteString -> ByteString
Packit f46cda
-- hash d = unsafeDoIO $ withCtxNewThrow $ \ptr -> c_sha256_init ptr >> updateInternalIO ptr d >> finalizeInternalIO ptr
Packit f46cda
hash d = unsafeDoIO $ unsafeUseAsCStringLen d $ \(cs, len) -> create digestSize (c_sha256_hash (castPtr cs) (fromIntegral len))
Packit f46cda
Packit f46cda
{-# NOINLINE hashlazy #-}
Packit f46cda
-- | hash a lazy bytestring into a digest bytestring (32 bytes)
Packit f46cda
hashlazy :: L.ByteString -> ByteString
Packit f46cda
hashlazy l = unsafeDoIO $ withCtxNewThrow $ \ptr ->
Packit f46cda
    c_sha256_init ptr >> mapM_ (updateInternalIO ptr) (L.toChunks l) >> finalizeInternalIO ptr
Packit f46cda
Packit f46cda
{-# NOINLINE hashlazyAndLength #-}
Packit f46cda
-- | Variant of 'hashlazy' which simultaneously computes the hash and length of a lazy bytestring.
Packit f46cda
--
Packit f46cda
-- @since 0.11.101.0
Packit f46cda
hashlazyAndLength :: L.ByteString -> (ByteString,Word64)
Packit f46cda
hashlazyAndLength l = unsafeDoIO $ withCtxNewThrow $ \ptr ->
Packit f46cda
    c_sha256_init ptr >> mapM_ (updateInternalIO ptr) (L.toChunks l) >> finalizeInternalIO' ptr
Packit f46cda
Packit f46cda
Packit f46cda
-- | Compute 32-byte <https://tools.ietf.org/html/rfc2104 RFC2104>-compatible
Packit f46cda
-- HMAC-SHA-256 digest for a strict bytestring message
Packit f46cda
--
Packit f46cda
-- @since 0.11.100.0
Packit f46cda
hmac :: ByteString -- ^ secret
Packit f46cda
     -> ByteString -- ^ message
Packit f46cda
     -> ByteString -- ^ digest (32 bytes)
Packit f46cda
hmac secret msg = hash $ B.append opad (hashlazy $ L.fromChunks [ipad,msg])
Packit f46cda
  where
Packit f46cda
    opad = B.map (xor 0x5c) k'
Packit f46cda
    ipad = B.map (xor 0x36) k'
Packit f46cda
Packit f46cda
    k'  = B.append kt pad
Packit f46cda
    kt  = if B.length secret > 64 then hash secret else secret
Packit f46cda
    pad = B.replicate (64 - B.length kt) 0
Packit f46cda
Packit f46cda
Packit f46cda
-- | Compute 32-byte <https://tools.ietf.org/html/rfc2104 RFC2104>-compatible
Packit f46cda
-- HMAC-SHA-256 digest for a lazy bytestring message
Packit f46cda
--
Packit f46cda
-- @since 0.11.100.0
Packit f46cda
hmaclazy :: ByteString   -- ^ secret
Packit f46cda
         -> L.ByteString -- ^ message
Packit f46cda
         -> ByteString   -- ^ digest (32 bytes)
Packit f46cda
hmaclazy secret msg = hash $ B.append opad (hashlazy $ L.append ipad msg)
Packit f46cda
  where
Packit f46cda
    opad = B.map (xor 0x5c) k'
Packit f46cda
    ipad = L.fromChunks [B.map (xor 0x36) k']
Packit f46cda
Packit f46cda
    k'  = B.append kt pad
Packit f46cda
    kt  = if B.length secret > 64 then hash secret else secret
Packit f46cda
    pad = B.replicate (64 - B.length kt) 0
Packit f46cda
Packit f46cda
Packit f46cda
-- | Variant of 'hmaclazy' which also returns length of message
Packit f46cda
--
Packit f46cda
-- @since 0.11.101.0
Packit f46cda
hmaclazyAndLength :: ByteString   -- ^ secret
Packit f46cda
                  -> L.ByteString -- ^ message
Packit f46cda
                  -> (ByteString,Word64) -- ^ digest (32 bytes) and length of message
Packit f46cda
hmaclazyAndLength secret msg =
Packit f46cda
    (hash (B.append opad htmp), sz' - fromIntegral ipadLen)
Packit f46cda
  where
Packit f46cda
    (htmp, sz') = hashlazyAndLength (L.append ipad msg)
Packit f46cda
Packit f46cda
    opad = B.map (xor 0x5c) k'
Packit f46cda
    ipad = L.fromChunks [B.map (xor 0x36) k']
Packit f46cda
    ipadLen = B.length k'
Packit f46cda
Packit f46cda
    k'  = B.append kt pad
Packit f46cda
    kt  = if B.length secret > 64 then hash secret else secret
Packit f46cda
    pad = B.replicate (64 - B.length kt) 0
Packit f46cda
Packit f46cda
{-# NOINLINE hkdf #-}
Packit f46cda
-- | <https://tools.ietf.org/html/rfc6234 RFC6234>-compatible
Packit f46cda
-- HKDF-SHA-256 key derivation function.
Packit f46cda
--
Packit f46cda
-- @since 0.11.101.0
Packit f46cda
hkdf :: ByteString -- ^ /IKM/ Input keying material
Packit f46cda
     -> ByteString -- ^ /salt/ Optional salt value, a non-secret random value (can be @""@)
Packit f46cda
     -> ByteString -- ^ /info/ Optional context and application specific information (can be @""@)
Packit f46cda
     -> Int        -- ^ /L/ length of output keying material in octets (at most 255*32 bytes)
Packit f46cda
     -> ByteString -- ^ /OKM/ Output keying material (/L/ bytes)
Packit f46cda
hkdf ikm salt info l
Packit f46cda
  | l == 0 = B.empty
Packit f46cda
  | 0 > l || l > 255*32 = error "hkdf: invalid L parameter"
Packit f46cda
  | otherwise = unsafeDoIO $ createAndTrim (32*fromIntegral cnt) (go 0 B.empty)
Packit f46cda
  where
Packit f46cda
    prk = hmac salt ikm
Packit f46cda
    cnt = fromIntegral ((l+31) `div` 32) :: Word8
Packit f46cda
Packit f46cda
    go :: Word8 -> ByteString -> Ptr Word8 -> IO Int
Packit f46cda
    go !i t !p | i == cnt  = return l
Packit f46cda
               | otherwise = do
Packit f46cda
                   let t' = hmaclazy prk (L.fromChunks [t,info,B.singleton (i+1)])
Packit f46cda
                   withByteStringPtr t' $ \tptr' -> memcpy p tptr' 32
Packit f46cda
                   go (i+1) t' (p `plusPtr` 32)