fscrypt/
hkdf.rs

1// Copyright 2025 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4use hmac::Mac;
5
6// Fscrypt tacks a prefix onto the 'info' field in HKDF used for different purposes.
7// This prefix is built from one of the following context.
8pub const HKDF_CONTEXT_KEY_IDENTIFIER: u8 = 1;
9pub const HKDF_CONTEXT_PER_FILE_ENC_KEY: u8 = 2;
10pub const HKDF_CONTEXT_DIRHASH_KEY: u8 = 5;
11pub const HKDF_CONTEXT_IV_INO_LBLK_32_KEY: u8 = 6;
12pub const HKDF_CONTEXT_INODE_HASH_KEY: u8 = 7;
13
14/// An fscrypt compatible implementation of HKDF (HKDF-extract + HKDF-expand)
15/// This is just regular HKDF but with 'info' prefixed.
16/// `context` is an fscrypt special.
17pub fn fscrypt_hkdf<const L: usize>(
18    initial_key_material: &[u8],
19    info: &[u8],
20    context: u8,
21) -> [u8; L] {
22    let mut out = [0u8; L];
23    let mut fscrypt_info = Vec::with_capacity(9 + info.len());
24    fscrypt_info.extend_from_slice(b"fscrypt\0");
25    fscrypt_info.push(context);
26    debug_assert_eq!(fscrypt_info.len(), 9);
27    fscrypt_info.extend_from_slice(info);
28    hkdf::<L>(initial_key_material, &fscrypt_info, &mut out);
29    out
30}
31
32/// Standard HKDF implementation. See https://datatracker.ietf.org/doc/html/rfc5869
33/// Note that we assume an all-zero seed for PRK.
34/// `initial_key_material` is the data being hashed.
35/// `info` is optional context (can be zero length string)
36/// `out` is populated with the result.
37pub fn hkdf<const L: usize>(initial_key_material: &[u8], info: &[u8], out: &mut [u8; L]) {
38    const HASH_LEN: usize = 64;
39    // HKDF-extract
40    let mut hmac = hmac::Hmac::<sha2::Sha512>::new_from_slice(&[0; HASH_LEN]).unwrap();
41    hmac.update(initial_key_material);
42    let prk = hmac.finalize().into_bytes();
43    // HKDF-expand
44    let mut last = [].as_slice();
45    let mut out = out.as_mut_slice();
46    let mut i = 1;
47    loop {
48        let mut hmac = hmac::Hmac::<sha2::Sha512>::new_from_slice(&prk).unwrap();
49        hmac.update(&last);
50        hmac.update(&info);
51        hmac.update(&[i as u8]);
52        let val = hmac.finalize().into_bytes();
53        if out.len() < HASH_LEN {
54            out.copy_from_slice(&val.as_slice()[..out.len()]);
55            break;
56        }
57        out[..HASH_LEN].copy_from_slice(&val.as_slice());
58        (last, out) = out.split_at_mut(HASH_LEN);
59        i += 1;
60    }
61}