Line data Source code
1 : use hmac::{
2 : digest::{consts::U32, generic_array::GenericArray},
3 : Hmac, Mac,
4 : };
5 : use sha2::Sha256;
6 :
7 : pub(crate) struct Pbkdf2 {
8 : hmac: Hmac<Sha256>,
9 : prev: GenericArray<u8, U32>,
10 : hi: GenericArray<u8, U32>,
11 : iterations: u32,
12 : }
13 :
14 : // inspired from <https://github.com/neondatabase/rust-postgres/blob/20031d7a9ee1addeae6e0968e3899ae6bf01cee2/postgres-protocol/src/authentication/sasl.rs#L36-L61>
15 : impl Pbkdf2 {
16 36 : pub(crate) fn start(str: &[u8], salt: &[u8], iterations: u32) -> Self {
17 36 : let hmac =
18 36 : Hmac::<Sha256>::new_from_slice(str).expect("HMAC is able to accept all key sizes");
19 36 :
20 36 : let prev = hmac
21 36 : .clone()
22 36 : .chain_update(salt)
23 36 : .chain_update(1u32.to_be_bytes())
24 36 : .finalize()
25 36 : .into_bytes();
26 36 :
27 36 : Self {
28 36 : hmac,
29 36 : // one consumed for the hash above
30 36 : iterations: iterations - 1,
31 36 : hi: prev,
32 36 : prev,
33 36 : }
34 36 : }
35 :
36 35 : pub(crate) fn cost(&self) -> u32 {
37 35 : (self.iterations).clamp(0, 4096)
38 35 : }
39 :
40 912 : pub(crate) fn turn(&mut self) -> std::task::Poll<[u8; 32]> {
41 912 : let Self {
42 912 : hmac,
43 912 : prev,
44 912 : hi,
45 912 : iterations,
46 912 : } = self;
47 912 :
48 912 : // only do 4096 iterations per turn before sharing the thread for fairness
49 912 : let n = (*iterations).clamp(0, 4096);
50 912 : for _ in 0..n {
51 3722844 : *prev = hmac.clone().chain_update(*prev).finalize().into_bytes();
52 :
53 119131008 : for (hi, prev) in hi.iter_mut().zip(*prev) {
54 119131008 : *hi ^= prev;
55 119131008 : }
56 : }
57 :
58 912 : *iterations -= n;
59 912 : if *iterations == 0 {
60 36 : std::task::Poll::Ready((*hi).into())
61 : } else {
62 876 : std::task::Poll::Pending
63 : }
64 912 : }
65 : }
66 :
67 : #[cfg(test)]
68 : mod tests {
69 : use super::Pbkdf2;
70 : use pbkdf2::pbkdf2_hmac_array;
71 : use sha2::Sha256;
72 :
73 : #[test]
74 6 : fn works() {
75 6 : let salt = b"sodium chloride";
76 6 : let pass = b"Ne0n_!5_50_C007";
77 6 :
78 6 : let mut job = Pbkdf2::start(pass, salt, 600000);
79 6 : let hash = loop {
80 882 : let std::task::Poll::Ready(hash) = job.turn() else {
81 876 : continue;
82 : };
83 6 : break hash;
84 6 : };
85 6 :
86 6 : let expected = pbkdf2_hmac_array::<Sha256, 32>(pass, salt, 600000);
87 6 : assert_eq!(hash, expected);
88 6 : }
89 : }
|