Quite a few Challenges deal with hash functions and message authentication codes based on them. This module contains some machinery for dealing with them.
module Hash
(
MAC(..), MACGenerator
, secretPrefixMAC, validateMAC
, mkHMAC
, SHA1Digest(..), sha1Hash
, SHA1MAC, mkSHA1MAC, validateSHA1MAC
, mkHMACSHA1, validateHMACSHA1
, MD4Digest(..), md4Hash
, MD4MAC, mkMD4MAC, validateMD4MAC
, SHA256Digest(..), sha256Hash
, SHA256MAC, mkHMACSHA256, validateHMACSHA256
) where
import Bytes ( HasBytes(..), Bytes, xorb )
import Data.Maybe ( fromJust )
import qualified Crypto.Hash as H
import qualified Data.ByteArray as A
import qualified Data.ByteString as BWe'll be making lots of different kinds of MACs,
so we have a general framework for them.
A MAC consists of a message (an arbitrary HasBytes)
and an authenticating hash of some kind
(another HasBytes, usually a hash digest).
data MAC text digest = MAC{ macMessage :: text, macHash :: digest }
deriving (Eq,Ord,Show)Generating a MAC is a general operation which takes a key and a message and produces the MAC authenticating the message with the key.
type MACGenerator key text digest = key -> text -> MAC text digestA secret-prefix MAC simply appends the text to the key then hashes with some hash function to create the digest.
secretPrefixMAC :: (HasBytes key, HasBytes text)
=> (Bytes -> digest) -> MACGenerator key text digest
secretPrefixMAC hash key text =
let keytext = toBytes key <> toBytes text
in MAC{ macMessage = text, macHash = hash keytext }Validating a MAC is a general operation, given a MAC generating function. The validation simply regenerates the MAC with our key and compares the generated hash with the provided one.
validateMAC :: Eq digest
=> MACGenerator key text digest
-> key -> MAC text digest -> Bool
validateMAC mkMAC key mac =
let newMAC = mkMAC key (macMessage mac)
in macHash mac == macHash newMACHMAC is a generally useful MAC format which uses two layers of hashing. It can be used with any hash function, so we provide a general function for making an HMAC from a text. The hash function itself and its block size must be provided.
mkHMAC :: (HasBytes digest, HasBytes key, HasBytes text)
=> (Bytes -> digest) -> Int -> MACGenerator key text digest
mkHMAC hash blockSize key text =Our key must be made precisely blockSize bytes long.
We right-pad it if too short, or hash it if too long.
let key' | numBytes key > blockSize = pad $ hash $ toBytes key
| otherwise = pad key
pad k = toBytes k <> B.replicate (blockSize - numBytes k) 0The key is bit-flipped for use in the MAC. In the inner hash layer, we flip bits in the pattern 00110110 (0x36); in the outer layer, in the pattern 01011010 (0x5c).
ikey = key' `xorb` B.replicate blockSize 0x36
okey = key' `xorb` B.replicate blockSize 0x5cThe inner hash prefixes the text with ikey:
h1 = hash (ikey <> toBytes text)while the outer hash prefixes h1 with okey:
h2 = hash (okey <> toBytes h1)h2 is the hash of the entire message.
in MAC{ macMessage = text, macHash = h2 }We use cryptonite's implementations of hash functions.
cryptonite's SHA-1 digest is a ByteArray;
fortunately, these can be converted to and from ByteStrings.
newtype SHA1Digest = SHA1Digest (H.Digest H.SHA1)
deriving (Eq,Ord,Show)
instance HasBytes SHA1Digest where
toBytes (SHA1Digest ba) = A.convert ba
fromBytes = SHA1Digest . fromJust . H.digestFromByteString
numBytes _ = H.hashDigestSize H.SHA1sha1Hash hashes bytes into a digest.
sha1Hash :: HasBytes text => text -> SHA1Digest
sha1Hash = SHA1Digest . H.hash . toBytesWe thus have trivial implementations of MAC generation and validation functions.
type SHA1MAC text = MAC text SHA1Digest
mkSHA1MAC :: (HasBytes key, HasBytes text) => MACGenerator key text SHA1Digest
mkSHA1MAC = secretPrefixMAC sha1Hash
validateSHA1MAC :: (HasBytes key, HasBytes text) => key -> SHA1MAC text -> Bool
validateSHA1MAC = validateMAC mkSHA1MAC
mkHMACSHA1 :: (HasBytes key, HasBytes text) => MACGenerator key text SHA1Digest
mkHMACSHA1 = mkHMAC sha1Hash (H.hashBlockSize H.SHA1)
validateHMACSHA1 :: (HasBytes key, HasBytes text) => key -> SHA1MAC text -> Bool
validateHMACSHA1 = validateMAC mkHMACSHA1The MD-4 hash implementation works exactly the same.
newtype MD4Digest = MD4Digest (H.Digest H.MD4)
deriving (Eq,Ord,Show)
instance HasBytes MD4Digest where
toBytes (MD4Digest ba) = A.convert ba
fromBytes = MD4Digest . fromJust . H.digestFromByteString
numBytes _ = H.hashDigestSize H.MD4
md4Hash :: HasBytes text => text -> MD4Digest
md4Hash = MD4Digest . H.hash . toBytes
type MD4MAC text = MAC text MD4Digest
mkMD4MAC :: (HasBytes key, HasBytes text) => MACGenerator key text MD4Digest
mkMD4MAC = secretPrefixMAC md4Hash
validateMD4MAC :: (HasBytes key, HasBytes text) => key -> MD4MAC text -> Bool
validateMD4MAC = validateMAC mkMD4MACAs does the SHA-256 implementation.
newtype SHA256Digest = SHA256Digest (H.Digest H.SHA256)
deriving (Eq,Ord,Show)
instance HasBytes SHA256Digest where
toBytes (SHA256Digest ba) = A.convert ba
fromBytes = SHA256Digest . fromJust . H.digestFromByteString
numBytes _ = H.hashDigestSize H.SHA256
sha256Hash :: HasBytes text => text -> SHA256Digest
sha256Hash = SHA256Digest . H.hash . toBytes
type SHA256MAC text = MAC text SHA256Digest
mkHMACSHA256 :: (HasBytes key, HasBytes text)
=> MACGenerator key text SHA256Digest
mkHMACSHA256 = mkHMAC sha256Hash (H.hashBlockSize H.SHA256)
validateHMACSHA256 :: (HasBytes key, HasBytes text)
=> key -> SHA256MAC text -> Bool
validateHMACSHA256 = validateMAC mkHMACSHA256