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 };