hare

[hare] The Hare programming language
git clone https://git.torresjrjr.com/hare.git
Log | Files | Refs | README | LICENSE

hkdf.ha (4165B)


      1 // SPDX-License-Identifier: MPL-2.0
      2 // (c) Hare authors <https://harelang.org>
      3 
      4 use bytes;
      5 use crypto::hmac;
      6 use crypto::mac;
      7 use hash;
      8 
      9 // Calls [[extract]] and [[expand]] to derive a single key from specified 'key'
     10 // material using HMAC with 'h' as underlying hash function and writes it to
     11 // 'dest'. The resulting key size is of the size of 'dest'.
     12 //
     13 // 'info' binds the resulting key to the context in where it is being used and
     14 // therefore prevents the derivation of the same key for different contexts. It
     15 // should be independent of the input key. 'salt' does not need to be secret and
     16 // it's recommended to use a random or pseudo random value, ideally of the hash
     17 // size of the given hash function. The 'salt' must be a fixed value or void
     18 // between many different contexts.
     19 //
     20 // 'buf' must be of the size [[hash::bsz]] + [[hash::sz]] of given hash 'h'.
     21 export fn hkdf(
     22 	h: *hash::hash,
     23 	dest: []u8,
     24 	key: []u8,
     25 	info: []u8,
     26 	salt: ([]u8 | void),
     27 	buf: []u8,
     28 ) void = {
     29 	const hashsz = hash::sz(h);
     30 	assert(len(buf) >= (hash::sz(h) + hash::bsz(h)),
     31 		"len(buf) must be at least `hash::sz(h) + hash::bsz(h)`");
     32 
     33 	let prk = buf[..hashsz];
     34 	let buf = buf[hashsz..];
     35 
     36 	extract(h, prk, key, salt, buf);
     37 	// use prk as buffer for the last block operation since it's not
     38 	// required for future expanding.
     39 	iexpand(h, dest, prk, info, buf, prk);
     40 
     41 	// buf is zeroed in expand
     42 };
     43 
     44 // Extracts a pseudo random key (prk) from given 'key' and 'salt' and writes it
     45 // to 'prk' using HMAC of given hash function 'h'. 'prk' must be the size of
     46 // [[hash::sz]] of given hash. The resulting 'prk' can be used to derive keys
     47 // using the [[expand]] function.
     48 //
     49 // 'salt' does not need to be secret and it's recommended to use a random or
     50 // pseudo random value, ideally of the hash size of the given hash function.
     51 // 'buf' must be of the size [[hash::bsz]] of given hash.
     52 export fn extract(
     53 	h: *hash::hash,
     54 	prk: []u8,
     55 	key: []u8,
     56 	salt: ([]u8 | void),
     57 	buf: []u8
     58 ) void = {
     59 	const hashsz = hash::sz(h);
     60 	assert(len(buf) >= hash::bsz(h),
     61 		"len(buf) must be at least `hash::bsz(h)`");
     62 	assert(len(prk) == hash::sz(h), "prk must be of hash::sz(h)");
     63 
     64 	let prkkey = match(salt) {
     65 	case let s: []u8 =>
     66 		yield s;
     67 	case void =>
     68 		bytes::zero(prk);
     69 		yield prk;
     70 	};
     71 
     72 	let hm = hmac::hmac(h, prkkey, buf);
     73 	mac::write(&hm, key);
     74 	mac::sum(&hm, prk);
     75 	mac::finish(&hm);
     76 
     77 	// buf is zeroed in mac::finish
     78 };
     79 
     80 // Derives a new key form 'prk' using HMAC of given hash function 'h' and stores
     81 // it into 'dest'. 'prk' must be created using [[extract]]. 'info' binds the
     82 // resulting key to the context in where it is being used and therefore prevents
     83 // the derivation of the same key for different contexts. 'buf' must be at least
     84 // of the size [[hash::sz]] + [[hash::bsz]] of given hash function 'h'. The same
     85 // hash function that was used at the [[extract]] step must also be used in
     86 // [[expand]].
     87 export fn expand(
     88 	h: *hash::hash,
     89 	dest: []u8,
     90 	prk: []u8,
     91 	info: []u8,
     92 	buf: []u8,
     93 ) void = {
     94 	assert(len(buf) >= (hash::bsz(h) + hash::sz(h)),
     95 		"len(buf) must be at least `hash::bsz(h)`");
     96 
     97 	const hashsz = hash::sz(h);
     98 	iexpand(h, dest, prk, info, buf[hashsz..], buf[..hashsz]);
     99 };
    100 
    101 // Internal [[expand]] function that allows to specify separately 'hashbuf',
    102 // the buffer for the last block operation.
    103 fn iexpand(
    104 	h: *hash::hash,
    105 	dest: []u8,
    106 	prk: []u8,
    107 	info: []u8,
    108 	buf: []u8,
    109 	hashbuf: []u8,
    110 ) void = {
    111 	const hashsz = hash::sz(h);
    112 
    113 	// to avoid ctr overflows
    114 	assert((len(dest) + hashsz - 1) / hashsz < 255,
    115 		"'dest' exceeds maximum allowed size");
    116 
    117 	let ctr: [1]u8 = [0];
    118 	let preblock: []u8 = [];
    119 
    120 	defer bytes::zero(hashbuf);
    121 	// buf is zeroed during mac::finish
    122 
    123 	for (let i = 0u8; len(dest) > 0; i += 1) {
    124 		hash::reset(h);
    125 		let hm = hmac::hmac(h, prk, buf);
    126 		defer mac::finish(&hm);
    127 
    128 		if (i > 0) {
    129 			mac::write(&hm, preblock);
    130 		};
    131 		mac::write(&hm, info);
    132 
    133 		ctr[0] = i + 1;
    134 		mac::write(&hm, ctr);
    135 
    136 		const n = if (len(dest) >= hashsz) {
    137 			mac::sum(&hm, dest[..hashsz]);
    138 			preblock = dest[..hashsz];
    139 			yield hashsz;
    140 		} else {
    141 			mac::sum(&hm, hashbuf);
    142 			dest[..] = hashbuf[..len(dest)];
    143 			yield len(dest);
    144 		};
    145 
    146 		dest = dest[n..];
    147 	};
    148 };