Skip to main content

sha_crypt/
mcf.rs

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