v / vlib / crypto / hkdf / hkdf.v
115 lines · 109 sloc · 3.64 KB · 7337193ae08442bb500f556ae388d428da552876
Raw
1// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved.
2// Use of this source code is governed by an MIT license
3// that can be found in the LICENSE file.
4// Package hkdf implements the HMAC-based Extract-and-Expand Key Derivation
5// Function (HKDF) as defined in RFC 5869.
6//
7// HKDF is a cryptographic key derivation function (KDF) with the goal of
8// expanding limited input keying material into one or more cryptographically
9// strong secret keys.
10// Based off: https://github.com/golang/go/tree/master/src/crypto/hkdf
11module hkdf
12
13// extract generates a pseudorandom key for use with `expand` from an input
14// secret and an optional independent salt.
15//
16// Only use this function if you need to reuse the extracted key with multiple
17// `expand` invocations and different context values. Most common scenarios,
18// including the generation of multiple keys, should use `key` instead.
19pub fn extract[H](h fn () H, secret []u8, salt []u8) ![]u8 {
20 check_fips140_only(h, secret)!
21 mut hash := h()
22 mut xkey := salt.clone()
23 if xkey.len == 0 {
24 xkey = []u8{len: hash.size()}
25 }
26 return hmac_sum(h, xkey, secret)
27}
28
29// expand derives a key from the given hash, key, and optional context info,
30// returning a []u8 of length key_length that can be used as cryptographic key.
31// The extraction step is skipped.
32//
33// The key should have been generated by `extract`, or be a uniformly
34// random or pseudorandom cryptographically strong key. See RFC 5869, Section
35// 3.3. Most common scenarios will want to use `key` instead.
36pub fn expand[H](h fn () H, pseudorandom_key []u8, info string, key_length int) ![]u8 {
37 check_fips140_only(h, pseudorandom_key)!
38 mut hash := h()
39 if key_length < 0 {
40 return error('hkdf: requested key length must be non-negative')
41 }
42 limit := hash.size() * 255
43 if key_length > limit {
44 return error('hkdf: requested key length too large')
45 }
46 mut out := []u8{cap: key_length}
47 mut counter := u8(0)
48 mut buf := []u8{}
49 info_bytes := info.bytes()
50 for out.len < key_length {
51 counter++
52 if counter == 0 {
53 panic('hkdf: counter overflow')
54 }
55 mut data := []u8{cap: buf.len + info_bytes.len + 1}
56 data << buf
57 data << info_bytes
58 data << counter
59 buf = hmac_sum(h, pseudorandom_key, data)
60 remaining := key_length - out.len
61 if remaining < buf.len {
62 out << buf[..remaining]
63 } else {
64 out << buf
65 }
66 }
67 return out
68}
69
70// key derives a key from the given hash, secret, salt and context info,
71// returning a []u8 of length key_length that can be used as cryptographic key.
72// Salt and info can be empty.
73pub fn key[H](h fn () H, secret []u8, salt []u8, info string, key_length int) ![]u8 {
74 check_fips140_only(h, secret)!
75 if key_length < 0 {
76 return error('hkdf: requested key length must be non-negative')
77 }
78 mut hash := h()
79 limit := hash.size() * 255
80 if key_length > limit {
81 return error('hkdf: requested key length too large')
82 }
83 prk := extract(h, secret, salt)!
84 return expand(h, prk, info, key_length)
85}
86
87fn check_fips140_only[H](_h fn () H, _key []u8) ! {
88 // V does not currently have a FIPS 140-only mode.
89 return
90}
91
92fn hmac_sum[H](h fn () H, key []u8, data []u8) []u8 {
93 mut hash := h()
94 block_size := hash.block_size()
95 mut b_key := if key.len <= block_size { key.clone() } else { hash_sum(h, key) }
96 if b_key.len > block_size {
97 b_key = b_key[..block_size].clone()
98 }
99 mut inner := []u8{len: block_size, init: 0x36}
100 mut outer := []u8{len: block_size, init: 0x5c}
101 for i, b in b_key {
102 inner[i] = b ^ 0x36
103 outer[i] = b ^ 0x5c
104 }
105 inner << data
106 inner_hash := hash_sum(h, inner)
107 outer << inner_hash
108 return hash_sum(h, outer)
109}
110
111fn hash_sum[H](h fn () H, data []u8) []u8 {
112 mut hash := h()
113 hash.write(data) or { panic(err) }
114 return hash.sum([]u8{})
115}
116