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