sha_crypt/
mcf.rs

1//! Implementation of the `password-hash` crate API.
2
3pub use mcf::{PasswordHash, PasswordHashRef};
4
5use crate::{BLOCK_SIZE_SHA256, BLOCK_SIZE_SHA512, Params, algorithm::Algorithm};
6use base64ct::{Base64ShaCrypt, Encoding};
7use core::str::FromStr;
8use mcf::Base64;
9use password_hash::{
10    CustomizedPasswordHasher, Error, PasswordHasher, PasswordVerifier, Result, Version,
11};
12use subtle::ConstantTimeEq;
13
14/// SHA-crypt type for use with the [`PasswordHasher`] and [`PasswordVerifier`] traits, which can
15/// produce and verify password hashes in [`Modular Crypt Format`][`mcf`].
16#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
17pub struct ShaCrypt {
18    /// Default algorithm to use when generating password hashes.
19    algorithm: Algorithm,
20
21    /// Default params to use when generating password hashes.
22    params: Params,
23}
24
25impl ShaCrypt {
26    /// SHA-crypt configured with SHA-256 as the default.
27    pub const SHA256: Self = Self::new(Algorithm::Sha256Crypt, Params::RECOMMENDED);
28
29    /// SHA-crypt configured with SHA-512 as the default.
30    pub const SHA512: Self = Self::new(Algorithm::Sha512Crypt, Params::RECOMMENDED);
31
32    /// Create a new password hasher with customized algorithm and params.
33    pub const fn new(algorithm: Algorithm, params: Params) -> Self {
34        Self { algorithm, params }
35    }
36}
37
38impl CustomizedPasswordHasher<PasswordHash> for ShaCrypt {
39    type Params = Params;
40
41    fn hash_password_customized(
42        &self,
43        password: &[u8],
44        salt: &[u8],
45        alg_id: Option<&str>,
46        version: Option<Version>,
47        params: Params,
48    ) -> Result<PasswordHash> {
49        let alg = alg_id
50            .map(Algorithm::try_from)
51            .transpose()?
52            .unwrap_or(self.algorithm);
53
54        if version.is_some() {
55            return Err(Error::Version);
56        }
57
58        // We compute the function over the Base64-encoded salt
59        let salt = Base64ShaCrypt::encode_string(salt);
60        let mut mcf_hash = PasswordHash::from_id(alg.to_str()).expect("should have valid ID");
61
62        mcf_hash
63            .push_displayable(params)
64            .expect("should be valid field");
65
66        mcf_hash
67            .push_str(&salt)
68            .map_err(|_| Error::EncodingInvalid)?;
69
70        match alg {
71            Algorithm::Sha256Crypt => {
72                let out = sha256_crypt_core(password, salt.as_bytes(), params);
73                mcf_hash.push_base64(&out, Base64::Crypt);
74            }
75            Algorithm::Sha512Crypt => {
76                let out = sha512_crypt_core(password, salt.as_bytes(), params);
77                mcf_hash.push_base64(&out, Base64::Crypt);
78            }
79        }
80
81        Ok(mcf_hash)
82    }
83}
84
85impl PasswordHasher<PasswordHash> for ShaCrypt {
86    fn hash_password_with_salt(&self, password: &[u8], salt: &[u8]) -> Result<PasswordHash> {
87        self.hash_password_customized(password, salt, None, None, self.params)
88    }
89}
90
91impl PasswordVerifier<PasswordHash> for ShaCrypt {
92    fn verify_password(&self, password: &[u8], hash: &PasswordHash) -> Result<()> {
93        self.verify_password(password, hash.as_password_hash_ref())
94    }
95}
96
97impl PasswordVerifier<PasswordHashRef> for ShaCrypt {
98    fn verify_password(&self, password: &[u8], hash: &PasswordHashRef) -> Result<()> {
99        let alg = hash.id().parse::<Algorithm>()?;
100        let mut fields = hash.fields();
101        let mut next = fields.next().ok_or(Error::EncodingInvalid)?;
102
103        let mut params = Params::default();
104
105        // decode params
106        // TODO(tarcieri): `mcf::Field` helper methods for parsing params?
107        if let Ok(p) = Params::from_str(next.as_str()) {
108            params = p;
109            next = fields.next().ok_or(Error::EncodingInvalid)?;
110        }
111
112        let salt = next.as_str().as_bytes();
113
114        // decode expected password hash
115        let expected = fields
116            .next()
117            .ok_or(Error::EncodingInvalid)?
118            .decode_base64(Base64::Crypt)
119            .map_err(|_| Error::EncodingInvalid)?;
120
121        // should be the last field
122        if fields.next().is_some() {
123            return Err(Error::EncodingInvalid);
124        }
125
126        let is_valid = match alg {
127            Algorithm::Sha256Crypt => sha256_crypt_core(password, salt, params)
128                .as_ref()
129                .ct_eq(&expected),
130            Algorithm::Sha512Crypt => sha512_crypt_core(password, salt, params)
131                .as_ref()
132                .ct_eq(&expected),
133        };
134
135        if (!is_valid).into() {
136            return Err(Error::PasswordInvalid);
137        }
138
139        Ok(())
140    }
141}
142
143impl PasswordVerifier<str> for ShaCrypt {
144    fn verify_password(&self, password: &[u8], hash: &str) -> password_hash::Result<()> {
145        // TODO(tarcieri): better mapping from `mcf::Error` and `password_hash::Error`?
146        let hash = PasswordHashRef::new(hash).map_err(|_| Error::EncodingInvalid)?;
147        self.verify_password(password, hash)
148    }
149}
150
151impl From<Algorithm> for ShaCrypt {
152    fn from(algorithm: Algorithm) -> Self {
153        Self {
154            algorithm,
155            params: Params::default(),
156        }
157    }
158}
159
160impl From<Params> for ShaCrypt {
161    fn from(params: Params) -> Self {
162        Self {
163            algorithm: Algorithm::default(),
164            params,
165        }
166    }
167}
168
169/// SHA-256-crypt core function: uses an algorithm-specific transposition table.
170fn sha256_crypt_core(password: &[u8], salt: &[u8], params: Params) -> [u8; BLOCK_SIZE_SHA256] {
171    let output = super::sha256_crypt(password, salt, params);
172    let transposition_table = [
173        20, 10, 0, 11, 1, 21, 2, 22, 12, 23, 13, 3, 14, 4, 24, 5, 25, 15, 26, 16, 6, 17, 7, 27, 8,
174        28, 18, 29, 19, 9, 30, 31,
175    ];
176
177    let mut transposed = [0u8; BLOCK_SIZE_SHA256];
178    for (i, &ti) in transposition_table.iter().enumerate() {
179        transposed[i] = output[ti as usize];
180    }
181
182    transposed
183}
184
185/// SHA-512-crypt core function: uses an algorithm-specific transposition table.
186fn sha512_crypt_core(password: &[u8], salt: &[u8], params: Params) -> [u8; BLOCK_SIZE_SHA512] {
187    let output = super::sha512_crypt(password, salt, params);
188    let transposition_table = [
189        42, 21, 0, 1, 43, 22, 23, 2, 44, 45, 24, 3, 4, 46, 25, 26, 5, 47, 48, 27, 6, 7, 49, 28, 29,
190        8, 50, 51, 30, 9, 10, 52, 31, 32, 11, 53, 54, 33, 12, 13, 55, 34, 35, 14, 56, 57, 36, 15,
191        16, 58, 37, 38, 17, 59, 60, 39, 18, 19, 61, 40, 41, 20, 62, 63,
192    ];
193
194    let mut transposed = [0u8; BLOCK_SIZE_SHA512];
195    for (i, &ti) in transposition_table.iter().enumerate() {
196        transposed[i] = output[ti as usize];
197    }
198
199    transposed
200}