{-# LANGUAGE CPP #-} {-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE ConstraintKinds #-} -- | This module exposes the low-level internal details of -- cryptographic hashes. Do not import this module unless you want to -- implement a new hash or give a new implementation of an existing -- hash. module Raaz.Hash.Internal ( -- * Cryptographic hashes and their implementations. -- $hashdoc$ Hash(..) , hash, hashFile, hashSource -- ** Computing hashes using non-standard implementations. , hash', hashFile', hashSource' -- * Hash implementations. , HashI(..), SomeHashI(..), HashM -- ** Implementation of truncated hashes. , truncatedI -- * Memory used by most hashes. , HashMemory(..), extractLength, updateLength -- * Some low level functions. , completeHashing ) where #if !MIN_VERSION_base(4,8,0) import Control.Applicative #endif import qualified Data.ByteString as B import qualified Data.ByteString.Lazy as L import Data.Monoid import Data.Word import Foreign.Storable import System.IO import System.IO.Unsafe (unsafePerformIO) import Raaz.Core -- $hashdoc$ -- -- Each cryptographic hash is a distinct type and are instances of a -- the type class `Hash`. The standard idiom that we follow for hash -- implementations are the following: -- -- [`HashI`:] This type captures implementations of a the hash. This -- type is parameterised over the memory element used by the -- implementation. -- -- [`SomeHashI`:] This type is the existentially quantified version of -- `HashI` over its memory element. Thus it exposes only the interface -- and not the internals of the implementation. The `Implementation` -- associated type of a hash is the type `SomeHashI` -- -- To support a new hash, a developer needs to: -- -- 1. Define a new type which captures the result of hashing. This -- type should be an instance of the class `Hash`. -- -- 2. Define an implementation, i.e. a value of the type `SomeHashI`. -- -- 3. Define a recommended implementation, i.e. an instance of the -- type class `Raaz.Core.Primitives.Recommendation` -------------------- Hash Implementations -------------------------- -- | The Hash implementation. Implementations should ensure the -- following. -- -- 1. The action @compress impl ptr blks@ should only read till the -- @blks@ offset starting at ptr and never write any data. -- -- 2. The action @padFinal impl ptr byts@ should touch at most -- @⌈byts/blocksize⌉ + padBlocks@ blocks starting at ptr. It should -- not write anything till the @byts@ offset but may write stuff -- beyond that. -- -- An easy to remember this rule is to remember that computing hash of -- a payload should not modify the payload. -- data HashI h m = HashI { hashIName :: String , hashIDescription :: String , compress :: Pointer -> BLOCKS h -> MT m () -- ^ compress the blocks, , compressFinal :: Pointer -> BYTES Int -> MT m () -- ^ pad and process the final bytes, , compressStartAlignment :: Alignment } instance BlockAlgorithm (HashI h m) where bufferStartAlignment = compressStartAlignment -- | The constraints that a memory used by a hash implementation -- should satisfy. type HashM h m = (Initialisable m (), Extractable m h, Primitive h) -- | Some implementation of a given hash. The existentially -- quantification allows us freedom to choose the best memory type -- suitable for each implementations. data SomeHashI h = forall m . HashM h m => SomeHashI (HashI h m) instance Describable (HashI h m) where name = hashIName description = hashIDescription instance Describable (SomeHashI h) where name (SomeHashI hI) = name hI description (SomeHashI hI) = description hI instance BlockAlgorithm (SomeHashI h) where bufferStartAlignment (SomeHashI imp) = bufferStartAlignment imp -- | Certain hashes are essentially bit-truncated versions of other -- hashes. For example, SHA224 is obtained from SHA256 by dropping the -- last 32-bits. This combinator can be used build an implementation -- of truncated hash from the implementation of its parent hash. truncatedI :: (BLOCKS htrunc -> BLOCKS h) -> (mtrunc -> m) -> HashI h m -> HashI htrunc mtrunc truncatedI coerce unMtrunc (HashI{..}) = HashI { hashIName = hashIName , hashIDescription = hashIDescription , compress = comp , compressFinal = compF , compressStartAlignment = compressStartAlignment } where comp ptr = onSubMemory unMtrunc . compress ptr . coerce compF ptr = onSubMemory unMtrunc . compressFinal ptr ---------------------- The Hash class --------------------------------- -- | Type class capturing a cryptographic hash. class ( Primitive h , EndianStore h , Encodable h , Eq h , Implementation h ~ SomeHashI h ) => Hash h where -- | Cryptographic hashes can be computed for messages that are not -- a multiple of the block size. This combinator computes the -- maximum size of padding that can be attached to a message. additionalPadBlocks :: h -> BLOCKS h ---------------------- Helper combinators -------------------------- -- | Compute the hash of a pure byte source like, `B.ByteString`. hash :: ( Hash h, Recommendation h, PureByteSource src ) => src -- ^ Message -> h hash = unsafePerformIO . hashSource {-# INLINEABLE hash #-} {-# SPECIALIZE hash :: (Hash h, Recommendation h) => B.ByteString -> h #-} {-# SPECIALIZE hash :: (Hash h, Recommendation h) => L.ByteString -> h #-} -- | Compute the hash of file. hashFile :: ( Hash h, Recommendation h) => FilePath -- ^ File to be hashed -> IO h hashFile fileName = withBinaryFile fileName ReadMode hashSource {-# INLINEABLE hashFile #-} -- | Compute the hash of a generic byte source. hashSource :: ( Hash h, Recommendation h, ByteSource src ) => src -- ^ Message -> IO h hashSource = go undefined where go :: (Hash h, Recommendation h, ByteSource src) => h -> src -> IO h go h = hashSource' $ recommended h {-# INLINEABLE hashSource #-} {-# SPECIALIZE hashSource :: (Hash h, Recommendation h) => Handle -> IO h #-} -- | Similar to `hash` but the user can specify the implementation to -- use. hash' :: ( PureByteSource src , Hash h ) => Implementation h -- ^ Implementation -> src -- ^ the message as a byte source. -> h hash' imp = unsafePerformIO . hashSource' imp {-# INLINEABLE hash' #-} -- TODO: For bytestrings (strict and lazy) we can do better. We can -- avoid copying as the bytestring exposes the underlying -- pointer. However, there is a huge cost if the underlying pointer -- does not start at the machine alignment boundary. The idea -- therefore is to process strict bytestring is multiples of blocks -- directly if the starting pointer is aligned. -- -- More details in the bug report #256. -- -- https://github.com/raaz-crypto/raaz/issues/256 -- -- | Similar to hashFile' but the user can specify the implementation -- to use. hashFile' :: Hash h => Implementation h -- ^ Implementation -> FilePath -- ^ File to be hashed -> IO h hashFile' imp fileName = withBinaryFile fileName ReadMode $ hashSource' imp {-# INLINEABLE hashFile' #-} -- | Similar to @hashSource@ but the user can specify the -- implementation to use. hashSource' :: (Hash h, ByteSource src) => Implementation h -> src -> IO h hashSource' (SomeHashI impl) src = insecurely $ initialise () >> completeHashing impl src -- | Gives a memory action that completes the hashing procedure with -- the rest of the source. Useful to compute the hash of a source with -- some prefix (like in the HMAC procedure). completeHashing :: (Hash h, ByteSource src, HashM h m) => HashI h m -> src -> MT m h completeHashing imp@(HashI{..}) src = allocate $ \ ptr -> let comp = compress ptr bufSize finish bytes = compressFinal ptr bytes >> extract in processChunks comp finish src bufSize ptr where bufSize = atLeast l1Cache <> toEnum 1 totalSize = bufSize <> additionalPadBlocks undefined allocate = liftPointerAction $ allocBufferFor (SomeHashI imp) totalSize ----------------------- Hash memory ---------------------------------- -- | Computing cryptographic hashes usually involves chunking the -- message into blocks and compressing one block at a time. Usually -- this compression makes use of the hash of the previous block and -- the length of the message seen so far to compressing the current -- block. Most implementations therefore need to keep track of only -- hash and the length of the message seen so. This memory can be used -- in such situations. data HashMemory h = HashMemory { hashCell :: MemoryCell h -- ^ Cell to store the hash , messageLengthCell :: MemoryCell (BITS Word64) -- ^ Cell to store the length } instance Storable h => Memory (HashMemory h) where memoryAlloc = HashMemory <$> memoryAlloc <*> memoryAlloc unsafeToPointer = unsafeToPointer . hashCell instance Storable h => Initialisable (HashMemory h) h where initialise h = do onSubMemory hashCell $ initialise h onSubMemory messageLengthCell $ initialise (0 :: BITS Word64) instance Storable h => Extractable (HashMemory h) h where extract = onSubMemory hashCell extract -- | Extract the length of the message hashed so far. extractLength :: MT (HashMemory h) (BITS Word64) extractLength = onSubMemory messageLengthCell extract {-# INLINE extractLength #-} -- | Update the message length by a given amount. updateLength :: LengthUnit u => u -> MT (HashMemory h) () {-# INLINE updateLength #-} updateLength u = onSubMemory messageLengthCell $ modify (nBits +) where nBits :: BITS Word64 nBits = inBits u