Skip to content

Commit b8f1d16

Browse files
authored
scrypt: add MCF support (#781)
Information about the scrypt MCF format comes largely from libxcrypt: https://github.com/besser82/libxcrypt/blob/a74a677/lib/crypt-scrypt.c The format is somewhat like yescrypt in that it uses a special binary encoding of the parameters which is then serialized as little endian crypt-like Base64 (i.e. `./0123456789ABC..XYZZabc..xyz`). However, scrypt doesn't place the parameters in their own `$`-delimited field like yescrypt, but instead concatenates them to the salt and shoves the combined params + salt into a single `$`-delimited field. Also, where yescrypt uses a variable-width integer encoding, scrypt uses a fixed-width encoding.
1 parent 486547d commit b8f1d16

File tree

11 files changed

+369
-66
lines changed

11 files changed

+369
-66
lines changed

.readme/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,4 @@ publish = false
99
password-hash = "0.6.0-rc.3"
1010
argon2 = { path = "../argon2" }
1111
pbkdf2 = { path = "../pbkdf2", features = ["password-hash"] }
12-
scrypt = { path = "../scrypt" }
12+
scrypt = { path = "../scrypt", features = ["phc"] }

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

password-auth/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ password-hash = { version = "0.6.0-rc.6", features = ["alloc", "getrandom", "phc
2323
# optional dependencies
2424
argon2 = { version = "0.6.0-rc.5", optional = true, default-features = false, features = ["alloc", "password-hash"] }
2525
pbkdf2 = { version = "0.13.0-rc.5", optional = true, default-features = false, features = ["password-hash"] }
26-
scrypt = { version = "0.12.0-rc.6", optional = true, default-features = false, features = ["password-hash"] }
26+
scrypt = { version = "0.12.0-rc.6", optional = true, default-features = false, features = ["phc"] }
2727

2828
[features]
2929
default = ["argon2", "std"]

password-auth/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ pub fn is_hash_obsolete(hash: &str) -> Result<bool, ParseError> {
118118
|| hash.params != default_params_string::<argon2::Params>());
119119

120120
#[cfg(feature = "scrypt")]
121-
return Ok(hash.algorithm != scrypt::ALG_ID
121+
return Ok(hash.algorithm != scrypt::phc::ALG_ID
122122
|| hash.params != default_params_string::<scrypt::Params>());
123123

124124
#[cfg(feature = "pbkdf2")]

scrypt/Cargo.toml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,16 @@ sha2 = { version = "0.11.0-rc.3", default-features = false }
2020
rayon = { version = "1.11", optional = true }
2121

2222
# optional dependencies
23-
password-hash = { version = "0.6.0-rc.6", optional = true, default-features = false, features = ["phc"] }
23+
mcf = { version = "0.6.0-rc.0", optional = true }
24+
password-hash = { version = "0.6.0-rc.6", optional = true, default-features = false }
25+
subtle = { version = "2", optional = true, default-features = false }
2426

2527
[features]
26-
default = ["password-hash", "rayon"]
2728
alloc = ["password-hash?/alloc"]
2829

2930
getrandom = ["password-hash", "password-hash/getrandom"]
30-
password-hash = ["dep:password-hash"]
31+
mcf = ["alloc", "password-hash", "dep:mcf", "dep:subtle"]
32+
phc = ["password-hash/phc"]
3133
rand_core = ["password-hash/rand_core"]
3234
rayon = ["dep:rayon"]
3335

scrypt/src/lib.rs

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,11 @@
2727
//! let password = b"hunter42"; // Bad password; don't actually use!
2828
//!
2929
//! // Hash password to PHC string ($scrypt$...)
30-
//! let password_hash = Scrypt.hash_password(password)?.to_string();
30+
//! let hash: PasswordHash = Scrypt.hash_password(password)?;
31+
//! let hash_string = hash.to_string();
3132
//!
3233
//! // Verify password against PHC string
33-
//! let parsed_hash = PasswordHash::new(&password_hash)?;
34+
//! let parsed_hash = PasswordHash::new(&hash_string)?;
3435
//! assert!(Scrypt.verify_password(password, &parsed_hash).is_ok());
3536
//! # Ok(())
3637
//! # }
@@ -58,16 +59,18 @@ pub mod errors;
5859
mod params;
5960
mod romix;
6061

61-
#[cfg(feature = "password-hash")]
62-
mod phc;
62+
#[cfg(feature = "mcf")]
63+
pub mod mcf;
64+
#[cfg(feature = "phc")]
65+
pub mod phc;
6366

6467
pub use crate::params::Params;
6568

6669
#[cfg(feature = "password-hash")]
6770
pub use password_hash;
6871

69-
#[cfg(feature = "password-hash")]
70-
pub use crate::phc::{ALG_ID, Scrypt};
72+
#[cfg(all(doc, feature = "password-hash"))]
73+
use password_hash::{CustomizedPasswordHasher, PasswordHasher, PasswordVerifier};
7174

7275
/// The scrypt key derivation function.
7376
///
@@ -139,3 +142,14 @@ fn romix_parallel(nr128: usize, r128: usize, n: usize, b: &mut [u8]) {
139142
romix::scrypt_ro_mix(chunk, &mut v, &mut t, n);
140143
});
141144
}
145+
146+
/// scrypt password hashing type which can produce and verify strings in either the Password Hashing
147+
/// Competition (PHC) string format which begin with `$scrypt$`, or in Modular Crypt Format (MCF)
148+
/// which begin with `$7$`.
149+
///
150+
/// This is a ZST which impls traits from the [`password-hash`][`password_hash`] crate, notably
151+
/// the [`PasswordHasher`], [`PasswordVerifier`], and [`CustomizedPasswordHasher`] traits.
152+
///
153+
/// See the toplevel documentation for a code example.
154+
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
155+
pub struct Scrypt;

0 commit comments

Comments
 (0)