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