A pure Haskell implementation of the HMAC-DRBG cryptographically-secure PRNG, as specified by NIST SP 800-90A.
A sample GHCi session:
> -- extensions/b16 import just for illustration here; not required for use
> :set -XOverloadedStrings
> :set -XRankNTypes
> import qualified Data.ByteString.Base16 as B16
>
> -- import qualified
> import qualified Crypto.DRBG.HMAC.SHA256 as DRBG
>
> -- instantiate a DRBG
> let entropy = "very random"
> let nonce = "very unused"
> let personalization_string = "very personal"
>
> drbg <- DRBG.new entropy nonce personalization_string
>
> -- use it to generate some bytes
>
> fmap B16.encode <$> DRBG.gen drbg mempty 32
Right "e4d17210810c4b343f6eae2c19e3d82395b555294b1b16a85f91dbea67e5f277"
>
> -- reuse the generator to get more; the state is updated automatically
>
> fmap B16.encode <$> DRBG.gen drbg mempty 16
Right "5d867730d99eb5335f16b1d622f03023"
>
> -- this DRBG was instantiated in the IO monad:
>
> :t drbg
drbg :: DRBG.DRBG ghc-prim:GHC.Prim.RealWorld
>
> -- but you can also use ST to keep things pure:
>
> import Control.Monad.ST
>
> :{
ghci| let drbg_pure = DRBG.new mempty mempty mempty ::
ghci| forall s. ST s (DRBG.DRBG s)
ghci| :}
>
> :t drbg_pure
drbg_pure :: ST s (DRBG.DRBG s)
>
> runST $ drbg_pure >>= fmap (fmap B16.encode) . (\d -> DRBG.gen d mempty 16)
Right "b44299907e4e42aa4fded5d6153e8bac"
Haddocks (API documentation, etc.) are hosted at docs.ppad.tech/hmac-drbg.
The aim is best-in-class performance for pure, highly-auditable Haskell code.
Current benchmark figures on an M4 Silicon MacBook Air look like (use
cabal bench to run the benchmark suite):
benchmarking ppad-hmac-drbg/HMAC-SHA256/new
time 225.2 ns (224.3 ns .. 226.4 ns)
0.999 R² (0.998 R² .. 1.000 R²)
mean 233.4 ns (227.9 ns .. 241.8 ns)
std dev 23.42 ns (12.58 ns .. 34.87 ns)
variance introduced by outliers: 90% (severely inflated)
benchmarking ppad-hmac-drbg/HMAC-SHA256/reseed
time 211.3 ns (210.6 ns .. 211.9 ns)
1.000 R² (1.000 R² .. 1.000 R²)
mean 210.7 ns (210.3 ns .. 211.1 ns)
std dev 1.381 ns (1.133 ns .. 1.766 ns)
benchmarking ppad-hmac-drbg/HMAC-SHA256/gen (32B)
time 367.3 ns (366.4 ns .. 368.3 ns)
0.999 R² (0.999 R² .. 1.000 R²)
mean 375.9 ns (370.3 ns .. 388.7 ns)
std dev 28.42 ns (13.66 ns .. 55.18 ns)
variance introduced by outliers: 83% (severely inflated)
benchmarking ppad-hmac-drbg/HMAC-SHA256/gen (256B)
time 1.472 μs (1.468 μs .. 1.476 μs)
1.000 R² (1.000 R² .. 1.000 R²)
mean 1.470 μs (1.465 μs .. 1.474 μs)
std dev 15.77 ns (12.15 ns .. 21.36 ns)
You should compile with the 'llvm' flag for maximum performance.
This library aims at the maximum security achievable in a garbage-collected language under an optimizing compiler such as GHC, in which strict constant-timeness can be challenging to achieve.
The HMAC-DRBG implementation within has been tested against the NIST DRBGVS vectors available for SHA-256 and SHA-512.
DRBG internal state, which must be kept secret, is kept in a single, pinned, heap-allocated mutable buffer. It is never copied, is guaranteed never to be moved around by the garbage collector, and its components are never allocated anywhere else on the heap. You should zero out the DRBG state via the 'wipe' function when you've finished using it.
(The security properties of this library have been examined and defended in more detail in a security analysis at ppad.tech.)
If you discover any vulnerabilities, please disclose them via security@ppad.tech.
You'll require Nix with flake support enabled. Enter a development shell with:
$ nix develop
Then do e.g.:
$ cabal repl ppad-hmac-drbg
to get a REPL for the main library.