1use ic_core::traits::Mac;
4use ic_core::{ensure, Result, Zeroize};
5
6const MAX_TAG_LEN: usize = 64;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum IterationVerdict {
12 Recommended,
14 Weak,
16 Unacceptable,
18}
19
20pub const fn check_iterations(iterations: u32) -> IterationVerdict {
26 if iterations < 1_000 {
27 IterationVerdict::Unacceptable
28 } else if iterations < crate::PBKDF2_MIN_RECOMMENDED_ITERATIONS {
29 IterationVerdict::Weak
30 } else {
31 IterationVerdict::Recommended
32 }
33}
34
35pub fn pbkdf2<M: Mac>(password: &[u8], salt: &[u8], iterations: u32, out: &mut [u8]) -> Result<()> {
41 ensure!(
42 !matches!(check_iterations(iterations), IterationVerdict::Unacceptable),
43 InvalidParameter,
44 "pbkdf2 iterations below the SP 800-132 minimum of 1000"
45 );
46 ensure!(
47 salt.len() >= 16,
48 InvalidParameter,
49 "pbkdf2 salt must be >= 128 bits"
50 );
51 ensure!(!out.is_empty(), InvalidLength, "pbkdf2 output");
52 ensure!(
53 M::TAG_LEN <= MAX_TAG_LEN,
54 InvalidParameter,
55 "mac tag too wide"
56 );
57
58 let mut u = [0u8; MAX_TAG_LEN];
59 let mut acc = [0u8; MAX_TAG_LEN];
60
61 for (block_index, chunk) in out.chunks_mut(M::TAG_LEN).enumerate() {
62 let counter = (block_index as u32)
63 .checked_add(1)
64 .ok_or(ic_core::err!(CounterExhausted, "pbkdf2 block counter"))?;
65
66 let mut m = M::new(password)?;
68 m.update(salt);
69 m.update(&counter.to_be_bytes());
70 let t = m.finalize();
71 u[..M::TAG_LEN].copy_from_slice(t.as_ref());
72 acc[..M::TAG_LEN].copy_from_slice(t.as_ref());
73
74 for _ in 1..iterations {
76 let mut m = M::new(password)?;
77 m.update(&u[..M::TAG_LEN]);
78 let t = m.finalize();
79 u[..M::TAG_LEN].copy_from_slice(t.as_ref());
80 for j in 0..M::TAG_LEN {
81 acc[j] ^= u[j];
82 }
83 }
84 chunk.copy_from_slice(&acc[..chunk.len()]);
85 }
86
87 u.zeroize();
88 acc.zeroize();
89 Ok(())
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95 use ic_core::codec::hex;
96 use ic_mac::{HmacSha256, HmacSha512};
97
98 #[test]
105 fn matches_the_prf_xor_chain() {
106 let salt = b"0123456789abcdef";
107 let mut out = [0u8; 32];
108 pbkdf2::<HmacSha256>(b"pw", salt, 1_000, &mut out).unwrap();
109
110 let mut first = HmacSha256::new(b"pw").unwrap();
111 first.update(salt);
112 first.update(&1u32.to_be_bytes());
113 let mut u = first.finalize();
114 let mut acc = u;
115 for _ in 1..1_000 {
116 u = HmacSha256::mac(b"pw", u.as_ref()).unwrap();
117 for j in 0..32 {
118 acc[j] ^= u[j];
119 }
120 }
121 assert_eq!(hex(&out), hex(acc.as_ref()));
122 }
123
124 #[test]
125 fn is_deterministic() {
126 let mut a = [0u8; 32];
127 let mut b = [0u8; 32];
128 pbkdf2::<HmacSha256>(b"passwd", b"salt-at-least-16", 1_000, &mut a).unwrap();
129 pbkdf2::<HmacSha256>(b"passwd", b"salt-at-least-16", 1_000, &mut b).unwrap();
130 assert_eq!(a, b);
131 }
132
133 #[test]
134 fn iteration_count_changes_the_key() {
135 let mut a = [0u8; 32];
136 let mut b = [0u8; 32];
137 pbkdf2::<HmacSha256>(b"pw", b"0123456789abcdef", 1_000, &mut a).unwrap();
138 pbkdf2::<HmacSha256>(b"pw", b"0123456789abcdef", 2_000, &mut b).unwrap();
139 assert_ne!(a, b);
140 }
141
142 #[test]
143 fn output_longer_than_one_block() {
144 let mut out = [0u8; 100];
145 pbkdf2::<HmacSha256>(b"pw", b"0123456789abcdef", 1_000, &mut out).unwrap();
146 assert_ne!(&out[..32], &out[32..64]);
149 }
150
151 #[test]
152 fn sha512_instantiation_differs() {
153 let mut a = [0u8; 32];
154 let mut b = [0u8; 32];
155 pbkdf2::<HmacSha256>(b"pw", b"0123456789abcdef", 1_000, &mut a).unwrap();
156 pbkdf2::<HmacSha512>(b"pw", b"0123456789abcdef", 1_000, &mut b).unwrap();
157 assert_ne!(a, b);
158 }
159
160 #[test]
161 fn rejects_weak_parameters() {
162 let mut out = [0u8; 32];
163 assert!(pbkdf2::<HmacSha256>(b"pw", b"0123456789abcdef", 999, &mut out).is_err());
164 assert!(pbkdf2::<HmacSha256>(b"pw", b"short", 100_000, &mut out).is_err());
165 assert!(pbkdf2::<HmacSha256>(b"pw", b"0123456789abcdef", 1_000, &mut []).is_err());
166 }
167
168 #[test]
169 fn iteration_verdicts() {
170 assert_eq!(check_iterations(999), IterationVerdict::Unacceptable);
171 assert_eq!(check_iterations(1_000), IterationVerdict::Weak);
172 assert_eq!(check_iterations(600_000), IterationVerdict::Recommended);
173 }
174}