1#![no_std]
32#![cfg_attr(docsrs, feature(doc_cfg))]
33#![doc(
34 html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg",
35 html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg"
36)]
37#![deny(unsafe_code)]
38#![warn(missing_docs, rust_2018_idioms)]
39
40#[macro_use]
42extern crate alloc;
43
44#[cfg(feature = "std")]
45extern crate std;
46
47mod b64;
48mod defs;
49mod errors;
50mod params;
51
52pub use crate::{
53 defs::{BLOCK_SIZE_SHA256, BLOCK_SIZE_SHA512},
54 errors::CryptError,
55 params::{Sha256Params, Sha512Params, ROUNDS_DEFAULT, ROUNDS_MAX, ROUNDS_MIN},
56};
57
58use alloc::{string::String, vec::Vec};
59use sha2::{Digest, Sha256, Sha512};
60
61#[cfg(feature = "simple")]
62use {
63 crate::{
64 defs::{SALT_MAX_LEN, TAB},
65 errors::CheckError,
66 },
67 alloc::string::ToString,
68 rand::{distributions::Distribution, thread_rng, Rng},
69};
70
71#[cfg(feature = "simple")]
72static SHA256_SALT_PREFIX: &str = "$5$";
73#[cfg(feature = "simple")]
74static SHA256_ROUNDS_PREFIX: &str = "rounds=";
75
76#[cfg(feature = "simple")]
77static SHA512_SALT_PREFIX: &str = "$6$";
78#[cfg(feature = "simple")]
79static SHA512_ROUNDS_PREFIX: &str = "rounds=";
80
81pub fn sha512_crypt(
96 password: &[u8],
97 salt: &[u8],
98 params: &Sha512Params,
99) -> Result<[u8; BLOCK_SIZE_SHA512], CryptError> {
100 let pw_len = password.len();
101
102 let salt_len = salt.len();
103 let salt = match salt_len {
104 0..=15 => &salt[0..salt_len],
105 _ => &salt[0..16],
106 };
107 let salt_len = salt.len();
108
109 if params.rounds < ROUNDS_MIN || params.rounds > ROUNDS_MAX {
110 return Err(CryptError::RoundsError);
111 }
112
113 let digest_a = sha512crypt_intermediate(password, salt);
114
115 let mut hasher_alt = Sha512::default();
117
118 for _ in 0..pw_len {
120 hasher_alt.update(password);
121 }
122
123 let dp = hasher_alt.finalize();
125
126 let p_vec = produce_byte_seq(pw_len, &dp);
129
130 hasher_alt = Sha512::default();
132
133 for _ in 0..(16 + digest_a[0] as usize) {
136 hasher_alt.update(salt);
137 }
138
139 let ds = hasher_alt.finalize();
142
143 let s_vec = produce_byte_seq(salt_len, &ds);
146
147 let mut digest_c = digest_a;
148 for i in 0..params.rounds {
151 let mut hasher = Sha512::default();
153
154 if (i & 1) != 0 {
156 hasher.update(&p_vec);
157 } else {
158 hasher.update(digest_c);
159 }
160
161 if i % 3 != 0 {
163 hasher.update(&s_vec);
164 }
165
166 if i % 7 != 0 {
168 hasher.update(&p_vec);
169 }
170
171 if (i & 1) != 0 {
173 hasher.update(digest_c);
174 } else {
175 hasher.update(&p_vec);
176 }
177
178 digest_c.clone_from_slice(&hasher.finalize());
179 }
180
181 Ok(digest_c)
182}
183
184pub fn sha256_crypt(
199 password: &[u8],
200 salt: &[u8],
201 params: &Sha256Params,
202) -> Result<[u8; BLOCK_SIZE_SHA256], CryptError> {
203 let pw_len = password.len();
204
205 let salt_len = salt.len();
206 let salt = match salt_len {
207 0..=15 => &salt[0..salt_len],
208 _ => &salt[0..16],
209 };
210 let salt_len = salt.len();
211
212 if params.rounds < ROUNDS_MIN || params.rounds > ROUNDS_MAX {
213 return Err(CryptError::RoundsError);
214 }
215
216 let digest_a = sha256crypt_intermediate(password, salt);
217
218 let mut hasher_alt = Sha256::default();
220
221 for _ in 0..pw_len {
223 hasher_alt.update(password);
224 }
225
226 let dp = hasher_alt.finalize();
228
229 let p_vec = produce_byte_seq(pw_len, &dp);
232
233 hasher_alt = Sha256::default();
235
236 for _ in 0..(16 + digest_a[0] as usize) {
239 hasher_alt.update(salt);
240 }
241
242 let ds = hasher_alt.finalize();
245
246 let s_vec = produce_byte_seq(salt_len, &ds);
249
250 let mut digest_c = digest_a;
251 for i in 0..params.rounds {
254 let mut hasher = Sha256::default();
256
257 if (i & 1) != 0 {
259 hasher.update(&p_vec);
260 } else {
261 hasher.update(digest_c);
262 }
263
264 if i % 3 != 0 {
266 hasher.update(&s_vec);
267 }
268
269 if i % 7 != 0 {
271 hasher.update(&p_vec);
272 }
273
274 if (i & 1) != 0 {
276 hasher.update(digest_c);
277 } else {
278 hasher.update(&p_vec);
279 }
280
281 digest_c.clone_from_slice(&hasher.finalize());
282 }
283
284 Ok(digest_c)
285}
286
287pub fn sha512_crypt_b64(
299 password: &[u8],
300 salt: &[u8],
301 params: &Sha512Params,
302) -> Result<String, CryptError> {
303 let output = sha512_crypt(password, salt, params)?;
304 let r = String::from_utf8(b64::encode_sha512(&output).to_vec())?;
305 Ok(r)
306}
307
308pub fn sha256_crypt_b64(
320 password: &[u8],
321 salt: &[u8],
322 params: &Sha256Params,
323) -> Result<String, CryptError> {
324 let output = sha256_crypt(password, salt, params)?;
325 let r = String::from_utf8(b64::encode_sha256(&output).to_vec())?;
326 Ok(r)
327}
328
329#[cfg(feature = "simple")]
341#[cfg_attr(docsrs, doc(cfg(feature = "simple")))]
342pub fn sha512_simple(password: &str, params: &Sha512Params) -> Result<String, CryptError> {
343 let rng = thread_rng();
344
345 let salt: String = rng
346 .sample_iter(&ShaCryptDistribution)
347 .take(SALT_MAX_LEN)
348 .collect();
349
350 let out = sha512_crypt(password.as_bytes(), salt.as_bytes(), params)?;
351
352 let mut result = String::new();
353 result.push_str(SHA512_SALT_PREFIX);
354 if params.rounds != ROUNDS_DEFAULT {
355 result.push_str(&format!("{}{}", SHA512_ROUNDS_PREFIX, params.rounds));
356 result.push('$');
357 }
358 result.push_str(&salt);
359 result.push('$');
360 let s = String::from_utf8(b64::encode_sha512(&out).to_vec())?;
361 result.push_str(&s);
362 Ok(result)
363}
364
365#[cfg(feature = "simple")]
377#[cfg_attr(docsrs, doc(cfg(feature = "simple")))]
378pub fn sha256_simple(password: &str, params: &Sha256Params) -> Result<String, CryptError> {
379 let rng = thread_rng();
380
381 let salt: String = rng
382 .sample_iter(&ShaCryptDistribution)
383 .take(SALT_MAX_LEN)
384 .collect();
385
386 let out = sha256_crypt(password.as_bytes(), salt.as_bytes(), params)?;
387
388 let mut result = String::new();
389 result.push_str(SHA256_SALT_PREFIX);
390 if params.rounds != ROUNDS_DEFAULT {
391 result.push_str(&format!("{}{}", SHA256_ROUNDS_PREFIX, params.rounds));
392 result.push('$');
393 }
394 result.push_str(&salt);
395 result.push('$');
396 let s = String::from_utf8(b64::encode_sha256(&out).to_vec())?;
397 result.push_str(&s);
398 Ok(result)
399}
400
401#[cfg(feature = "simple")]
414#[cfg_attr(docsrs, doc(cfg(feature = "simple")))]
415pub fn sha512_check(password: &str, hashed_value: &str) -> Result<(), CheckError> {
416 let mut iter = hashed_value.split('$');
417
418 if iter.next() != Some("") {
420 return Err(CheckError::InvalidFormat(
421 "Should start with '$".to_string(),
422 ));
423 }
424
425 if iter.next() != Some("6") {
426 return Err(CheckError::InvalidFormat(format!(
427 "does not contain SHA512 identifier: '{SHA512_SALT_PREFIX}'",
428 )));
429 }
430
431 let mut next = iter.next().ok_or_else(|| {
432 CheckError::InvalidFormat("Does not contain a rounds or salt nor hash string".to_string())
433 })?;
434 let rounds = if next.starts_with(SHA512_ROUNDS_PREFIX) {
435 let rounds = next;
436 next = iter.next().ok_or_else(|| {
437 CheckError::InvalidFormat("Does not contain a salt nor hash string".to_string())
438 })?;
439
440 rounds[SHA512_ROUNDS_PREFIX.len()..].parse().map_err(|_| {
441 CheckError::InvalidFormat(format!(
442 "{SHA512_ROUNDS_PREFIX} specifier need to be a number",
443 ))
444 })?
445 } else {
446 ROUNDS_DEFAULT
447 };
448
449 let salt = next;
450
451 let hash = iter
452 .next()
453 .ok_or_else(|| CheckError::InvalidFormat("Does not contain a hash string".to_string()))?;
454
455 if iter.next().is_some() {
457 return Err(CheckError::InvalidFormat(
458 "Trailing characters present".to_string(),
459 ));
460 }
461
462 let params = Sha512Params { rounds };
463
464 let output = match sha512_crypt(password.as_bytes(), salt.as_bytes(), ¶ms) {
465 Ok(v) => v,
466 Err(e) => return Err(CheckError::Crypt(e)),
467 };
468
469 let hash = b64::decode_sha512(hash.as_bytes())?;
470
471 use subtle::ConstantTimeEq;
472 if output.ct_eq(&hash).into() {
473 Ok(())
474 } else {
475 Err(CheckError::HashMismatch)
476 }
477}
478
479#[cfg(feature = "simple")]
492#[cfg_attr(docsrs, doc(cfg(feature = "simple")))]
493pub fn sha256_check(password: &str, hashed_value: &str) -> Result<(), CheckError> {
494 let mut iter = hashed_value.split('$');
495
496 if iter.next() != Some("") {
498 return Err(CheckError::InvalidFormat(
499 "Should start with '$".to_string(),
500 ));
501 }
502
503 if iter.next() != Some("5") {
504 return Err(CheckError::InvalidFormat(format!(
505 "does not contain SHA256 identifier: '{SHA256_SALT_PREFIX}'",
506 )));
507 }
508
509 let mut next = iter.next().ok_or_else(|| {
510 CheckError::InvalidFormat("Does not contain a rounds or salt nor hash string".to_string())
511 })?;
512 let rounds = if next.starts_with(SHA256_ROUNDS_PREFIX) {
513 let rounds = next;
514 next = iter.next().ok_or_else(|| {
515 CheckError::InvalidFormat("Does not contain a salt nor hash string".to_string())
516 })?;
517
518 rounds[SHA256_ROUNDS_PREFIX.len()..].parse().map_err(|_| {
519 CheckError::InvalidFormat(format!(
520 "{SHA256_ROUNDS_PREFIX} specifier need to be a number",
521 ))
522 })?
523 } else {
524 ROUNDS_DEFAULT
525 };
526
527 let salt = next;
528
529 let hash = iter
530 .next()
531 .ok_or_else(|| CheckError::InvalidFormat("Does not contain a hash string".to_string()))?;
532
533 if iter.next().is_some() {
535 return Err(CheckError::InvalidFormat(
536 "Trailing characters present".to_string(),
537 ));
538 }
539
540 let params = Sha256Params { rounds };
541
542 let output = match sha256_crypt(password.as_bytes(), salt.as_bytes(), ¶ms) {
543 Ok(v) => v,
544 Err(e) => return Err(CheckError::Crypt(e)),
545 };
546
547 let hash = b64::decode_sha256(hash.as_bytes())?;
548
549 use subtle::ConstantTimeEq;
550 if output.ct_eq(&hash).into() {
551 Ok(())
552 } else {
553 Err(CheckError::HashMismatch)
554 }
555}
556
557#[cfg(feature = "simple")]
558#[derive(Debug)]
559struct ShaCryptDistribution;
560
561#[cfg(feature = "simple")]
562impl Distribution<char> for ShaCryptDistribution {
563 fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> char {
564 const RANGE: u32 = 26 + 26 + 10 + 2; loop {
566 let var = rng.next_u32() >> (32 - 6);
567 if var < RANGE {
568 return TAB[var as usize] as char;
569 }
570 }
571 }
572}
573
574fn produce_byte_seq(len: usize, fill_from: &[u8]) -> Vec<u8> {
575 let bs = fill_from.len();
576 let mut seq: Vec<u8> = vec![0; len];
577 let mut offset: usize = 0;
578 for _ in 0..(len / bs) {
579 seq[offset..offset + bs].clone_from_slice(fill_from);
580 offset += bs;
581 }
582 let from_slice = &fill_from[..(len % bs)];
583 seq[offset..offset + (len % bs)].clone_from_slice(from_slice);
584 seq
585}
586
587fn sha512crypt_intermediate(password: &[u8], salt: &[u8]) -> [u8; BLOCK_SIZE_SHA512] {
588 let pw_len = password.len();
589
590 let mut hasher = Sha512::default();
591 hasher.update(password);
592 hasher.update(salt);
593
594 let mut hasher_alt = Sha512::default();
596 hasher_alt.update(password);
598 hasher_alt.update(salt);
600 hasher_alt.update(password);
602 let digest_b = hasher_alt.finalize();
604
605 for _ in 0..(pw_len / BLOCK_SIZE_SHA512) {
607 hasher.update(digest_b);
608 }
609 hasher.update(&digest_b[..(pw_len % BLOCK_SIZE_SHA512)]);
611
612 let mut n = pw_len;
614 for _ in 0..pw_len {
615 if n == 0 {
616 break;
617 }
618 if (n & 1) != 0 {
619 hasher.update(digest_b);
620 } else {
621 hasher.update(password);
622 }
623 n >>= 1;
624 }
625
626 hasher.finalize().as_slice().try_into().unwrap()
628}
629
630fn sha256crypt_intermediate(password: &[u8], salt: &[u8]) -> [u8; BLOCK_SIZE_SHA256] {
631 let pw_len = password.len();
632
633 let mut hasher = Sha256::default();
634 hasher.update(password);
635 hasher.update(salt);
636
637 let mut hasher_alt = Sha256::default();
639 hasher_alt.update(password);
641 hasher_alt.update(salt);
643 hasher_alt.update(password);
645 let digest_b = hasher_alt.finalize();
647
648 for _ in 0..(pw_len / BLOCK_SIZE_SHA256) {
650 hasher.update(digest_b);
651 }
652 hasher.update(&digest_b[..(pw_len % BLOCK_SIZE_SHA256)]);
654
655 let mut n = pw_len;
657 for _ in 0..pw_len {
658 if n == 0 {
659 break;
660 }
661 if (n & 1) != 0 {
662 hasher.update(digest_b);
663 } else {
664 hasher.update(password);
665 }
666 n >>= 1;
667 }
668
669 hasher.finalize().as_slice().try_into().unwrap()
671}