hkdf

Pure Haskell HMAC-based KDF (docs.ppad.tech/hkdf).
git clone git://git.ppad.tech/hkdf.git
Log | Files | Refs | README | LICENSE

commit 5812ff7eedc5bb03f1166ef7b3843b6ec9cdcc0f
parent e4be3c94d2dd8cec85892fdc7c07eb3576502089
Author: Jared Tobin <jared@jtobin.io>
Date:   Fri, 10 Jan 2025 18:28:19 +0400

lib: s/hkdf/derive

Gels nicer with a qualified import.

Diffstat:
MREADME.md | 9++++++---
Mbench/Main.hs | 8+++++---
Mlib/Crypto/KDF/HMAC.hs | 11++++++-----
Mtest/Main.hs | 6+++---
4 files changed, 20 insertions(+), 14 deletions(-)

diff --git a/README.md b/README.md @@ -9,11 +9,14 @@ A sample GHCi session: ``` > :set -XOverloadedStrings + > -- import qualified + > import qualified Crypto.KDF.HMAC as KDF + > > -- supply your own HMAC function > import qualified Crypto.Hash.SHA256 as SHA256 > > -- derive a 32-byte key from a secret - > hkdf SHA256.hmac "my salt" "my optional info" 32 "my secret input" + > KDF.derive SHA256.hmac "my salt" "my optional info" 32 "my secret input" "\EM\232\v\140\202\230\f2:\221n\221\209\233\US\209>\174_!\138\255\\C\150\237^X\226\tt\252" ``` @@ -31,14 +34,14 @@ Current benchmark figures on my mid-2020 MacBook Air look like (use `cabal bench` to run the benchmark suite): ``` - benchmarking ppad-hkdf/HKDF-SHA256/32 + benchmarking ppad-hkdf/HKDF-SHA256/derive (outlen 32) time 12.69 μs (12.58 μs .. 12.84 μs) 0.999 R² (0.999 R² .. 1.000 R²) mean 12.82 μs (12.75 μs .. 12.89 μs) std dev 231.0 ns (190.1 ns .. 299.9 ns) variance introduced by outliers: 16% (moderately inflated) - benchmarking ppad-hkdf/HKDF-SHA512/32 + benchmarking ppad-hkdf/HKDF-SHA512/derive (outlen 32) time 12.24 μs (12.16 μs .. 12.31 μs) 1.000 R² (1.000 R² .. 1.000 R²) mean 12.27 μs (12.22 μs .. 12.32 μs) diff --git a/bench/Main.hs b/bench/Main.hs @@ -3,7 +3,7 @@ module Main where import Criterion.Main -import qualified Crypto.KDF.HMAC as K +import qualified Crypto.KDF.HMAC as KDF import qualified Crypto.Hash.SHA256 as SHA256 import qualified Crypto.Hash.SHA512 as SHA512 @@ -16,10 +16,12 @@ suite :: Benchmark suite = bgroup "ppad-hkdf" [ bgroup "HKDF-SHA256" [ - bench "32" $ nf (K.hkdf SHA256.hmac "muh salt" "muh info" 32) "muh secret" + bench "derive (outlen 32)" $ + nf (KDF.derive SHA256.hmac "muh salt" "muh info" 32) "muh secret" ] , bgroup "HKDF-SHA512" [ - bench "32" $ nf (K.hkdf SHA512.hmac "muh salt" "muh info" 32) "muh secret" + bench "derive (outlen 32)" $ + nf (KDF.derive SHA512.hmac "muh salt" "muh info" 32) "muh secret" ] ] diff --git a/lib/Crypto/KDF/HMAC.hs b/lib/Crypto/KDF/HMAC.hs @@ -13,7 +13,7 @@ module Crypto.KDF.HMAC ( -- * HMAC-based KDF - hkdf + derive , HMAC -- internals @@ -75,22 +75,23 @@ expand (HMACEnv hmac hashlen) info (fi -> len) prk in go (succ j) (t <> BSB.byteString nt) nt {-# INLINE expand #-} --- | HMAC-based key derivation function. +-- | Derive a key from a secret, via a HMAC-based key derivation +-- function. -- -- The /salt/ and /info/ arguments are optional to the KDF, and may -- be simply passed as 'mempty'. An empty salt will be replaced by -- /hashlen/ zero bytes. -- -- >>> import qualified Crypto.Hash.SHA256 as SHA256 --- >>> hkdf SHA256.hmac "my public salt" mempty 64 "my secret input" +-- >>> derive SHA256.hmac "my public salt" mempty 64 "my secret input" -- <64-byte output keying material> -hkdf +derive :: HMAC -- ^ HMAC function -> BS.ByteString -- ^ salt -> BS.ByteString -- ^ optional context and application-specific info -> Word64 -- ^ bytelength of output keying material (<= 255 * hashlen) -> BS.ByteString -- ^ input keying material -> BS.ByteString -- ^ output keying material -hkdf hmac salt info len = expand env info len . extract env salt where +derive hmac salt info len = expand env info len . extract env salt where env = HMACEnv hmac (fi (BS.length (hmac mempty mempty))) diff --git a/test/Main.hs b/test/Main.hs @@ -6,7 +6,7 @@ module Main where import Control.Exception import qualified Crypto.Hash.SHA256 as SHA256 import qualified Crypto.Hash.SHA512 as SHA512 -import qualified Crypto.KDF.HMAC as H +import qualified Crypto.KDF.HMAC as KDF import qualified Data.ByteString as BS import qualified Data.Aeson as A import qualified Data.Text.IO as TIO @@ -52,13 +52,13 @@ execute h W.HkdfTest {..} = testCase t_msg $ do pec = ht_okm if ht_result == "invalid" then do - out <- try (pure $! H.hkdf hmac sal inf siz ikm) + out <- try (pure $! KDF.derive hmac sal inf siz ikm) :: IO (Either ErrorCall BS.ByteString) case out of Left _ -> assertBool "invalid" True Right o -> assertBool "invalid" (pec /= o) else do - let out = H.hkdf hmac sal inf siz ikm + let out = KDF.derive hmac sal inf siz ikm assertEqual mempty pec out where hmac = case h of