4040#[ macro_use]
4141extern crate alloc;
4242
43- mod b64;
4443mod consts;
4544mod errors;
4645mod params;
46+ mod simple;
4747
4848pub use crate :: {
4949 consts:: { BLOCK_SIZE_SHA256 , BLOCK_SIZE_SHA512 } ,
5050 errors:: CryptError ,
5151 params:: { ROUNDS_DEFAULT , ROUNDS_MAX , ROUNDS_MIN , Sha256Params , Sha512Params } ,
5252} ;
5353
54+ #[ cfg( feature = "simple" ) ]
55+ pub use crate :: simple:: { sha256_check, sha256_simple, sha512_check, sha512_simple} ;
56+
5457use alloc:: { string:: String , vec:: Vec } ;
5558use base64ct:: { Base64ShaCrypt , Encoding } ;
5659use sha2:: { Digest , Sha256 , Sha512 } ;
@@ -60,20 +63,6 @@ pub use crate::errors::{CheckError, DecodeError};
6063
6164use crate :: consts:: { MAP_SHA256 , MAP_SHA512 } ;
6265
63- #[ cfg( feature = "simple" ) ]
64- use {
65- crate :: consts:: SALT_MAX_LEN ,
66- alloc:: string:: ToString ,
67- rand_core:: { OsRng , RngCore , TryRngCore } ,
68- } ;
69-
70- #[ cfg( feature = "simple" ) ]
71- static SHA256_MCF_ID : & str = "5" ;
72- #[ cfg( feature = "simple" ) ]
73- static SHA512_MCF_ID : & str = "6" ;
74- #[ cfg( feature = "simple" ) ]
75- static ROUNDS_PARAM : & str = "rounds=" ;
76-
7766/// The SHA512 crypt function returned as byte vector
7867///
7968/// If the provided hash is longer than defs::SALT_MAX_LEN character, it will
@@ -316,225 +305,6 @@ pub fn sha256_crypt_b64(password: &[u8], salt: &[u8], params: &Sha256Params) ->
316305 Base64ShaCrypt :: encode_string ( & transposed)
317306}
318307
319- /// Simple interface for generating a SHA512 password hash.
320- ///
321- /// The salt will be chosen randomly. The output format will conform to [1].
322- ///
323- /// `$<ID>$<SALT>$<HASH>`
324- ///
325- /// # Returns
326- /// - `Ok(String)` containing the full SHA512 password hash format on success
327- /// - `Err(CryptError)` if something went wrong.
328- ///
329- /// [1]: https://www.akkadia.org/drepper/SHA-crypt.txt
330- #[ cfg( feature = "simple" ) ]
331- pub fn sha512_simple ( password : & str , params : & Sha512Params ) -> String {
332- let salt = random_salt ( ) ;
333- let out = sha512_crypt_b64 ( password. as_bytes ( ) , salt. as_bytes ( ) , params) ;
334-
335- let mut mcf_hash = mcf:: PasswordHash :: from_id ( SHA512_MCF_ID ) . expect ( "should have valid ID" ) ;
336-
337- if params. rounds != ROUNDS_DEFAULT {
338- mcf_hash
339- . push_str ( & format ! ( "{}{}" , ROUNDS_PARAM , params. rounds) )
340- . expect ( "should be valid field" ) ;
341- }
342-
343- mcf_hash. push_str ( & salt) . expect ( "should have valid salt" ) ;
344- mcf_hash. push_str ( & out) . expect ( "should have valid hash" ) ;
345-
346- mcf_hash. into ( )
347- }
348-
349- /// Simple interface for generating a SHA256 password hash.
350- ///
351- /// The salt will be chosen randomly. The output format will conform to [1].
352- ///
353- /// `$<ID>$<SALT>$<HASH>`
354- ///
355- /// # Returns
356- /// - `Ok(String)` containing the full SHA256 password hash format on success
357- /// - `Err(CryptError)` if something went wrong.
358- ///
359- /// [1]: https://www.akkadia.org/drepper/SHA-crypt.txt
360- #[ cfg( feature = "simple" ) ]
361- pub fn sha256_simple ( password : & str , params : & Sha256Params ) -> String {
362- let salt = random_salt ( ) ;
363- let out = sha256_crypt_b64 ( password. as_bytes ( ) , salt. as_bytes ( ) , params) ;
364-
365- let mut mcf_hash = mcf:: PasswordHash :: from_id ( SHA256_MCF_ID ) . expect ( "should have valid ID" ) ;
366-
367- if params. rounds != ROUNDS_DEFAULT {
368- mcf_hash
369- . push_str ( & format ! ( "{}{}" , ROUNDS_PARAM , params. rounds) )
370- . expect ( "should be valid field" ) ;
371- }
372-
373- mcf_hash. push_str ( & salt) . expect ( "should have valid salt" ) ;
374- mcf_hash. push_str ( & out) . expect ( "should have valid hash" ) ;
375-
376- mcf_hash. into ( )
377- }
378-
379- /// Checks that given password matches provided hash.
380- ///
381- /// # Arguments
382- /// - `password` - expected password
383- /// - `hashed_value` - the hashed value which should be used for checking,
384- /// should be of format mentioned in [1]: `$6$<SALT>$<PWD>`
385- ///
386- /// # Return
387- /// `OK(())` if password matches otherwise Err(CheckError) in case of invalid
388- /// format or password mismatch.
389- ///
390- /// [1]: https://www.akkadia.org/drepper/SHA-crypt.txt
391- #[ cfg( feature = "simple" ) ]
392- pub fn sha512_check ( password : & str , hashed_value : & str ) -> Result < ( ) , CheckError > {
393- let mut iter = hashed_value. split ( '$' ) ;
394-
395- // Check that there are no characters before the first "$"
396- if iter. next ( ) != Some ( "" ) {
397- return Err ( CheckError :: InvalidFormat (
398- "Should start with '$" . to_string ( ) ,
399- ) ) ;
400- }
401-
402- if iter. next ( ) != Some ( "6" ) {
403- return Err ( CheckError :: InvalidFormat ( format ! (
404- "does not contain SHA512 identifier: '${SHA512_MCF_ID}$'" ,
405- ) ) ) ;
406- }
407-
408- let mut next = iter. next ( ) . ok_or_else ( || {
409- CheckError :: InvalidFormat ( "Does not contain a rounds or salt nor hash string" . to_string ( ) )
410- } ) ?;
411- let rounds = if next. starts_with ( ROUNDS_PARAM ) {
412- let rounds = next;
413- next = iter. next ( ) . ok_or_else ( || {
414- CheckError :: InvalidFormat ( "Does not contain a salt nor hash string" . to_string ( ) )
415- } ) ?;
416-
417- rounds[ ROUNDS_PARAM . len ( ) ..] . parse ( ) . map_err ( |_| {
418- CheckError :: InvalidFormat ( format ! ( "{ROUNDS_PARAM} specifier need to be a number" , ) )
419- } ) ?
420- } else {
421- ROUNDS_DEFAULT
422- } ;
423-
424- let salt = next;
425-
426- let hash = iter
427- . next ( )
428- . ok_or_else ( || CheckError :: InvalidFormat ( "Does not contain a hash string" . to_string ( ) ) ) ?;
429-
430- // Make sure there is no trailing data after the final "$"
431- if iter. next ( ) . is_some ( ) {
432- return Err ( CheckError :: InvalidFormat (
433- "Trailing characters present" . to_string ( ) ,
434- ) ) ;
435- }
436-
437- let params = match Sha512Params :: new ( rounds) {
438- Ok ( p) => p,
439- Err ( e) => return Err ( CheckError :: Crypt ( e) ) ,
440- } ;
441-
442- let output = sha512_crypt ( password. as_bytes ( ) , salt. as_bytes ( ) , & params) ;
443-
444- let hash = b64:: decode_sha512 ( hash. as_bytes ( ) ) ?;
445-
446- use subtle:: ConstantTimeEq ;
447- if output. ct_eq ( & hash) . into ( ) {
448- Ok ( ( ) )
449- } else {
450- Err ( CheckError :: HashMismatch )
451- }
452- }
453-
454- /// Checks that given password matches provided hash.
455- ///
456- /// # Arguments
457- /// - `password` - expected password
458- /// - `hashed_value` - the hashed value which should be used for checking,
459- /// should be of format mentioned in [1]: `$6$<SALT>$<PWD>`
460- ///
461- /// # Return
462- /// `OK(())` if password matches otherwise Err(CheckError) in case of invalid
463- /// format or password mismatch.
464- ///
465- /// [1]: https://www.akkadia.org/drepper/SHA-crypt.txt
466- #[ cfg( feature = "simple" ) ]
467- pub fn sha256_check ( password : & str , hashed_value : & str ) -> Result < ( ) , CheckError > {
468- let mut iter = hashed_value. split ( '$' ) ;
469-
470- // Check that there are no characters before the first "$"
471- if iter. next ( ) != Some ( "" ) {
472- return Err ( CheckError :: InvalidFormat (
473- "Should start with '$" . to_string ( ) ,
474- ) ) ;
475- }
476-
477- if iter. next ( ) != Some ( "5" ) {
478- return Err ( CheckError :: InvalidFormat ( format ! (
479- "does not contain SHA256 identifier: '${SHA256_MCF_ID}$'" ,
480- ) ) ) ;
481- }
482-
483- let mut next = iter. next ( ) . ok_or_else ( || {
484- CheckError :: InvalidFormat ( "Does not contain a rounds or salt nor hash string" . to_string ( ) )
485- } ) ?;
486- let rounds = if next. starts_with ( ROUNDS_PARAM ) {
487- let rounds = next;
488- next = iter. next ( ) . ok_or_else ( || {
489- CheckError :: InvalidFormat ( "Does not contain a salt nor hash string" . to_string ( ) )
490- } ) ?;
491-
492- rounds[ ROUNDS_PARAM . len ( ) ..] . parse ( ) . map_err ( |_| {
493- CheckError :: InvalidFormat ( format ! ( "{ROUNDS_PARAM} specifier need to be a number" , ) )
494- } ) ?
495- } else {
496- ROUNDS_DEFAULT
497- } ;
498-
499- let salt = next;
500-
501- let hash = iter
502- . next ( )
503- . ok_or_else ( || CheckError :: InvalidFormat ( "Does not contain a hash string" . to_string ( ) ) ) ?;
504-
505- // Make sure there is no trailing data after the final "$"
506- if iter. next ( ) . is_some ( ) {
507- return Err ( CheckError :: InvalidFormat (
508- "Trailing characters present" . to_string ( ) ,
509- ) ) ;
510- }
511-
512- let params = match Sha256Params :: new ( rounds) {
513- Ok ( p) => p,
514- Err ( e) => return Err ( CheckError :: Crypt ( e) ) ,
515- } ;
516-
517- let output = sha256_crypt ( password. as_bytes ( ) , salt. as_bytes ( ) , & params) ;
518-
519- let hash = b64:: decode_sha256 ( hash. as_bytes ( ) ) ?;
520-
521- use subtle:: ConstantTimeEq ;
522- if output. ct_eq ( & hash) . into ( ) {
523- Ok ( ( ) )
524- } else {
525- Err ( CheckError :: HashMismatch )
526- }
527- }
528-
529- /// Generate a random salt that is 16-bytes long.
530- #[ cfg( feature = "simple" ) ]
531- fn random_salt ( ) -> String {
532- // Create buffer containing raw bytes to encode as Base64
533- let mut buf = [ 0u8 ; ( SALT_MAX_LEN * 3 ) . div_ceil ( 4 ) ] ;
534- OsRng . unwrap_err ( ) . fill_bytes ( & mut buf) ;
535- Base64ShaCrypt :: encode_string ( & buf)
536- }
537-
538308fn produce_byte_seq ( len : usize , fill_from : & [ u8 ] ) -> Vec < u8 > {
539309 let bs = fill_from. len ( ) ;
540310 let mut seq: Vec < u8 > = vec ! [ 0 ; len] ;
0 commit comments