1use getrandom;
3use std::convert::AsRef;
4use std::fmt;
5use std::str::FromStr;
6
7mod b64;
8mod bcrypt;
9mod errors;
10
11pub use crate::bcrypt::bcrypt;
12pub use crate::errors::{BcryptError, BcryptResult};
13
14const MIN_COST: u32 = 4;
16const MAX_COST: u32 = 31;
17pub const DEFAULT_COST: u32 = 12;
18
19#[derive(Debug, PartialEq)]
20pub struct HashParts {
22 cost: u32,
23 salt: String,
24 hash: String,
25}
26
27pub enum Version {
30 TwoA,
31 TwoX,
32 TwoY,
33 TwoB,
34}
35
36impl HashParts {
37 fn format(self) -> String {
39 self.format_for_version(Version::TwoB)
40 }
41
42 pub fn get_cost(&self) -> u32 {
44 self.cost
45 }
46
47 pub fn get_salt(&self) -> String {
49 self.salt.clone()
50 }
51
52 pub fn format_for_version(&self, version: Version) -> String {
54 format!("${}${:02}${}{}", version, self.cost, self.salt, self.hash)
56 }
57}
58
59impl FromStr for HashParts {
60 type Err = BcryptError;
61
62 fn from_str(s: &str) -> Result<Self, Self::Err> {
63 split_hash(s)
64 }
65}
66
67impl ToString for HashParts {
68 fn to_string(&self) -> String {
69 self.format_for_version(Version::TwoY)
70 }
71}
72
73impl fmt::Display for Version {
74 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
75 let str = match self {
76 Version::TwoA => "2a",
77 Version::TwoB => "2b",
78 Version::TwoX => "2x",
79 Version::TwoY => "2y",
80 };
81 write!(f, "{}", str)
82 }
83}
84
85fn _hash_password(password: &[u8], cost: u32, salt: &[u8]) -> BcryptResult<HashParts> {
88 if cost > MAX_COST || cost < MIN_COST {
89 return Err(BcryptError::CostNotAllowed(cost));
90 }
91 if password.contains(&0u8) {
92 return Err(BcryptError::InvalidPassword);
93 }
94
95 let mut output = [0u8; 24];
97 let mut vec: Vec<u8> = Vec::new();
99 vec.extend_from_slice(password);
100 vec.push(0);
101 let truncated = if vec.len() > 72 { &vec[..72] } else { &vec };
104
105 bcrypt::bcrypt(cost, salt, truncated, &mut output);
106
107 Ok(HashParts {
108 cost,
109 salt: b64::encode(salt),
110 hash: b64::encode(&output[..23]), })
112}
113
114fn split_hash(hash: &str) -> BcryptResult<HashParts> {
117 let mut parts = HashParts {
118 cost: 0,
119 salt: "".to_string(),
120 hash: "".to_string(),
121 };
122
123 let raw_parts: Vec<_> = hash.split('$').filter(|s| !s.is_empty()).collect();
125
126 if raw_parts.len() != 3 {
127 return Err(BcryptError::InvalidHash(hash.to_string()));
128 }
129
130 if raw_parts[0] != "2y" && raw_parts[0] != "2b" && raw_parts[0] != "2a" {
131 return Err(BcryptError::InvalidPrefix(raw_parts[0].to_string()));
132 }
133
134 if let Ok(c) = raw_parts[1].parse::<u32>() {
135 parts.cost = c;
136 } else {
137 return Err(BcryptError::InvalidCost(raw_parts[1].to_string()));
138 }
139
140 if raw_parts[2].len() == 53 {
141 parts.salt = raw_parts[2][..22].chars().collect();
142 parts.hash = raw_parts[2][22..].chars().collect();
143 } else {
144 return Err(BcryptError::InvalidHash(hash.to_string()));
145 }
146
147 Ok(parts)
148}
149
150pub fn hash<P: AsRef<[u8]>>(password: P, cost: u32) -> BcryptResult<String> {
153 hash_with_result(password, cost).map(|r| r.format())
154}
155
156pub fn hash_with_result<P: AsRef<[u8]>>(password: P, cost: u32) -> BcryptResult<HashParts> {
160 let salt = {
161 let mut s = [0u8; 16];
162 getrandom::getrandom(&mut s).expect("An error occurred");
163 s
164 };
165
166 _hash_password(password.as_ref(), cost, salt.as_ref())
167}
168
169pub fn hash_with_salt<P: AsRef<[u8]>>(password: P, cost: u32, salt: &[u8]) -> BcryptResult<HashParts> {
172 _hash_password(password.as_ref(), cost, salt)
173}
174
175pub fn verify<P: AsRef<[u8]>>(password: P, hash: &str) -> BcryptResult<bool> {
177 let parts = split_hash(hash)?;
178 let salt = b64::decode(&parts.salt)?;
179 let generated = _hash_password(password.as_ref(), parts.cost, &salt)?;
180 let source_decoded = b64::decode(&parts.hash)?;
181 let generated_decoded = b64::decode(&generated.hash)?;
182 if source_decoded.len() != generated_decoded.len() {
183 return Ok(false);
184 }
185
186 for (a, b) in source_decoded.into_iter().zip(generated_decoded) {
187 if a != b {
188 return Ok(false);
189 }
190 }
191
192 Ok(true)
193}
194
195#[cfg(test)]
196mod tests {
197 use super::{
198 _hash_password, hash, hash_with_salt, split_hash, verify, BcryptError, BcryptResult, HashParts, Version,
199 DEFAULT_COST,
200 };
201 use quickcheck::{quickcheck, TestResult};
202 use std::iter;
203 use std::str::FromStr;
204
205 #[test]
206 fn can_split_hash() {
207 let hash = "$2y$12$L6Bc/AlTQHyd9liGgGEZyOFLPHNgyxeEPfgYfBCVxJ7JIlwxyVU3u";
208 let output = split_hash(hash).unwrap();
209 let expected = HashParts {
210 cost: 12,
211 salt: "L6Bc/AlTQHyd9liGgGEZyO".to_string(),
212 hash: "FLPHNgyxeEPfgYfBCVxJ7JIlwxyVU3u".to_string(),
213 };
214 assert_eq!(output, expected);
215 }
216
217 #[test]
218 fn can_output_cost_and_salt_from_parsed_hash() {
219 let hash = "$2y$12$L6Bc/AlTQHyd9liGgGEZyOFLPHNgyxeEPfgYfBCVxJ7JIlwxyVU3u";
220 let parsed = HashParts::from_str(hash).unwrap();
221 assert_eq!(parsed.get_cost(), 12);
222 assert_eq!(parsed.get_salt(), "L6Bc/AlTQHyd9liGgGEZyO".to_string());
223 }
224
225 #[test]
226 fn returns_an_error_if_a_parsed_hash_is_baddly_formated() {
227 let hash1 = "$2y$12$L6Bc/AlTQHyd9lGEZyOFLPHNgyxeEPfgYfBCVxJ7JIlwxyVU3u";
228 assert!(HashParts::from_str(hash1).is_err());
229
230 let hash2 = "!2y$12$L6Bc/AlTQHyd9liGgGEZyOFLPHNgyxeEPfgYfBCVxJ7JIlwxyVU3u";
231 assert!(HashParts::from_str(hash2).is_err());
232
233 let hash3 = "$2y$-12$L6Bc/AlTQHyd9liGgGEZyOFLPHNgyxeEPfgYfBCVxJ7JIlwxyVU3u";
234 assert!(HashParts::from_str(hash3).is_err());
235 }
236
237 #[test]
238 fn can_verify_hash_generated_from_some_online_tool() {
239 let hash = "$2a$04$UuTkLRZZ6QofpDOlMz32MuuxEHA43WOemOYHPz6.SjsVsyO1tDU96";
240 assert!(verify("password", hash).unwrap());
241 }
242
243 #[test]
244 fn can_verify_hash_generated_from_python() {
245 let hash = "$2b$04$EGdrhbKUv8Oc9vGiXX0HQOxSg445d458Muh7DAHskb6QbtCvdxcie";
246 assert!(verify("correctbatteryhorsestapler", hash).unwrap());
247 }
248
249 #[test]
250 fn can_verify_hash_generated_from_node() {
251 let hash = "$2a$04$n4Uy0eSnMfvnESYL.bLwuuj0U/ETSsoTpRT9GVk5bektyVVa5xnIi";
252 assert!(verify("correctbatteryhorsestapler", hash).unwrap());
253 }
254
255 #[test]
256 fn a_wrong_password_is_false() {
257 let hash = "$2b$04$EGdrhbKUv8Oc9vGiXX0HQOxSg445d458Muh7DAHskb6QbtCvdxcie";
258 assert!(!verify("wrong", hash).unwrap());
259 }
260
261 #[test]
262 fn errors_with_invalid_hash() {
263 let hash = "$2a$04$n4Uy0eSnMfvnESYL.bLwuuj0U/ETSsoTpRT9GVk$5bektyVVa5xnIi";
265 assert!(verify("correctbatteryhorsestapler", hash).is_err());
266 }
267
268 #[test]
269 fn errors_with_non_number_cost() {
270 let hash = "$2a$ab$n4Uy0eSnMfvnESYL.bLwuuj0U/ETSsoTpRT9GVk$5bektyVVa5xnIi";
272 assert!(verify("correctbatteryhorsestapler", hash).is_err());
273 }
274
275 #[test]
276 fn errors_with_a_hash_too_long() {
277 let hash = "$2a$04$n4Uy0eSnMfvnESYL.bLwuuj0U/ETSsoTpRT9GVk$5bektyVVa5xnIerererereri";
279 assert!(verify("correctbatteryhorsestapler", hash).is_err());
280 }
281
282 #[test]
283 fn can_verify_own_generated() {
284 let hashed = hash("hunter2", 4).unwrap();
285 assert_eq!(true, verify("hunter2", &hashed).unwrap());
286 }
287
288 #[test]
289 fn long_passwords_truncate_correctly() {
290 let hash = "$2a$05$......................YgIDy4hFBdVlc/6LHnD9mX488r9cLd2";
292 assert!(verify(iter::repeat("x").take(100).collect::<String>(), hash).unwrap());
293 }
294
295 #[test]
296 fn generate_versions() {
297 let password = "hunter2".as_bytes();
298 let salt = vec![0; 16];
299 let result = _hash_password(password, DEFAULT_COST, salt.as_slice()).unwrap();
300 assert_eq!(
301 "$2a$12$......................21jzCB1r6pN6rp5O2Ev0ejjTAboskKm",
302 result.format_for_version(Version::TwoA)
303 );
304 assert_eq!(
305 "$2b$12$......................21jzCB1r6pN6rp5O2Ev0ejjTAboskKm",
306 result.format_for_version(Version::TwoB)
307 );
308 assert_eq!(
309 "$2x$12$......................21jzCB1r6pN6rp5O2Ev0ejjTAboskKm",
310 result.format_for_version(Version::TwoX)
311 );
312 assert_eq!(
313 "$2y$12$......................21jzCB1r6pN6rp5O2Ev0ejjTAboskKm",
314 result.format_for_version(Version::TwoY)
315 );
316 let hash = result.to_string();
317 assert_eq!(true, verify("hunter2", &hash).unwrap());
318 }
319
320 #[test]
321 fn forbid_null_bytes() {
322 fn assert_invalid_password(password: &[u8]) {
323 match hash(password, DEFAULT_COST) {
324 Ok(_) => panic!(format!(
325 "NULL bytes must be forbidden, but {:?} is allowed.",
326 password
327 )),
328 Err(BcryptError::InvalidPassword) => {}
329 Err(e) => panic!(format!(
330 "NULL bytes are forbidden but error differs: {} for {:?}.",
331 e, password
332 )),
333 }
334 }
335 assert_invalid_password("\0".as_bytes());
336 assert_invalid_password("\0\0\0\0\0\0\0\0".as_bytes());
337 assert_invalid_password("passw0rd\0".as_bytes());
338 assert_invalid_password("passw0rd\0with tail".as_bytes());
339 assert_invalid_password("\0passw0rd".as_bytes());
340 }
341
342 #[test]
343 fn hash_with_fixed_salt() {
344 let salt = vec![38, 113, 212, 141, 108, 213, 195, 166,
345 201, 38, 20, 13, 47, 40, 104, 18];
346 let hashed = hash_with_salt("My S3cre7 P@55w0rd!", 5, &salt).unwrap().to_string();
347 assert_eq!("$2y$05$HlFShUxTu4ZHHfOLJwfmCeDj/kuKFKboanXtDJXxCC7aIPTUgxNDe", &hashed);
348 }
349
350 quickcheck! {
351 fn can_verify_arbitrary_own_generated(pass: Vec<u8>) -> BcryptResult<bool> {
352 let mut pass = pass;
353 pass.retain(|&b| b != 0);
354 let hashed = hash(&pass, 4)?;
355 verify(pass, &hashed)
356 }
357
358 fn doesnt_verify_different_passwords(a: Vec<u8>, b: Vec<u8>) -> BcryptResult<TestResult> {
359 let mut a = a;
360 a.retain(|&b| b != 0);
361 let mut b = b;
362 b.retain(|&b| b != 0);
363 if a == b {
364 return Ok(TestResult::discard());
365 }
366 let hashed = hash(a, 4)?;
367 Ok(TestResult::from_bool(!verify(b, &hashed)?))
368 }
369 }
370}