hmac-drbg

Pure Haskell HMAC-DRBG per NIST-SP 800-90A.
git clone git://git.ppad.tech/hmac-drbg.git
Log | Files | Refs | LICENSE

commit 9df254eba641370663206b1e0e2448b91f38974e
parent e11123f4fb2da5ca4737358db6ba50179837667b
Author: Jared Tobin <jared@jtobin.io>
Date:   Fri,  4 Oct 2024 15:18:55 +0400

lib: track reseed counter

Diffstat:
Mbench/Main.hs | 7+++----
Mlib/Crypto/DRBG/HMAC.hs | 86++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------
Mtest/Main.hs | 1-
3 files changed, 64 insertions(+), 30 deletions(-)

diff --git a/bench/Main.hs b/bench/Main.hs @@ -18,11 +18,10 @@ main = do suite drbg = bgroup "ppad-hmac-drbg" [ bgroup "HMAC-SHA256" [ - bench "gen (32B)" $ whnfAppIO (DRBG.gen mempty 32) drbg - , bench "gen (64B)" $ whnfAppIO (DRBG.gen mempty 64) drbg - , bench "gen (128B)" $ whnfAppIO (DRBG.gen mempty 128) drbg + bench "new" $ whnfAppIO (DRBG.new SHA256.hmac mempty mempty) mempty + , bench "reseed" $ whnfAppIO (DRBG.reseed mempty mempty) drbg + , bench "gen (32B)" $ whnfAppIO (DRBG.gen mempty 32) drbg , bench "gen (256B)" $ whnfAppIO (DRBG.gen mempty 256) drbg - , bench "gen (512B)" $ whnfAppIO (DRBG.gen mempty 512) drbg ] ] diff --git a/lib/Crypto/DRBG/HMAC.hs b/lib/Crypto/DRBG/HMAC.hs @@ -15,6 +15,7 @@ module Crypto.DRBG.HMAC ( import Control.Monad.Primitive (PrimMonad, PrimState) import qualified Data.ByteString as BS import qualified Data.ByteString.Builder as BSB +import qualified Data.ByteString.Builder.Extra as BE import qualified Data.Primitive.MutVar as P import Data.Word (Word64) @@ -28,16 +29,27 @@ toStrict :: BSB.Builder -> BS.ByteString toStrict = BS.toStrict . BSB.toLazyByteString {-# INLINE toStrict #-} +toStrictSmall :: BSB.Builder -> BS.ByteString +toStrictSmall = + BS.toStrict + . BE.toLazyByteStringWith + (BE.safeStrategy 128 BE.smallChunkSize) mempty +{-# INLINE toStrictSmall #-} + -- dumb strict pair data Pair a b = Pair !a !b deriving Show -- types ---------------------------------------------------------------------- --- HMAC function and its associated outlength -data HMAC = HMAC - !(BS.ByteString -> BS.ByteString -> BS.ByteString) - {-# UNPACK #-} !Word64 +_RESEED_COUNTER :: Word64 +_RESEED_COUNTER = (2 :: Word64) ^ (48 :: Word64) + +-- | A deterministic random bit generator (DRBG). +-- +-- Create a DRBG with 'new', and then use and reuse it to generate +-- bytes as needed. +newtype DRBG s = DRBG (P.MutVar s DRBGState) -- DRBG environment data and state -- @@ -45,11 +57,14 @@ data HMAC = HMAC -- strength, length input verification, etc. data DRBGState = DRBGState !HMAC -- hmac function & outlen + !Word64 -- reseed counter {-# UNPACK #-} !BS.ByteString -- v {-# UNPACK #-} !BS.ByteString -- key --- | The DRBG. -newtype DRBG s = DRBG (P.MutVar s DRBGState) +-- HMAC function and its associated outlength +data HMAC = HMAC + !(BS.ByteString -> BS.ByteString -> BS.ByteString) + {-# UNPACK #-} !Word64 -- | Read the 'V' value from the DRBG state. Useful for testing. _read_v @@ -57,7 +72,7 @@ _read_v => DRBG (PrimState m) -> m BS.ByteString _read_v (DRBG mut) = do - DRBGState _ v _ <- P.readMutVar mut + DRBGState _ _ v _ <- P.readMutVar mut pure v -- | Read the 'Key' value from the DRBG state. Useful for testing. @@ -66,13 +81,19 @@ _read_k => DRBG (PrimState m) -> m BS.ByteString _read_k (DRBG mut) = do - DRBGState _ _ k <- P.readMutVar mut + DRBGState _ _ _ k <- P.readMutVar mut pure k -- drbg interaction ------------------------------------------------------ -- | Create a DRBG from the supplied HMAC function, entropy, nonce, and -- personalization string. +-- +-- Returns the DRBG in any 'PrimMonad', e.g. 'ST' or 'IO'. +-- +-- >>> import qualified Crypto.Hash.SHA256 as SHA256 +-- >>> new SHA256.hmac entropy nonce personalization_string +-- "<drbg>" new :: PrimMonad m => (BS.ByteString -> BS.ByteString -> BS.ByteString) -- HMAC function @@ -87,8 +108,11 @@ new hmac entropy nonce ps = do -- | Reseed a DRBG. -- --- Note that this can be used to implement "explicit" permission +-- Note that this can be used to implement "explicit" prediction -- resistance by injecting entropy generated elsewhere. +-- +-- >>> reseed entropy addl_bytes drbg +-- "<reseeded drbg>" reseed :: PrimMonad m => BS.ByteString @@ -97,7 +121,17 @@ reseed -> m () reseed ent add (DRBG drbg) = P.modifyMutVar' drbg (reseed_pure ent add) --- | Generate bytes from a DRBG. +-- | Generate bytes from a DRBG, optionally injecting additional bytes +-- per SP 800-90A. +-- +-- >>> import qualified Data.ByteString.Base16 as B16 +-- >>> drbg <- new SHA256.hmac entropy nonce personalization_string +-- >>> bytes0 <- gen addl_bytes 16 drbg +-- >>> bytes1 <- gen addl_bytes 16 drbg +-- >>> B16.encode bytes0 +-- "938d6ca6d0b797f7b3c653349d6e3135" +-- >>> B16.encode bytes1 +-- "5f379d16de6f2c6f8a35c56f13f9e5a5" gen :: PrimMonad m => BS.ByteString @@ -116,17 +150,16 @@ update_pure :: BS.ByteString -> DRBGState -> DRBGState -update_pure provided_data (DRBGState h@(HMAC hmac _) v0 k0) = +update_pure provided_data (DRBGState h@(HMAC hmac _) r v0 k0) = let !k1 = hmac k0 (cat v0 0x00 provided_data) !v1 = hmac k1 v0 in if BS.null provided_data - then (DRBGState h v1 k1) + then DRBGState h r v1 k1 else let !k2 = hmac k1 (cat v1 0x01 provided_data) !v2 = hmac k2 v1 - in DRBGState h v2 k2 + in DRBGState h r v2 k2 where - -- XX custom builder strategy possibly more efficient here - cat bs byte suf = toStrict $ + cat bs byte suf = toStrictSmall $ BSB.byteString bs <> BSB.word8 byte <> BSB.byteString suf new_pure @@ -136,30 +169,33 @@ new_pure -> BS.ByteString -- personalization string -> DRBGState new_pure hmac entropy nonce ps = - let !drbg = DRBGState (HMAC hmac outlen) v0 k0 + let !drbg = DRBGState (HMAC hmac outlen) 1 v0 k0 in update_pure seed_material drbg where - -- XX any better to use builder? seed_material = entropy <> nonce <> ps outlen = fi (BS.length (hmac mempty mempty)) k0 = BS.replicate (fi outlen) 0x00 v0 = BS.replicate (fi outlen) 0x01 reseed_pure :: BS.ByteString -> BS.ByteString -> DRBGState -> DRBGState -reseed_pure entropy addl drbg = update_pure (entropy <> addl) drbg +reseed_pure entropy addl drbg = + let !(DRBGState h _ v k) = update_pure (entropy <> addl) drbg + in DRBGState h 1 v k gen_pure :: BS.ByteString -> Word64 -> DRBGState -> Pair BS.ByteString DRBGState -gen_pure addl bytes drbg0@(DRBGState h@(HMAC hmac outlen) _ _) = - let !(Pair temp drbg1) = loop mempty 0 v1 - returned_bits = BS.take (fi bytes) temp - drbg = update_pure addl drbg1 - in Pair returned_bits drbg +gen_pure addl bytes drbg0@(DRBGState h@(HMAC hmac outlen) _ _ _) + | r > _RESEED_COUNTER = error "ppad-sha256: reseed required" + | otherwise = + let !(Pair temp drbg1) = loop mempty 0 v1 + returned_bits = BS.take (fi bytes) temp + drbg = update_pure addl drbg1 + in Pair returned_bits drbg where - !(DRBGState _ v1 k1) + !(DRBGState _ r v1 k1) | BS.null addl = drbg0 | otherwise = update_pure addl drbg0 @@ -172,5 +208,5 @@ gen_pure addl bytes drbg0@(DRBGState h@(HMAC hmac outlen) _ _) = | otherwise = let facc = toStrict acc - in Pair facc (DRBGState h vl k1) + in Pair facc (DRBGState h (succ r) vl k1) diff --git a/test/Main.hs b/test/Main.hs @@ -26,7 +26,6 @@ main = do Right cs -> defaultMain (cavs_14_3 cs) --- XX additionalInput cases not being handled correctly cavs_14_3 :: [Case] -> TestTree cavs_14_3 cs = testGroup "CAVS 14.3" [ testGroup "SHA-256" (fmap (execute SHA256.hmac) cs)