askar_crypto/kdf/
concat.rs

1//! ConcatKDF from NIST 800-56ar for ECDH-ES / ECDH-1PU
2
3use core::{fmt::Debug, marker::PhantomData};
4
5use digest::{Digest, FixedOutputReset};
6
7use crate::generic_array::{typenum::Unsigned, GenericArray};
8
9use crate::{buffer::WriteBuffer, error::Error};
10
11/// A struct providing the key derivation for a particular hash function
12#[derive(Clone, Copy, Debug)]
13pub struct ConcatKDF<H>(PhantomData<H>);
14
15/// Parameters for the key derivation
16#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
17pub struct ConcatKDFParams<'p> {
18    /// The algorithm name
19    pub alg: &'p [u8],
20    /// Sender identifier (PartyUInfo)
21    pub apu: &'p [u8],
22    /// Recipient identifier (PartyVInfo)
23    pub apv: &'p [u8],
24    /// SuppPubInfo as defined by the application
25    pub pub_info: &'p [u8],
26    /// SuppPrivInfo as defined by the application
27    pub prv_info: &'p [u8],
28}
29
30impl<H> ConcatKDF<H>
31where
32    H: Digest + FixedOutputReset,
33{
34    /// Perform the key derivation and write the result to the provided buffer
35    pub fn derive_key(
36        message: &[u8],
37        params: ConcatKDFParams<'_>,
38        mut output: &mut [u8],
39    ) -> Result<(), Error> {
40        let output_len = output.len();
41        if output_len > H::OutputSize::USIZE * (u32::MAX as usize) - 1 {
42            return Err(err_msg!(Usage, "Exceeded max output size for concat KDF"));
43        }
44        let mut hasher = ConcatKDFHash::<H>::new();
45        let mut remain = output_len;
46        while remain > 0 {
47            hasher.start_pass();
48            hasher.hash_message(message);
49            hasher.hash_params(params);
50            let hashed = hasher.finish_pass();
51            let cp_size = hashed.len().min(remain);
52            output[..cp_size].copy_from_slice(&hashed[..cp_size]);
53            output = &mut output[cp_size..];
54            remain -= cp_size;
55        }
56        Ok(())
57    }
58}
59
60/// Core hashing implementation of the multi-pass key derivation
61#[derive(Debug)]
62pub struct ConcatKDFHash<H: Digest> {
63    hasher: H,
64    counter: u32,
65}
66
67impl<H: Digest> ConcatKDFHash<H> {
68    /// Create a new instance
69    pub fn new() -> Self {
70        Self {
71            hasher: H::new(),
72            counter: 1,
73        }
74    }
75
76    /// Start a new pass of the key derivation
77    pub fn start_pass(&mut self) {
78        self.hasher.update(self.counter.to_be_bytes());
79        self.counter += 1;
80    }
81
82    /// Hash input to the key derivation
83    pub fn hash_message(&mut self, data: &[u8]) {
84        self.hasher.update(data);
85    }
86
87    /// Hash the parameters of the key derivation
88    pub fn hash_params(&mut self, params: ConcatKDFParams<'_>) {
89        let hash = &mut self.hasher;
90        hash.update((params.alg.len() as u32).to_be_bytes());
91        hash.update(params.alg);
92        hash.update((params.apu.len() as u32).to_be_bytes());
93        hash.update(params.apu);
94        hash.update((params.apv.len() as u32).to_be_bytes());
95        hash.update(params.apv);
96        hash.update(params.pub_info);
97        hash.update(params.prv_info);
98    }
99
100    /// Complete this pass of the key derivation, returning the result
101    pub fn finish_pass(&mut self) -> GenericArray<u8, H::OutputSize>
102    where
103        H: FixedOutputReset,
104    {
105        self.hasher.finalize_reset()
106    }
107}
108
109impl<H: Digest> Default for ConcatKDFHash<H> {
110    fn default() -> Self {
111        Self::new()
112    }
113}
114
115impl<D: Debug + Digest> WriteBuffer for ConcatKDFHash<D> {
116    fn buffer_write(&mut self, data: &[u8]) -> Result<(), Error> {
117        self.hasher.update(data);
118        Ok(())
119    }
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125    use sha2::Sha256;
126
127    #[test]
128    // testing with ConcatKDF - single pass via ConcatKDFHash is tested elsewhere
129    fn expected_1pu_output() {
130        let z = hex!(
131            "9e56d91d817135d372834283bf84269cfb316ea3da806a48f6daa7798cfe90c4
132            e3ca3474384c9f62b30bfd4c688b3e7d4110a1b4badc3cc54ef7b81241efd50d"
133        );
134        let mut output = [0u8; 32];
135        ConcatKDF::<Sha256>::derive_key(
136            &z,
137            ConcatKDFParams {
138                alg: b"A256GCM",
139                apu: b"Alice",
140                apv: b"Bob",
141                pub_info: &(256u32).to_be_bytes(),
142                prv_info: &[],
143            },
144            &mut output,
145        )
146        .unwrap();
147        assert_eq!(
148            output,
149            hex!("6caf13723d14850ad4b42cd6dde935bffd2fff00a9ba70de05c203a5e1722ca7")
150        );
151    }
152}