Skip to content

Commit 1e7ec15

Browse files
committed
Implement HMAC-based key derivation functions
This commit adds the `Crypto.Hash.SHA384.hkdf` and `Crypto.Hash.SHA512.hkdf` functions providing HKDF-SHA384 and HKDF-SHA512 respectively, as per RFC 5869. These aren't as common as HKDF-SHA256 but they might still occur in exotic use cases and many cryptographic libraries do support these modes. HKDF-SHA512/t, however, doesn't appear to occur at all in practice, so it's left out for now.
1 parent 1eb8406 commit 1e7ec15

File tree

6 files changed

+128
-4
lines changed

6 files changed

+128
-4
lines changed

changelog.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
See also http://pvp.haskell.org/faq
22

3+
## 0.11.103.0
4+
5+
- add `Crypto.Hash.SHA384.hkdf` and `Crypto.Hash.SHA512.hkdf` functions
6+
providing HKDF-SHA384 and HKDF-SHA512 respectively, as per RFC5869
7+
38
## 0.11.102.0
49

510
- expose SHA512/t variant via new `Crypto.Hash.SHA512t` module

cryptohash-sha512.cabal

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
cabal-version: 2.0
22
name: cryptohash-sha512
3-
version: 0.11.102.0
4-
x-revision: 5
3+
version: 0.11.103.0
54

65
synopsis: Fast, pure and practical SHA-512 implementation
76
description: {
@@ -20,6 +19,8 @@ Additionally, this package provides support for
2019
- HMAC-SHA-384: SHA-384-based [Hashed Message Authentication Codes](https://en.wikipedia.org/wiki/HMAC) (HMAC)
2120
- HMAC-SHA-512: SHA-512-based [Hashed Message Authentication Codes](https://en.wikipedia.org/wiki/HMAC) (HMAC)
2221
- HMAC-SHA-512\/t: SHA-512\/t-based [Hashed Message Authentication Codes](https://en.wikipedia.org/wiki/HMAC) (HMAC)
22+
- HKDF-SHA-384: [HMAC-SHA-384-based Key Derivation Function](https://en.wikipedia.org/wiki/HKDF) (HKDF)
23+
- HKDF-SHA-512: [HMAC-SHA-512-based Key Derivation Function](https://en.wikipedia.org/wiki/HKDF) (HKDF)
2324
.
2425
conforming to [RFC6234](https://tools.ietf.org/html/rfc6234), [RFC4231](https://tools.ietf.org/html/rfc4231), [RFC5869](https://tools.ietf.org/html/rfc5869), et al..
2526
.

src-tests/test-sha384.hs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,9 +236,37 @@ refImplTests =
236236
toStrict = B.concat . BL.toChunks
237237
fromStrict = BL.fromChunks . (:[])
238238

239+
-- generated via `openssl kdf -kdfopt digest:SHA2-512`
240+
hkdfVectors :: [(Int,ByteString,ByteString,ByteString,ByteString)]
241+
hkdfVectors = -- (l,ikm,salt,info,okm)
242+
[ (42, rep 22 0x0b, x"000102030405060708090a0b0c", x"f0f1f2f3f4f5f6f7f8f9", x"9b5097a86038b805309076a44b3a9f38063e25b516dcbf369f394cfab43685f748b6457763e4f0204fc5")
243+
, ( 82
244+
, x"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f"
245+
, x"606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeaf"
246+
, x"b0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"
247+
, x"484ca052b8cc724fd1c4ec64d57b4e818c7e25a8e0f4569ed72a6a05fe0649eebf69f8d5c832856bf4e4fbc17967d54975324a94987f7f41835817d8994fdbd6f4c09c5500dca24a56222fea53d8967a8b2e"
248+
)
249+
, ( 42, rep 22 0x0b, "", "", x"c8c96e710f89b0d7990bca68bcdec8cf854062e54c73a7abc743fade9b242daacc1cea5670415b52849c")
250+
, ( 42, rep 11 0x0b, x"000102030405060708090a0b0c", x"f0f1f2f3f4f5f6f7f8f9", x"fb7e6743eb42cde96f1b70778952ab7548cafe53249f7ffe1497a1635b201ff185b93e951992d858f11a" )
251+
, ( 42, rep 22 0x0c, "", "", x"6ad7c726c84009546a76e0545df266787e2b2cd6ca4373a1f31450a7bdf9482bfab811f554200ead8f53" )
252+
, ( 1317, rep 17 0x33, "HeLoLoL", "WoRlDoD", x"01baa7d9cf33777f237967ddce0203a2d7269de62910f00963283b127ccd8546de690cf37b767bc1a435a54a4bcddac63a5eade30bffc1d0a91ceac2c0afc3ff5dea18436db433c071da8c7fbc51376a88b2c7a131bcb49056f37159455ac813d5fdbe8bb3e054a9f0b03f852c78a7b64ea1404298569cfebcf699a096405beeaa22147f522a3e4f4b30d27d3395282af9d5e56459005b2d09f41bb74530956760cd0271eef7ea002e670054fb0397470c5f34625428b3e1355cf12e96cab712af5e3bcc2cc706fd486b2ea921d80af0d094b0f6af0255d76ec1a0774fa0f6e65cbff319a5911ec899079e07dae2569b51d65ad09d610b8833d6b23081b109ad85282c008526bbd3d5c6225bf3866d5d0cedf3edaaa44ded4e318d07e050e22568b59402500d8a542ce90a2113d563e90e10998ccb9517da0f89dec82e2164f079f1b6f3b6176e738ae3620cf35013ad8edd9caf1e365e0af705b152db70898f1be05917c9a912cfccee3de5e667e55bd4bc452fb8ecb87884b6cde738e12a77f2e3c7e1ede76880b1aa34a9ad4ac5e6ecff1ee6bd18784ace50860aab1a6b17672d8e3a146444c95b542b4f1109bf6e92a8b70de3842a172b439ee2658a8d35c9ed07162b21af3438c2efa670f08f0d5af33ed6e4b64d7970ca2c16d820cf76baa8951d1bfe0a6443688c0a31b435711928c48095f078437a39fccea10cf1c871dcce78323a6563ca5ccea8e441f540fc4e5659a0e6f77501efa6c1cd64cc381fc512d7ccebc4a4543168ee8b154792b250f618a0b3f04a62c72962a1ee4cad38aff8a33a3fef1d0f2bcd58e7056a0977446e836aa4a857b7c3eeb6a4d7ef30132db99cc3707134100c2b1383d1bfef218166abbba496a961e91f12c4eb98e25f259f5f9753a1caab56a49930ef3b68f4d13a99045724a91cf653b8986ca331861242a7cb90ed8a112243297997f78542e59cd8c5441e02bcf27ff802de52a166d4165c6468ad65e60c93c64ee1a8c0e5e1f1189c8ebea7f94c22dd002513c22508b9e66eb2816762e420f92732c50e1232053d4fc25fd15c3f47bf1a0777a8b392122a510484721803f9cbf6d707a1ca1771d632d79174022842a2cd6cbaf71928648e54d793f984d3f55619b9ca507ea3c326c344eed1900014406e98de675098ad2b488e10f0c4b3cd7972983b2e8860d7ed6fd63f3a40160dacd43111ae325e4e92c1284ab9bd23001038a3066c936404b1fa3a6804dc1b4a058d4ee985d0c0b6d10572c93849206cdaffdb9f979faf07194306c9fa254f45084d4559a5859ca555161d812c44909af881d7e420b6ff918e6c17ac3e1bf1f3761b72a54efe906435432fb599cab9e588ba6058ced12993a7a19b2c0dd94e01865f1c2414e216c82d8c7d68470d4e78779ce2f5dd0ac4cb7524718468eda89c1fdbe1fd144d4c5c34249075f68dc5a5c709ea81b84b5f845844efdda1ef191723535ce9644a71aec9e3c0ddb4c3cf6ec2b3370bb5e34a2704c7c9bf74cc2d84705e4280388cfade633a5885beab5fea84bf41ef6ca161522b403b6395d249a8c85a8c059a2542d2347bb5c2dd359830163455ed81506f6382147c26a77dadaf477dc7907a479310f9c387017d52c8970eddd4cd19e8213ed892e3a8918a4159ed993089a8c0c4f54742d18d3b208b449ae0145f8287ca0aa78a5bebce79b2c69a6bb2bdb6536e74beebf48c150fb227ae93470bab594d35e964f13c6449977610aff90a97b94f035358ea058c23981caf5ab60e95c18255be5c58b8d453cd55a3e12a34d478308ea298d7dd952f16986bb62d8fcdcb30fd4df44d56483a4cd8721ba833e319bc08f934")
253+
]
254+
where
255+
x = B16.decodeLenient
256+
rep n c = B.replicate n c
257+
258+
hkdfTests :: [TestTree]
259+
hkdfTests = zipWith makeTest [1::Int ..] hkdfVectors
260+
where
261+
makeTest i (l,ikm,salt,info,okm) = testGroup ("vec"++show i) $
262+
[ testCase "hkdf" (hex okm @=? hex (IUT.hkdf ikm salt info l)) ]
263+
264+
hex = B16.encode
265+
239266
main :: IO ()
240267
main = defaultMain $ testGroup "cryptohash-sha384"
241268
[ testGroup "KATs" katTests
242269
, testGroup "RFC4231" rfc4231Tests
243270
, testGroup "REF" refImplTests
271+
, testGroup "HKDF" hkdfTests
244272
]

src-tests/test-sha512.hs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,9 +236,37 @@ refImplTests =
236236
toStrict = B.concat . BL.toChunks
237237
fromStrict = BL.fromChunks . (:[])
238238

239+
-- generated via `openssl kdf -kdfopt digest:SHA2-512`
240+
hkdfVectors :: [(Int,ByteString,ByteString,ByteString,ByteString)]
241+
hkdfVectors = -- (l,ikm,salt,info,okm)
242+
[ (42, rep 22 0x0b, x"000102030405060708090a0b0c", x"f0f1f2f3f4f5f6f7f8f9", x"832390086cda71fb47625bb5ceb168e4c8e26a1a16ed34d9fc7fe92c1481579338da362cb8d9f925d7cb")
243+
, ( 82
244+
, x"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f"
245+
, x"606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeaf"
246+
, x"b0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"
247+
, x"ce6c97192805b346e6161e821ed165673b84f400a2b514b2fe23d84cd189ddf1b695b48cbd1c8388441137b3ce28f16aa64ba33ba466b24df6cfcb021ecff235f6a2056ce3af1de44d572097a8505d9e7a93"
248+
)
249+
, ( 42, rep 22 0x0b, "", "", x"f5fa02b18298a72a8c23898a8703472c6eb179dc204c03425c970e3b164bf90fff22d04836d0e2343bac")
250+
, ( 42, rep 11 0x0b, x"000102030405060708090a0b0c", x"f0f1f2f3f4f5f6f7f8f9", x"7413e8997e020610fbf6823f2ce14bff01875db1ca55f68cfcf3954dc8aff53559bd5e3028b080f7c068" )
251+
, ( 42, rep 22 0x0c, "", "", x"1407d46013d98bc6decefcfee55f0f90b0c7f63d68eb1a80eaf07e953cfc0a3a5240a155d6e4daa965bb" )
252+
, ( 1317, rep 17 0x33, "HeLoLoL", "WoRlDoD", x"e363393c57e4ab1a475bd8750ee08d96b024cd8252c06ae9d3a338e0f68f09d6e138777ea7039f6924f76652b93966c35486afe804aa0f210139aa88189ca930f21f27f12bdcc73f95a352ae6c158ac729cc09b32c68fb8322fc5cafa378b0e123dfe8be44fdf242384e61a42e59c13646a2ef5e4931eef2ed8cd38da6f439cc3b6e1ea541aae2b9c59de7a194e9e7662ee156c67ad93f2c04313a6a431f784b12e8feafcc4cd4d98797e9d753b666a4c4f9c4981ae70255e5e3adcdfd8aaf08faae09a601a144af880cf98948be6dfe90114f6615cdc1edc4446451daaa1829b881e7f8968db279a03542a9729b8c08020320fb46c86c3dba9fdbb80767d487b911b52012deaa10dce7079afaad6f76a188945f99d7ba53cbaace919b9ce63cff2fd50e8ade7f5eb452ac2ea70be546784c4af03019519e971d3a2f6317f73c86d0b0c6cc6fe32163bdb34178a2c1f10d2d892bc06854f08e4bbca66ae7b57e915c0b57a90e2b624399f973d83675ccddee069e5ee150c6bd2a415d8a327d6fd7d3dfa49ec407d705fd5ac018d5285d2da8fdcba7494496a5a5ef753169bf4eae9dd5491ca11d1c7eb4255d404fc0dfa34e4605ab1b6495676aad0701e96c677ef4e4a87d661775ccaf7aaed60835219a391280ec786b08380f59780a744faa7313ac06f2ed0ef292c5b43ae7824e8ff8601957d570562feaadb011bb6a809bbc3ae3379f713579ffbe8c0fc46205d831ee3db910eb011aea8d4fbe040c15596c63f273dd5f0e386632eb27506d1ec9d1c5ec516c63406b1e9326145888244629b1dcdf877777532c05369dd42eff3871995d278b73123086625d3bf029cc3297aab6646d340a99da3008016d58c753504fd5c53c58ae2aa4d8ba6eae53f0b0ec5d3a3ece14f4e9286abbae628475f402f9a3b8f73d58aeb01ac43ee531784a8fd7c6b260f4ad69fdfe0bf641df8ab0d98be0df922f58b90840883621b26450c8e7c4b3fd0fbb09c3708270d26d976c96f41fc7ddf2c5470d0b39efa3e6c2762d2b388438f7520a4d5ca6ed80adc04fda438dbd542ff06335422d3055d885f04795e7369686b22f1181153920420b4a110ccd912af0ef6767b4c31ace6f39b6d203ac1694d65c6c6255ac5a6b2f82b9f700fffc3e37d416e919e6c509fa1b49186b6387dbd472b93cc38a8a8793aab9a1c4fa8960c989687f0266563db2f2ec083bea1ce1d479a5dde3a74587cb4f7e240f224129ff674f8b7d2ce6e079700300a655cb6aaa2e36d34c8d20f330a4fcb5b5a968b1fb3a1e189ca00c289fb7bc75ee9880c30dd807a4ff497f6156c03eb9771d9af1f767ca2395158f9eec133c585d554f6aa1f28fb1d78c0bbd764532c59c564a87abb5b6980c63351b32561a1eca0998f9378b9f39cc219c9b5c9cc3a09e1398ce019469a88c9b5ee77842e1ce458a11aef5d7002dd5ea6ca3f0f5fd533be56ee43bef39ec0a2ab1561570a5d4e2936b91e3c16b214b80271610f67d35edd877878f242b34fe34329355b09fd6f817c5cedef92c0198e62a15f1e526de0f66ed2afa0b388320008cafb62bfd8d0c447e8db920c526513ecbabcd806a1bda7e6b97d2f225a53b20e705c51e1c1bb646343a851fe0eadb89df13ac8a82c9c865d47a299d7800cbc50fb0296e0b44014e548c7a8d2ca2efde39e4794112194fd71a75431f1521719676d1b72f57ec00989052c3f62b7298d3d60fcd3345726b321f3b96e83a4d0a97851bc515b3c7f382dd5a2db0724546402a5c89b0f34202129358e53576124a5f4a7591a2eb5eb319a979d0701e16fde1357bcec651bb2f858fe1dcd6fa5eb5195f71152d1a460b5f36e9")
253+
]
254+
where
255+
x = B16.decodeLenient
256+
rep n c = B.replicate n c
257+
258+
hkdfTests :: [TestTree]
259+
hkdfTests = zipWith makeTest [1::Int ..] hkdfVectors
260+
where
261+
makeTest i (l,ikm,salt,info,okm) = testGroup ("vec"++show i) $
262+
[ testCase "hkdf" (hex okm @=? hex (IUT.hkdf ikm salt info l)) ]
263+
264+
hex = B16.encode
265+
239266
main :: IO ()
240267
main = defaultMain $ testGroup "cryptohash-sha512"
241268
[ testGroup "KATs" katTests
242269
, testGroup "RFC4231" rfc4231Tests
243270
, testGroup "REF" refImplTests
271+
, testGroup "HKDF" hkdfTests
244272
]

src/Crypto/Hash/SHA384.hs

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,12 @@ module Crypto.Hash.SHA384
8080
, hmac -- :: ByteString -> ByteString -> ByteString
8181
, hmaclazy -- :: ByteString -> L.ByteString -> ByteString
8282
, hmaclazyAndLength -- :: ByteString -> L.ByteString -> (ByteString,Word64)
83+
84+
-- ** HKDF-SHA-384
85+
--
86+
-- | <https://tools.ietf.org/html/rfc5869 RFC5869>-compatible
87+
-- <https://en.wikipedia.org/wiki/HKDF HKDF>-SHA-384 key derivation function
88+
, hkdf
8389
) where
8490

8591
import Prelude hiding (init)
@@ -91,7 +97,7 @@ import qualified Data.ByteString.Lazy as L
9197
import qualified Data.ByteString as B
9298
import Data.ByteString (ByteString)
9399
import Data.ByteString.Unsafe (unsafeUseAsCStringLen)
94-
import Data.ByteString.Internal (create, toForeignPtr, memcpy, mallocByteString)
100+
import Data.ByteString.Internal (create, createAndTrim, toForeignPtr, memcpy, mallocByteString)
95101
import Data.Bits (xor)
96102
import Data.Word
97103
import System.IO.Unsafe (unsafeDupablePerformIO)
@@ -290,3 +296,28 @@ hmaclazyAndLength secret msg =
290296
k' = B.append kt pad
291297
kt = if B.length secret > 128 then hash secret else secret
292298
pad = B.replicate (128 - B.length kt) 0
299+
300+
{-# NOINLINE hkdf #-}
301+
-- | <https://tools.ietf.org/html/rfc6234 RFC6234>-compatible
302+
-- HKDF-SHA-384 key derivation function.
303+
--
304+
-- @since 0.11.103.0
305+
hkdf :: ByteString -- ^ /IKM/ Input keying material
306+
-> ByteString -- ^ /salt/ Optional salt value, a non-secret random value (can be @""@)
307+
-> ByteString -- ^ /info/ Optional context and application specific information (can be @""@)
308+
-> Int -- ^ /L/ length of output keying material in octets (at most 255 * 'digestSize' bytes)
309+
-> ByteString -- ^ /OKM/ Output keying material (/L/ bytes)
310+
hkdf ikm salt info l
311+
| l == 0 = B.empty
312+
| 0 > l || l > 255*digestSize = error "hkdf: invalid L parameter"
313+
| otherwise = unsafeDoIO $ createAndTrim (digestSize*fromIntegral cnt) (go 0 B.empty)
314+
where
315+
prk = hmac salt ikm
316+
cnt = fromIntegral ((l+digestSize-1) `div` digestSize) :: Word8
317+
318+
go :: Word8 -> ByteString -> Ptr Word8 -> IO Int
319+
go i t p | i == cnt = return l
320+
| otherwise = do
321+
let t' = hmaclazy prk (L.fromChunks [t,info,B.singleton (i+1)])
322+
withByteStringPtr t' $ \tptr' -> memcpy p tptr' digestSize
323+
go (i+1) t' $! (p `plusPtr` digestSize)

src/Crypto/Hash/SHA512.hs

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,12 @@ module Crypto.Hash.SHA512
7979
, hmac -- :: ByteString -> ByteString -> ByteString
8080
, hmaclazy -- :: ByteString -> L.ByteString -> ByteString
8181
, hmaclazyAndLength -- :: ByteString -> L.ByteString -> (ByteString,Word64)
82+
83+
-- ** HKDF-SHA-512
84+
--
85+
-- | <https://tools.ietf.org/html/rfc5869 RFC5869>-compatible
86+
-- <https://en.wikipedia.org/wiki/HKDF HKDF>-SHA-512 key derivation function
87+
, hkdf
8288
) where
8389

8490
import Prelude hiding (init)
@@ -90,7 +96,7 @@ import qualified Data.ByteString.Lazy as L
9096
import qualified Data.ByteString as B
9197
import Data.ByteString (ByteString)
9298
import Data.ByteString.Unsafe (unsafeUseAsCStringLen)
93-
import Data.ByteString.Internal (create, toForeignPtr, memcpy, mallocByteString)
99+
import Data.ByteString.Internal (create, createAndTrim, toForeignPtr, memcpy, mallocByteString)
94100
import Data.Bits (xor)
95101
import Data.Word
96102
import System.IO.Unsafe (unsafeDupablePerformIO)
@@ -303,3 +309,28 @@ hmaclazyAndLength secret msg =
303309
k' = B.append kt pad
304310
kt = if B.length secret > 128 then hash secret else secret
305311
pad = B.replicate (128 - B.length kt) 0
312+
313+
{-# NOINLINE hkdf #-}
314+
-- | <https://tools.ietf.org/html/rfc6234 RFC6234>-compatible
315+
-- HKDF-SHA-512 key derivation function.
316+
--
317+
-- @since 0.11.103.0
318+
hkdf :: ByteString -- ^ /IKM/ Input keying material
319+
-> ByteString -- ^ /salt/ Optional salt value, a non-secret random value (can be @""@)
320+
-> ByteString -- ^ /info/ Optional context and application specific information (can be @""@)
321+
-> Int -- ^ /L/ length of output keying material in octets (at most 255 * 'digestSize' bytes)
322+
-> ByteString -- ^ /OKM/ Output keying material (/L/ bytes)
323+
hkdf ikm salt info l
324+
| l == 0 = B.empty
325+
| 0 > l || l > 255*digestSize = error "hkdf: invalid L parameter"
326+
| otherwise = unsafeDoIO $ createAndTrim (digestSize*fromIntegral cnt) (go 0 B.empty)
327+
where
328+
prk = hmac salt ikm
329+
cnt = fromIntegral ((l+digestSize-1) `div` digestSize) :: Word8
330+
331+
go :: Word8 -> ByteString -> Ptr Word8 -> IO Int
332+
go i t p | i == cnt = return l
333+
| otherwise = do
334+
let t' = hmaclazy prk (L.fromChunks [t,info,B.singleton (i+1)])
335+
withByteStringPtr t' $ \tptr' -> memcpy p tptr' digestSize
336+
go (i+1) t' $! (p `plusPtr` digestSize)

0 commit comments

Comments
 (0)