crypto-srp-0.1.0.0: src/Crypto/SRP/Hashing.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE StrictData #-}
{-# OPTIONS_HADDOCK prune #-}
{- |
Module : Crypto.SRP.Hashing
Copyright : (c) 2025 Tim Emiola
Maintainer : Tim Emiola <adetokunbo@emio.la>
SPDX-License-Identifier: BSD3
Provides the 'KnownAlgorithm' abstraction over SHA1, SHA256, SHA384 and SHA512,
together with the SRP-specific hash combinators ('calcK', 'calcClientX',
'calcXorHashnHashg') that implement the intermediate steps from
[RFC 5054 §2.6](https://datatracker.ietf.org/doc/html/rfc5054#section-2.6).
-}
module Crypto.SRP.Hashing
( -- * Supported hash algorithms
KnownAlgorithm (..)
-- ** Use the @'KnownAlgorithm's@ for hashing
, digestSize
, hash
, hashMany
, hashText
-- * SRP-specific hash calculations
, calcK
, calcClientX
, calcXorHashnHashg
)
where
import qualified Crypto.Hash.SHA1 as SHA1
import qualified Crypto.Hash.SHA256 as SHA256
import qualified Crypto.Hash.SHA384 as SHA384
import qualified Crypto.Hash.SHA512 as SHA512
import Crypto.SRP.PrimeGroup
( PrimeGroup
, bytesOf
, generatorFor
, padAs
, safePrimeFor
)
import Data.Bits (xor)
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
import Data.Text (Text)
import Data.Text.Encoding (encodeUtf8)
import Data.Text.Normalize (NormalizationMode (NFKC), normalize)
import Data.Word (Word32)
{- | Compute the multiplier \'k\' as a step in the calculation of the premaster
secret
See [premaster secret calculation](https://datatracker.ietf.org/doc/html/rfc5054#section-2.6)
-}
calcK :: KnownAlgorithm -> PrimeGroup -> ByteString
calcK known pg =
hashMany
known
[ bytesOf (fromIntegral (safePrimeFor pg)) `padAs` pg
, bytesOf (fromIntegral (generatorFor pg)) `padAs` pg
]
-- | Compute an XORed hash describing a @'PrimeGroup'@.
calcXorHashnHashg :: KnownAlgorithm -> PrimeGroup -> ByteString
calcXorHashnHashg known pg =
let hashedN = hash known (bytesOf (fromIntegral (safePrimeFor pg)))
hashedG = hash known (bytesOf (fromIntegral (generatorFor pg)) `padAs` pg)
in BS.pack $ BS.zipWith xor hashedN hashedG
{- | Compute the hash \'x\' as a step in the calculation of the premaster secret
See [premaster secret calculation](https://datatracker.ietf.org/doc/html/rfc5054#section-2.6)
-}
calcClientX :: (Text, Text) -> ByteString -> KnownAlgorithm -> ByteString
calcClientX (username, password) serverSalt known =
let h = hashMany known
normalize' = encodeUtf8 . normalize NFKC
in h [serverSalt, h [normalize' username, ":", normalize' password]]
-- | Hash a 'Text' value after normalising it to NFKC form
hashText :: KnownAlgorithm -> Text -> ByteString
hashText known txt =
let
normalize' = encodeUtf8 . normalize NFKC
in
hash known $ normalize' txt
-- | Provides an interface to the implemention of a hash algorithm
data Algorithm = Algorithm
{ algDigestSize :: {-# UNPACK #-} !Word32
, algHash :: !(ByteString -> ByteString)
, algHashMany :: !([ByteString] -> ByteString)
}
-- | Hash a @ByteString@ using the hash function of a 'KnownAlgorithm'
hash :: KnownAlgorithm -> ByteString -> ByteString
hash = algHash . alg
-- | Hash several @ByteStrings@ using the hash function of a 'KnownAlgorithm'
hashMany :: KnownAlgorithm -> [ByteString] -> ByteString
hashMany = algHashMany . alg
-- | The size of digest computed by a 'KnownAlgorithm'
digestSize :: KnownAlgorithm -> Word32
digestSize = algDigestSize . alg
-- | Enumerates specific hash algorithms supported by this package
data KnownAlgorithm
= SHA1
| SHA256
| SHA384
| SHA512
deriving (Eq, Show)
-- Provides an 'Algorithm' that contains the implementation for each 'KnownAlgorithm'
alg :: KnownAlgorithm -> Algorithm
alg SHA1 = Algorithm 20 SHA1.hash (SHA1.finalize . SHA1.updates SHA1.init)
alg SHA256 = Algorithm 32 SHA256.hash (SHA256.finalize . SHA256.updates SHA256.init)
alg SHA384 = Algorithm 48 SHA384.hash (SHA384.finalize . SHA384.updates SHA384.init)
alg SHA512 = Algorithm 64 SHA512.hash (SHA512.finalize . SHA512.updates SHA512.init)