sha512

Pure Haskell SHA-512, HMAC-SHA512 (docs.ppad.tech/sha512).
git clone git://git.ppad.tech/sha512.git
Log | Files | Refs | README | LICENSE

commit b632003b6905979aacdd34cf507643ca2b531f01
parent 428e2e09c345a0cb255d9aab432606308872c014
Author: Jared Tobin <jared@jtobin.io>
Date:   Sat, 10 Jan 2026 15:41:19 +0400

lib: add MAC type

Diffstat:
Mbench/Main.hs | 44+++++++++++++++++++++++++++-----------------
Mlib/Crypto/Hash/SHA512.hs | 7++++---
Mlib/Crypto/Hash/SHA512/Internal.hs | 29+++++++++++++++++++++++++++++
Mlib/Crypto/Hash/SHA512/Lazy.hs | 6+++---
Mtest/Main.hs | 27++++++++++++++++++---------
5 files changed, 81 insertions(+), 32 deletions(-)

diff --git a/bench/Main.hs b/bench/Main.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE BangPatterns #-} {-# LANGUAGE OverloadedStrings #-} module Main where @@ -14,22 +15,31 @@ main = defaultMain [ ] suite :: Benchmark -suite = env setup $ \ ~(bs, bl) -> - bgroup "ppad-sha512" [ - bgroup "SHA512 (32B input)" [ - bench "hash" $ whnf SHA512.hash bs - , bench "hash_lazy" $ whnf SHA512.hash_lazy bl - , bench "SHA.sha512" $ whnf SHA.sha512 bl +suite = + let !bs = BS.replicate 32 0 + !bl = BL.fromStrict bs + !mac0 = SHA512.hmac "key" "foo" + !mac1 = SHA512.hmac "key" "bar" + !mac2 = SHA512.hmac "key" "foo" + !macl0 = SHA512.hmac_lazy "key" "foo" + !macl1 = SHA512.hmac_lazy "key" "bar" + !macl2 = SHA512.hmac_lazy "key" "foo" + in bgroup "ppad-sha512" [ + bgroup "SHA512 (32B input)" [ + bench "hash" $ whnf SHA512.hash bs + , bench "hash_lazy" $ whnf SHA512.hash_lazy bl + , bench "SHA.sha512" $ whnf SHA.sha512 bl + ] + , bgroup "HMAC-SHA512 (32B input)" [ + bench "hmac" $ whnf (SHA512.hmac "key") bs + , bench "hmac_lazy" $ whnf (SHA512.hmac_lazy "key") bl + , bench "SHA.hmacSha512" $ whnf (SHA.hmacSha512 "key") bl + ] + , bgroup "MAC comparison" [ + bench "hmac, unequal" $ whnf (mac0 ==) mac1 + , bench "hmac, equal" $ whnf (mac0 ==) mac2 + , bench "hmac_lazy, unequal" $ whnf (macl0 ==) macl1 + , bench "hmac_lazy, equal" $ whnf (macl0 ==) macl2 + ] ] - , bgroup "HMAC-SHA512 (32B input)" [ - bench "hmac" $ whnf (SHA512.hmac "key") bs - , bench "hmac_lazy" $ whnf (SHA512.hmac_lazy "key") bl - , bench "SHA.hmacSha512" $ whnf (SHA.hmacSha512 "key") bl - ] - ] - where - setup = do - let bs_32B = BS.replicate 32 0 - bl_32B = BL.fromStrict bs_32B - pure (bs_32B, bl_32B) diff --git a/lib/Crypto/Hash/SHA512.hs b/lib/Crypto/Hash/SHA512.hs @@ -22,6 +22,7 @@ module Crypto.Hash.SHA512 ( , Lazy.hash_lazy -- * SHA512-based MAC functions + , MAC(..) , hmac , Lazy.hmac_lazy ) where @@ -94,15 +95,15 @@ data KeyAndLen = KeyAndLen hmac :: BS.ByteString -- ^ key -> BS.ByteString -- ^ text - -> BS.ByteString + -> MAC hmac mk@(BI.PS _ _ l) text | sha512_arm_available = let !inner = hash_arm_with ipad 128 text - in hash_arm (opad <> inner) + in MAC (hash_arm (opad <> inner)) | otherwise = let !ipad_state = block_hash iv (prepare_schedule (parse_block ipad 0)) !inner = cat (process_with ipad_state 128 text) - in hash (opad <> inner) + in MAC (hash (opad <> inner)) where !step1 = k <> BS.replicate (128 - lk) 0x00 !ipad = BS.map (B.xor 0x36) step1 diff --git a/lib/Crypto/Hash/SHA512/Internal.hs b/lib/Crypto/Hash/SHA512/Internal.hs @@ -1,5 +1,7 @@ {-# OPTIONS_HADDOCK hide #-} {-# LANGUAGE BangPatterns #-} +{-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE RecordWildCards #-} -- | @@ -15,6 +17,8 @@ module Crypto.Hash.SHA512.Internal ( , Block(..) , Schedule(..) + , MAC(..) + , iv , block_hash , prepare_schedule @@ -42,6 +46,31 @@ import Foreign.Marshal.Utils (copyBytes, fillBytes) import Foreign.Ptr (Ptr, plusPtr) import Foreign.Storable (poke) +-- MAC type ------------------------------------------------------------------ + +-- | A message authentication code. +-- +-- Note that you should compare MACs for equality using the 'Eq' +-- instance, which performs the comparison in constant time, instead +-- of unwrapping and comparing the underlying 'ByteStrings'. +-- +-- >>> let foo@(MAC bs0) = hmac key "hi" +-- >>> let bar@(MAC bs1) = hmac key "there" +-- >>> foo == bar -- do this +-- False +-- >>> bs0 == bs1 -- don't do this +-- False +newtype MAC = MAC BS.ByteString + deriving newtype Show + +instance Eq MAC where + -- | A constant-time equality check for message authentication codes. + -- + -- Runs in variable-time only for invalid inputs. + (MAC a@(BI.PS _ _ la)) == (MAC b@(BI.PS _ _ lb)) + | la /= lb = False + | otherwise = BS.foldl' (B..|.) 0 (BS.packZipWith B.xor a b) == 0 + -- preliminary utils --------------------------------------------------------- fi :: (Integral a, Num b) => a -> b diff --git a/lib/Crypto/Hash/SHA512/Lazy.hs b/lib/Crypto/Hash/SHA512/Lazy.hs @@ -21,6 +21,7 @@ module Crypto.Hash.SHA512.Lazy ( , hmac_lazy ) where +import Crypto.Hash.SHA512.Internal import qualified Data.Bits as B import qualified Data.ByteString as BS import qualified Data.ByteString.Builder as BSB @@ -30,7 +31,6 @@ import qualified Data.ByteString.Lazy as BL import qualified Data.ByteString.Lazy.Internal as BLI import Data.Word (Word64) import Foreign.ForeignPtr (plusForeignPtr) -import Crypto.Hash.SHA512.Internal -- preliminary utils @@ -136,7 +136,7 @@ data KeyAndLen = KeyAndLen hmac_lazy :: BS.ByteString -- ^ key -> BL.ByteString -- ^ text - -> BS.ByteString + -> MAC hmac_lazy mk@(BI.PS _ _ l) text = let step1 = k <> BS.replicate (128 - lk) 0x00 step2 = BS.map (B.xor 0x36) step1 @@ -144,7 +144,7 @@ hmac_lazy mk@(BI.PS _ _ l) text = step4 = hash_lazy step3 step5 = BS.map (B.xor 0x5C) step1 step6 = step5 <> step4 - in hash step6 + in MAC (hash step6) where hash bs = cat (go iv (pad bs)) where go :: Registers -> BS.ByteString -> Registers diff --git a/test/Main.hs b/test/Main.hs @@ -46,7 +46,8 @@ execute tag_size W.MacTest {..} = testCase t_msg $ do let key = decodeLenient (TE.encodeUtf8 mt_key) msg = decodeLenient (TE.encodeUtf8 mt_msg) pec = decodeLenient (TE.encodeUtf8 mt_tag) - out = BS.take bytes (SHA512.hmac key msg) + SHA512.MAC mac = SHA512.hmac key msg + out = BS.take bytes mac if mt_result == "invalid" then assertBool "invalid" (pec /= out) else assertEqual mempty pec out @@ -83,13 +84,16 @@ unit_tests = testGroup "unit tests" [ , cmp_hmac "hmv3" hmv3_key hmv3_put hmv3_pec , cmp_hmac "hmv4" hmv4_key hmv4_put hmv4_pec , testCase "hmv5" $ do - let out = BS.take 32 $ B16.encode (SHA512.hmac hmv5_key hmv5_put) + let SHA512.MAC mac = SHA512.hmac hmv5_key hmv5_put + out = BS.take 32 $ B16.encode mac assertEqual mempty hmv5_pec out , testCase "hmv6" $ do - let out = B16.encode (SHA512.hmac hmv6_key hmv6_put) + let SHA512.MAC mac = SHA512.hmac hmv6_key hmv6_put + out = B16.encode mac assertEqual mempty hmv6_pec out , testCase "hmv7" $ do - let out = B16.encode (SHA512.hmac hmv7_key hmv7_put) + let SHA512.MAC mac = SHA512.hmac hmv7_key hmv7_put + out = B16.encode mac assertEqual mempty hmv7_pec out ] , testGroup "hmac_lazy" [ @@ -99,15 +103,18 @@ unit_tests = testGroup "unit tests" [ , cmp_hmac_lazy "hmv4" hmv4_key hmv4_put hmv4_pec , testCase "hmv5" $ do let lut = BL.fromStrict hmv5_put - out = BS.take 32 $ B16.encode (SHA512.hmac_lazy hmv5_key lut) + SHA512.MAC mac = SHA512.hmac_lazy hmv5_key lut + out = BS.take 32 $ B16.encode mac assertEqual mempty hmv5_pec out , testCase "hmv6" $ do let lut = BL.fromStrict hmv6_put - out = B16.encode (SHA512.hmac_lazy hmv6_key lut) + SHA512.MAC mac = SHA512.hmac_lazy hmv6_key lut + out = B16.encode mac assertEqual mempty hmv6_pec out , testCase "hmv7" $ do let lut = BL.fromStrict hmv7_put - out = B16.encode (SHA512.hmac_lazy hmv7_key lut) + SHA512.MAC mac = SHA512.hmac_lazy hmv7_key lut + out = B16.encode mac assertEqual mempty hmv7_pec out ] ] @@ -229,12 +236,14 @@ cmp_hash_lazy msg (BL.fromStrict -> put) pec = testCase msg $ do cmp_hmac :: String -> BS.ByteString -> BS.ByteString -> BS.ByteString -> TestTree cmp_hmac msg key put pec = testCase msg $ do - let out = B16.encode (SHA512.hmac key put) + let SHA512.MAC mac = SHA512.hmac key put + out = B16.encode mac assertEqual mempty pec out cmp_hmac_lazy :: String -> BS.ByteString -> BS.ByteString -> BS.ByteString -> TestTree cmp_hmac_lazy msg key (BL.fromStrict -> put) pec = testCase msg $ do - let out = B16.encode (SHA512.hmac_lazy key put) + let SHA512.MAC mac = SHA512.hmac_lazy key put + out = B16.encode mac assertEqual mempty pec out