plan: libxcrypt-compatible sha512-crypt (16-char salt); KAT test

This commit is contained in:
grabowski committed 2026-10-23 10:19:19 +07:00
1 parent 07ff88c18d
commit bc600c27a0
1 file changed
+19 -3
+19 -3
View File
@@ -219,11 +219,19 @@ pub fn classify_secret(s: &str) -> SecretKind {
SecretKind::Plaintext(s.to_string()) SecretKind::Plaintext(s.to_string())
} }
/// SHA512-crypt hash (what `mkpasswd -m sha-512` produces). /// SHA512-crypt hash compatible with glibc/libxcrypt (Dovecot, `mkpasswd -m sha-512`).
///
/// The salt is 12 random bytes = exactly 16 salt characters. sha-crypt 0.6's
/// default (16 bytes -> 22 characters) hashes over all 22 characters, while
/// libxcrypt only uses the first 16, so such hashes never verify in Dovecot
/// (caught by the e2e test).
pub fn sha512_crypt(password: &str) -> Result<String> { pub fn sha512_crypt(password: &str) -> Result<String> {
use rand::RngExt;
use sha_crypt::{PasswordHasher, ShaCrypt}; use sha_crypt::{PasswordHasher, ShaCrypt};
let h = let salt: [u8; 12] = rand::rng().random();
ShaCrypt::default().hash_password(password.as_bytes()).map_err(|e| anyhow::anyhow!("hashing password: {e}"))?; let h = ShaCrypt::default()
.hash_password_with_salt(password.as_bytes(), &salt)
.map_err(|e| anyhow::anyhow!("hashing password: {e}"))?;
Ok(h.as_str().to_string()) Ok(h.as_str().to_string())
} }
@@ -487,6 +495,14 @@ mod tests {
assert!(h.starts_with("$6$")); assert!(h.starts_with("$6$"));
assert!(verify_sha_crypt("correct horse", &h)); assert!(verify_sha_crypt("correct horse", &h));
assert!(!verify_sha_crypt("wrong", &h)); assert!(!verify_sha_crypt("wrong", &h));
// glibc/libxcrypt only use 16 salt characters; longer salts never verify there.
let salt = h.trim_start_matches("$6$").split('$').find(|f| !f.starts_with("rounds=")).unwrap();
assert_eq!(salt.len(), 16, "{h}");
// Algorithm matches glibc (Drepper's test vector).
assert!(verify_sha_crypt(
"Hello world!",
"$6$saltstring$svn8UoSVapNtMuq1ukKS4tPQd8iKwSMHWjl/O817G3uBnIFNjnQJuesI68u4OTLiBFdcbYEdFCoEOfaS35inz1"
));
} }
#[test] #[test]