askar_crypto/kdf/
concat.rs1use 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#[derive(Clone, Copy, Debug)]
13pub struct ConcatKDF<H>(PhantomData<H>);
14
15#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
17pub struct ConcatKDFParams<'p> {
18 pub alg: &'p [u8],
20 pub apu: &'p [u8],
22 pub apv: &'p [u8],
24 pub pub_info: &'p [u8],
26 pub prv_info: &'p [u8],
28}
29
30impl<H> ConcatKDF<H>
31where
32 H: Digest + FixedOutputReset,
33{
34 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#[derive(Debug)]
62pub struct ConcatKDFHash<H: Digest> {
63 hasher: H,
64 counter: u32,
65}
66
67impl<H: Digest> ConcatKDFHash<H> {
68 pub fn new() -> Self {
70 Self {
71 hasher: H::new(),
72 counter: 1,
73 }
74 }
75
76 pub fn start_pass(&mut self) {
78 self.hasher.update(self.counter.to_be_bytes());
79 self.counter += 1;
80 }
81
82 pub fn hash_message(&mut self, data: &[u8]) {
84 self.hasher.update(data);
85 }
86
87 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 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 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}