1pub 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#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
17pub struct ShaCrypt {
18 algorithm: Algorithm,
20
21 params: Params,
23}
24
25impl ShaCrypt {
26 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 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 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 let expected = fields
110 .next()
111 .ok_or(Error::EncodingInvalid)?
112 .decode_base64(Base64::Crypt)
113 .map_err(|_| Error::EncodingInvalid)?;
114
115 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 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
163fn 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
179fn 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}