commit 8b5284b61a18c51f620abf57acdf6af2ac312d0f
parent 0919412be13703235c7deabdb6216254e4a39432
Author: Armin Preiml <apreiml@strohwolke.at>
Date: Sun, 16 Jul 2023 15:52:29 +0200
hkdf: add extract and expand functions
HKDF is designed to derive multiple keys from one secret for different
contexts. This commit introduces the extract and expand functions
as defined in the RFC in addition to the hkdf function that only allows
the derivation of just one key.
Diffstat:
3 files changed, 85 insertions(+), 18 deletions(-)
diff --git a/crypto/hkdf/+test.ha b/crypto/hkdf/+test.ha
@@ -1,5 +1,5 @@
// License: MPL-2.0
-// (c) 2022 Armin Preiml <apreiml@strohwolke.at>
+// (c) 2023 Armin Preiml <apreiml@strohwolke.at>
use bytes;
use crypto::sha1;
use crypto::sha256;
diff --git a/crypto/hkdf/README b/crypto/hkdf/README
@@ -1 +1,6 @@
-This module provides a HKDF implementation according to RFC 5869.
+This module provides a HKDF implementation according to RFC 5869. A pesudorandom
+key is created using [[extract]], which in turn is used to derive keys using
+[[expand]]. [[hkdf]] combines both [[extract]] and [[expand]] for deriving
+a single key.
+
+See the RFC 5869 for detailed usage guidance on how to choose the parameters.
diff --git a/crypto/hkdf/hkdf.ha b/crypto/hkdf/hkdf.ha
@@ -1,13 +1,13 @@
// License: MPL-2.0
-// (c) 2022 Armin Preiml <apreiml@strohwolke.at>
+// (c) 2023 Armin Preiml <apreiml@strohwolke.at>
use bytes;
use crypto::hmac;
use crypto::mac;
use hash;
-// Derives a new key from specified 'key' material using HMAC with 'h' as
-// underlying hash function and writes it to 'dest'. The resulting key size is
-// of the size of 'dest'.
+// Calls [[extract]] and [[expand]] to derive a single key from specified 'key'
+// material using HMAC with 'h' as underlying hash function and writes it to
+// 'dest'. The resulting key size is of the size of 'dest'.
//
// 'info' binds the resulting key to the context in where it is being used and
// therefore prevents the derivation of the same key for different contexts. It
@@ -17,8 +17,6 @@ use hash;
// between many different contexts.
//
// 'buf' must be of the size [[hash::bsz]] + [[hash::sz]] of given hash 'h'.
-//
-// See the RFC 5869 for detailed usage guidance.
export fn hkdf(
h: *hash::hash,
dest: []u8,
@@ -34,27 +32,96 @@ export fn hkdf(
let prk = buf[..hashsz];
let buf = buf[hashsz..];
+ extract(h, prk, key, salt, buf);
+ // use prk as buffer for the last block operation since it's not
+ // required for future expanding.
+ iexpand(h, dest, prk, info, buf, prk);
+
+ // buf is zeroed in expand
+};
+
+// Extracts a pseudo random key (prk) from given 'key' and 'salt' and writes it
+// to 'prk' using HMAC of given hash function 'h'. 'prk' must be the size of
+// [[hash::sz]] of given hash. The resulting 'prk' can be used to derive keys
+// using the [[expand]] function.
+//
+// 'salt' does not need to be secret and it's recommended to use a random or
+// pseudo random value, ideally of the hash size of the given hash function.
+// 'buf' must be of the size [[hash::bsz]] of given hash.
+export fn extract(
+ h: *hash::hash,
+ prk: []u8,
+ key: []u8,
+ salt: ([]u8 | void),
+ buf: []u8
+) void = {
+ const hashsz = hash::sz(h);
+ assert(len(buf) >= hash::bsz(h),
+ "len(buf) must be at least `hash::bsz(h)`");
+ assert(len(prk) == hash::sz(h), "prk must be of hash::sz(h)");
+
let prkkey = match(salt) {
case let s: []u8 =>
yield s;
case void =>
- // use prk for an hashsz full of zeros
bytes::zero(prk);
yield prk;
};
let hm = hmac::hmac(h, prkkey, buf);
-
mac::write(&hm, key);
mac::sum(&hm, prk);
mac::finish(&hm);
+ // buf is zeroed in mac::finish
+};
+
+// Derives a new key form 'prk' using HMAC of given hash function 'h' and stores
+// it into 'dest'. 'prk' must be created using [[extract]]. 'info' binds the
+// resulting key to the context in where it is being used and therefore prevents
+// the derivation of the same key for different contexts. 'buf' must be at least
+// of the size [[hash::sz]] + [[hash::bsz]] of given hash function 'h'. The same
+// hash function that was used at the [[extract]] step must also be used in
+// [[expand]].
+export fn expand(
+ h: *hash::hash,
+ dest: []u8,
+ prk: []u8,
+ info: []u8,
+ buf: []u8,
+) void = {
+ assert(len(buf) >= (hash::bsz(h) + hash::sz(h)),
+ "len(buf) must be at least `hash::bsz(h)`");
+
+ const hashsz = hash::sz(h);
+ iexpand(h, dest, prk, info, buf[hashsz..], buf[..hashsz]);
+};
+
+// Internal [[expand]] function that allows to specify separately 'hashbuf',
+// the buffer for the last block operation.
+fn iexpand(
+ h: *hash::hash,
+ dest: []u8,
+ prk: []u8,
+ info: []u8,
+ buf: []u8,
+ hashbuf: []u8,
+) void = {
+ const hashsz = hash::sz(h);
+
+ // to avoid ctr overflows
+ assert((len(dest) + hashsz - 1) / hashsz < 255,
+ "'dest' exceeds maximum allowed size");
+
let ctr: [1]u8 = [0];
let preblock: []u8 = [];
+ defer bytes::zero(hashbuf);
+ // buf is zeroed during mac::finish
+
for (let i = 0u8; len(dest) > 0; i += 1) {
hash::reset(h);
- hm = hmac::hmac(h, prk, buf);
+ let hm = hmac::hmac(h, prk, buf);
defer mac::finish(&hm);
if (i > 0) {
@@ -70,16 +137,11 @@ export fn hkdf(
preblock = dest[..hashsz];
yield hashsz;
} else {
- // use the prk as buffer for the last block as it's
- // not needed anymore afterwards.
- mac::sum(&hm, prk);
- dest[..] = prk[..len(dest)];
+ mac::sum(&hm, hashbuf);
+ dest[..] = hashbuf[..len(dest)];
yield len(dest);
};
dest = dest[n..];
};
-
- bytes::zero(prk);
- bytes::zero(buf);
};