Line data Source code
1 : //! Password hashing routines.
2 :
3 : use super::key::ScramKey;
4 :
5 : pub const SALTED_PASSWORD_LEN: usize = 32;
6 :
7 : /// Salted hashed password is essential for [key](super::key) derivation.
8 : #[repr(transparent)]
9 : pub struct SaltedPassword {
10 : bytes: [u8; SALTED_PASSWORD_LEN],
11 : }
12 :
13 : impl SaltedPassword {
14 : /// See `scram-common.c : scram_SaltedPassword` for details.
15 : /// Further reading: <https://datatracker.ietf.org/doc/html/rfc2898> (see `PBKDF2`).
16 28 : pub fn new(password: &[u8], salt: &[u8], iterations: u32) -> SaltedPassword {
17 28 : pbkdf2::pbkdf2_hmac_array::<sha2::Sha256, 32>(password, salt, iterations).into()
18 28 : }
19 :
20 : /// Derive `ClientKey` from a salted hashed password.
21 26 : pub fn client_key(&self) -> ScramKey {
22 26 : super::hmac_sha256(&self.bytes, [b"Client Key".as_ref()]).into()
23 26 : }
24 :
25 : /// Derive `ServerKey` from a salted hashed password.
26 26 : pub fn server_key(&self) -> ScramKey {
27 26 : super::hmac_sha256(&self.bytes, [b"Server Key".as_ref()]).into()
28 26 : }
29 : }
30 :
31 : impl From<[u8; SALTED_PASSWORD_LEN]> for SaltedPassword {
32 : #[inline(always)]
33 30 : fn from(bytes: [u8; SALTED_PASSWORD_LEN]) -> Self {
34 30 : Self { bytes }
35 30 : }
36 : }
37 :
38 : #[cfg(test)]
39 : mod tests {
40 : use super::SaltedPassword;
41 :
42 2 : fn legacy_pbkdf2_impl(password: &[u8], salt: &[u8], iterations: u32) -> SaltedPassword {
43 2 : let one = 1_u32.to_be_bytes(); // magic
44 2 :
45 2 : let mut current = super::super::hmac_sha256(password, [salt, &one]);
46 2 : let mut result = current;
47 2 : for _ in 1..iterations {
48 8190 : current = super::super::hmac_sha256(password, [current.as_ref()]);
49 : // TODO: result = current.zip(result).map(|(x, y)| x ^ y), issue #80094
50 262080 : for (i, x) in current.iter().enumerate() {
51 262080 : result[i] ^= x;
52 262080 : }
53 : }
54 :
55 2 : result.into()
56 2 : }
57 :
58 2 : #[test]
59 2 : fn pbkdf2() {
60 2 : let password = "a-very-secure-password";
61 2 : let salt = "such-a-random-salt";
62 2 : let iterations = 4096;
63 2 : let output = [
64 2 : 203, 18, 206, 81, 4, 154, 193, 100, 147, 41, 211, 217, 177, 203, 69, 210, 194, 211,
65 2 : 101, 1, 248, 156, 96, 0, 8, 223, 30, 87, 158, 41, 20, 42,
66 2 : ];
67 2 :
68 2 : let actual = SaltedPassword::new(password.as_bytes(), salt.as_bytes(), iterations);
69 2 : let expected = legacy_pbkdf2_impl(password.as_bytes(), salt.as_bytes(), iterations);
70 2 :
71 2 : assert_eq!(actual.bytes, output);
72 2 : assert_eq!(actual.bytes, expected.bytes);
73 2 : }
74 : }
|