1pub 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#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
21pub struct ShaCrypt {
22 algorithm: Algorithm,
24
25 params: Params,
27}
28
29impl ShaCrypt {
30 pub const SHA256: Self = Self::new(Algorithm::Sha256Crypt, Params::RECOMMENDED);
32
33 pub const SHA512: Self = Self::new(Algorithm::Sha512Crypt, Params::RECOMMENDED);
35
36 #[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 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 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 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 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 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
178fn 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
191fn 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#[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}