hmac-drbg

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

commit f3f2ea5d9e1058547bdaac505420ab976f542039
parent 36c1a780cf1b7a3fd305c224298d3bf0a2afc6e6
Author: Jared Tobin <jared@jtobin.io>
Date:   Mon, 30 Sep 2024 11:33:33 +0400

lib: develop bulk of lib

Diffstat:
A.gitignore | 1+
Mlib/Crypto/DRBG/HMAC.hs | 106+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mppad-hmac-drbg.cabal | 17+++++++++++++++++
Atest/Main.hs | 7+++++++
4 files changed, 131 insertions(+), 0 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1 @@ +dist-newstyle diff --git a/lib/Crypto/DRBG/HMAC.hs b/lib/Crypto/DRBG/HMAC.hs @@ -1,3 +1,109 @@ +{-# OPTIONS_GHC -funbox-small-strict-fields #-} +{-# LANGUAGE BangPatterns #-} +{-# LANGUAGE LambdaCase #-} module Crypto.DRBG.HMAC where +import qualified Crypto.Hash.SHA256 as SHA256 +import qualified Data.ByteString as BS +import qualified Data.ByteString.Builder as BSB +import Data.Word (Word64) + +-- keystroke saver +fi :: (Integral a, Num b) => a -> b +fi = fromIntegral +{-# INLINE fi #-} + +_RESEED_INTERVAL :: Word64 +_RESEED_INTERVAL = (2 :: Word64) ^ (48 :: Word64) + +data HMAC = HMAC + !(BS.ByteString -> BS.ByteString -> BS.ByteString) + {-# UNPACK #-} !Word64 + +data DRBG = DRBG + !HMAC -- hmac function & outlen + {-# UNPACK #-} !BS.ByteString -- v + {-# UNPACK #-} !BS.ByteString -- key + {-# UNPACK #-} !Word64 -- reseed_counter + +instance Show DRBG where + show (DRBG _ v k r) = "DRBG " <> show v <> " " <> show k <> " " <> show r + +-- dumb strict pair +data Pair a b = Pair !a !b + deriving Show + +update + :: BS.ByteString + -> DRBG + -> DRBG +update provided_data (DRBG h@(HMAC hmac _) v0 k0 r) = + let !k1 = hmac k0 (suf 0x00 v0) + !v1 = hmac k1 v0 + in if BS.null provided_data + then (DRBG h v1 k1 r) + else let !k2 = hmac k1 (suf 0x01 v1) + !v2 = hmac k2 v1 + in DRBG h v2 k2 r + where + suf byte bs = BS.toStrict + . BSB.toLazyByteString + $ BSB.byteString bs <> BSB.word8 byte <> BSB.byteString provided_data + +instantiate + :: (BS.ByteString -> BS.ByteString -> BS.ByteString) + -> BS.ByteString + -> BS.ByteString + -> BS.ByteString + -> DRBG +instantiate hmac entropy nonce ps = + let drbg = DRBG (HMAC hmac outlen) v0 k0 1 + in update seed_material drbg + where + seed_material = entropy <> nonce <> ps + outlen = fi (BS.length (hmac mempty mempty)) -- UX hack, costs 1 hmac call + k0 = BS.replicate (fi outlen) 0x00 + v0 = BS.replicate (fi outlen) 0x01 + +reseed :: DRBG -> BS.ByteString -> BS.ByteString -> DRBG +reseed drbg entropy addl = + let !(DRBG hmac v k _) = update seed_material drbg + in DRBG hmac v k 1 + where + seed_material = entropy <> addl + +generate + :: BS.ByteString + -> Word64 + -> DRBG + -> (BS.ByteString, DRBG) +generate addl bytes drbg0@(DRBG h@(HMAC hmac outlen) _ _ r) + | r > _RESEED_INTERVAL = error "ppad-hmac-drbg: DRBG reseed required" + | otherwise = + let !(Pair temp drbg1) = go mempty 0 v1 + + !returned_bits = BS.take (fi bytes) temp + !drbg2 = update addl drbg1 + + in (returned_bits, drbg2) + where + !(DRBG _ v1 k1 _) + | BS.null addl = drbg0 + | otherwise = update addl drbg0 + + go !acc !len !vl + | len < bytes = + let nv = hmac k1 vl + nacc = acc <> BSB.byteString nv + nlen = len + outlen + in go nacc nlen nv + + -- take opportunity to update reseed_counter here + | otherwise = + let facc = BS.toStrict . BSB.toLazyByteString $ acc + in Pair facc (DRBG h vl k1 (succ r)) + + +-- XX test against +-- https://raw.githubusercontent.com/coruus/nist-testvectors/refs/heads/master/csrc.nist.gov/groups/STM/cavp/documents/drbg/drbgtestvectors/drbgvectors_pr_true/HMAC_DRBG.txt diff --git a/ppad-hmac-drbg.cabal b/ppad-hmac-drbg.cabal @@ -30,3 +30,20 @@ library , bytestring >= 0.9 && < 0.13 , ppad-sha256 >= 0.1.0 && < 0.2.0 +test-suite hmac-drbg-tests + type: exitcode-stdio-1.0 + default-language: Haskell2010 + hs-source-dirs: test + main-is: Main.hs + + ghc-options: + -rtsopts -Wall -O2 + + build-depends: + base + , base16-bytestring + , bytestring + , ppad-hmac-drbg + , tasty + , tasty-hunit + diff --git a/test/Main.hs b/test/Main.hs @@ -0,0 +1,7 @@ + +module Main where + +import qualified Crypto.DRBG.HMAC as DRBG +import qualified Data.ByteString as BS +import qualified Data.ByteString.Base16 as B16 +