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 const SHA256: Self = Self::new(Algorithm::Sha256Crypt, Params::RECOMMENDED);
28
29 pub const SHA512: Self = Self::new(Algorithm::Sha512Crypt, Params::RECOMMENDED);
31
32 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 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 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 let expected = fields
116 .next()
117 .ok_or(Error::EncodingInvalid)?
118 .decode_base64(Base64::Crypt)
119 .map_err(|_| Error::EncodingInvalid)?;
120
121 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 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
169fn 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
185fn 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}