#![cfg_attr(docsrs, doc(cfg(feature = "safe_api")))]
pub use super::hltypes::Password;
use super::hltypes::Salt;
use crate::{
errors::UnknownCryptoError,
hazardous::kdf::argon2i::{self, LANES, MIN_MEMORY},
};
use ct_codecs::{Base64NoPadding, Decoder, Encoder};
#[cfg(feature = "serde")]
use serde::{
de::{self, Deserialize, Deserializer},
ser::{Serialize, Serializer},
};
pub const SALT_LENGTH: usize = 16;
pub const PWHASH_LENGTH: usize = 32;
pub(crate) const MIN_ITERATIONS: u32 = 3;
pub struct PasswordHash {
encoded_password_hash: String,
password_hash: Vec<u8>,
salt: Salt,
iterations: u32,
memory: u32,
}
#[allow(clippy::len_without_is_empty)]
impl PasswordHash {
pub const MIN_ENCODED_LEN: usize = 92;
pub const MAX_ENCODED_LEN: usize = 110;
fn parse_decimal_value(value: &str) -> Result<u32, UnknownCryptoError> {
if value.len() > 1 && value.starts_with('0') {
return Err(UnknownCryptoError);
}
Ok(value.parse::<u32>()?)
}
fn encode(
password_hash: &[u8],
salt: &[u8],
iterations: u32,
memory: u32,
) -> Result<String, UnknownCryptoError> {
Ok(format!(
"$argon2i$v=19$m={},t={},p=1${}${}",
memory,
iterations,
Base64NoPadding::encode_to_string(salt)?,
Base64NoPadding::encode_to_string(password_hash)?,
))
}
#[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."]
pub fn from_slice(
password_hash: &[u8],
salt: &[u8],
iterations: u32,
memory: u32,
) -> Result<Self, UnknownCryptoError> {
if password_hash.len() != PWHASH_LENGTH {
return Err(UnknownCryptoError);
}
if salt.len() != SALT_LENGTH {
return Err(UnknownCryptoError);
}
if iterations < MIN_ITERATIONS {
return Err(UnknownCryptoError);
}
if memory < MIN_MEMORY {
return Err(UnknownCryptoError);
}
let encoded_password_hash = Self::encode(password_hash, salt, iterations, memory)?;
Ok(Self {
encoded_password_hash,
password_hash: password_hash.into(),
salt: Salt::from_slice(salt)?,
iterations,
memory,
})
}
#[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."]
pub fn from_encoded(password_hash: &str) -> Result<Self, UnknownCryptoError> {
if password_hash.len() > Self::MAX_ENCODED_LEN
|| password_hash.len() < Self::MIN_ENCODED_LEN
{
return Err(UnknownCryptoError);
}
if password_hash.contains(' ') {
return Err(UnknownCryptoError);
}
let parts_split = password_hash.split('$').collect::<Vec<&str>>();
if parts_split.len() != 6 {
return Err(UnknownCryptoError);
}
let mut parts = parts_split.into_iter();
if parts.next() != Some("") {
return Err(UnknownCryptoError);
}
if parts.next() != Some("argon2i") {
return Err(UnknownCryptoError);
}
if parts.next() != Some("v=19") {
return Err(UnknownCryptoError);
}
let param_parts_split = parts
.next()
.unwrap()
.split(['=', ','])
.collect::<Vec<&str>>();
if param_parts_split.len() != 6 {
return Err(UnknownCryptoError);
}
let mut param_parts = param_parts_split.into_iter();
if param_parts.next() != Some("m") {
return Err(UnknownCryptoError);
}
let memory = Self::parse_decimal_value(param_parts.next().unwrap())?;
if memory < MIN_MEMORY {
return Err(UnknownCryptoError);
}
if param_parts.next() != Some("t") {
return Err(UnknownCryptoError);
}
let iterations = Self::parse_decimal_value(param_parts.next().unwrap())?;
if iterations < MIN_ITERATIONS {
return Err(UnknownCryptoError);
}
if param_parts.next() != Some("p") {
return Err(UnknownCryptoError);
}
let lanes = Self::parse_decimal_value(param_parts.next().unwrap())?;
if lanes != LANES {
return Err(UnknownCryptoError);
}
let salt = Base64NoPadding::decode_to_vec(parts.next().unwrap(), None)?;
if salt.len() != SALT_LENGTH {
return Err(UnknownCryptoError);
}
let password_hash_raw = Base64NoPadding::decode_to_vec(parts.next().unwrap(), None)?;
if password_hash_raw.len() != PWHASH_LENGTH {
return Err(UnknownCryptoError);
}
Ok(Self {
encoded_password_hash: password_hash.into(),
password_hash: password_hash_raw,
salt: Salt::from_slice(&salt)?,
iterations,
memory,
})
}
#[inline]
pub fn unprotected_as_encoded(&self) -> &str {
self.encoded_password_hash.as_ref()
}
#[inline]
pub fn unprotected_as_bytes(&self) -> &[u8] {
self.password_hash.as_ref()
}
#[inline]
pub fn len(&self) -> usize {
self.password_hash.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
debug_assert_eq!(self.encoded_password_hash.is_empty(), self.password_hash.is_empty(),
"Both the encoded password hash and the raw hash must be non-empty or empty at the same time.");
self.password_hash.is_empty()
}
}
impl core::fmt::Debug for PasswordHash {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(
f,
"PasswordHash {{ encoded_password_hash: [***OMITTED***], password_hash: [***OMITTED***], iterations: \
{:?}, memory: {:?} }}",
self.iterations, self.memory
)
}
}
impl_ct_partialeq_trait!(PasswordHash, unprotected_as_bytes);
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
impl Serialize for PasswordHash {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let encoded_string = self.unprotected_as_encoded();
serializer.serialize_str(encoded_string)
}
}
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
impl<'de> Deserialize<'de> for PasswordHash {
fn deserialize<D>(deserializer: D) -> Result<PasswordHash, D::Error>
where
D: Deserializer<'de>,
{
let encoded_str = String::deserialize(deserializer)?;
PasswordHash::from_encoded(&encoded_str).map_err(de::Error::custom)
}
}
#[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."]
pub fn hash_password(
password: &Password,
iterations: u32,
memory: u32,
) -> Result<PasswordHash, UnknownCryptoError> {
if iterations < MIN_ITERATIONS {
return Err(UnknownCryptoError);
}
let salt = Salt::generate(SALT_LENGTH).unwrap();
let mut buffer = zeroize_wrap!([0u8; PWHASH_LENGTH]);
argon2i::derive_key(
password.unprotected_as_bytes(),
salt.as_ref(),
iterations,
memory,
None,
None,
buffer.as_mut(),
)?;
PasswordHash::from_slice(buffer.as_ref(), salt.as_ref(), iterations, memory)
}
#[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."]
pub fn hash_password_verify(
expected: &PasswordHash,
password: &Password,
) -> Result<(), UnknownCryptoError> {
let mut buffer = zeroize_wrap!([0u8; PWHASH_LENGTH]);
argon2i::verify(
expected.unprotected_as_bytes(),
password.unprotected_as_bytes(),
expected.salt.as_ref(),
expected.iterations,
expected.memory,
None,
None,
buffer.as_mut(),
)
}
#[cfg(test)]
mod public {
use super::*;
#[test]
#[cfg(feature = "safe_api")]
fn test_debug_impl() {
let valid = "$argon2i$v=19$m=65536,t=3,p=1$cHBwcHBwcHBwcHBwcHBwcA$MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA";
let password_hash = PasswordHash::from_encoded(valid).unwrap();
let debug = format!("{password_hash:?}");
let expected = "PasswordHash { encoded_password_hash: [***OMITTED***], password_hash: [***OMITTED***], iterations: 3, memory: 65536 }";
assert_eq!(debug, expected);
}
#[cfg(feature = "serde")]
mod test_serde_impls {
use super::*;
#[test]
fn test_valid_deserialization() {
let encoded_hash = "$argon2i$v=19$m=65536,t=3,p=1$c29tZXNhbHRzb21lc2FsdA$fRsRY9PAt5H+qAKuXRzL0/6JbFShsCd62W5aHzESk/c";
let expected = PasswordHash::from_encoded(encoded_hash).unwrap();
let deserialized: PasswordHash =
serde_json::from_str(format!("\"{encoded_hash}\"").as_str()).unwrap();
assert_eq!(deserialized, expected);
}
#[test]
fn test_valid_serialization() {
let encoded_hash = "$argon2i$v=19$m=65536,t=3,p=1$c29tZXNhbHRzb21lc2FsdA$fRsRY9PAt5H+qAKuXRzL0/6JbFShsCd62W5aHzESk/c";
let hash = PasswordHash::from_encoded(encoded_hash).unwrap();
let serialized: String = serde_json::to_string(&hash).unwrap();
assert_eq!(serialized, format!("\"{encoded_hash}\""));
}
}
mod test_encoding_from_ref {
use super::*;
use hex;
#[test]
fn test_encoding_and_verify_1() {
let password = Password::from_slice(b"password").unwrap();
let raw_hash =
hex::decode("7d1b1163d3c0b791fea802ae5d1ccbd3fe896c54a1b0277ad96e5a1f311293f7")
.unwrap();
let encoded_hash = "$argon2i$v=19$m=65536,t=3,p=1$c29tZXNhbHRzb21lc2FsdA$fRsRY9PAt5H+qAKuXRzL0/6JbFShsCd62W5aHzESk/c";
let expected = PasswordHash::from_encoded(encoded_hash).unwrap();
assert_eq!(expected.unprotected_as_bytes(), &raw_hash[..]);
assert!(hash_password_verify(&expected, &password).is_ok());
}
#[test]
fn test_encoding_and_verify_2() {
let password = Password::from_slice(b"passwordPASSWORDPassword").unwrap();
let raw_hash =
hex::decode("ed4b0fd657e165f9ffe90f66ff315fbec878e629f03b2d6468d4b17a50c796aa")
.unwrap();
let encoded_hash = "$argon2i$v=19$m=65536,t=3,p=1$c29tZXNhbHRzb21lc2FsdA$7UsP1lfhZfn/6Q9m/zFfvsh45inwOy1kaNSxelDHlqo";
let expected = PasswordHash::from_encoded(encoded_hash).unwrap();
assert_eq!(expected.unprotected_as_bytes(), &raw_hash[..]);
assert!(hash_password_verify(&expected, &password).is_ok());
}
#[test]
fn test_encoding_and_verify_3() {
let password = Password::from_slice(b"passwordPASSWORDPassword").unwrap();
let raw_hash =
hex::decode("fa9ea96fecd0998251d698c1303edda4df3889a39bfa87cd5e7b8656ef61b510")
.unwrap();
let encoded_hash = "$argon2i$v=19$m=65536,t=3,p=1$U29tZVNhbHRTb21lU2FsdA$+p6pb+zQmYJR1pjBMD7dpN84iaOb+ofNXnuGVu9htRA";
let expected = PasswordHash::from_encoded(encoded_hash).unwrap();
assert_eq!(expected.unprotected_as_bytes(), &raw_hash[..]);
assert!(hash_password_verify(&expected, &password).is_ok());
}
#[test]
fn test_encoding_and_verify_4() {
let password = Password::from_slice(b"passwordPASSWORDPassword").unwrap();
let raw_hash =
hex::decode("fb3e0cdf7b10970bf6711c151861851566006f8986c9109ba2cdd5d98f9ca9d7")
.unwrap();
let encoded_hash = "$argon2i$v=19$m=256,t=3,p=1$c29tZXNhbHRzb21lc2FsdA$+z4M33sQlwv2cRwVGGGFFWYAb4mGyRCbos3V2Y+cqdc";
let expected = PasswordHash::from_encoded(encoded_hash).unwrap();
assert_eq!(expected.unprotected_as_bytes(), &raw_hash[..]);
assert!(hash_password_verify(&expected, &password).is_ok());
}
#[test]
fn test_encoding_and_verify_5() {
let password = Password::from_slice(b"passwordPASSWORDPassword").unwrap();
let raw_hash =
hex::decode("9b134c4c1c34e66170d1088c18be3a8e0f4a1837d4c069703ce62f85248b1e8f")
.unwrap();
let encoded_hash = "$argon2i$v=19$m=256,t=4,p=1$c29tZXNhbHRzb21lc2FsdA$mxNMTBw05mFw0QiMGL46jg9KGDfUwGlwPOYvhSSLHo8";
let expected = PasswordHash::from_encoded(encoded_hash).unwrap();
assert_eq!(expected.unprotected_as_bytes(), &raw_hash[..]);
assert!(hash_password_verify(&expected, &password).is_ok());
}
}
mod test_password_hash {
use super::*;
#[test]
fn test_password_hash_eq() {
let password_hash =
PasswordHash::from_slice(&[0u8; 32], &[0u8; 16], 3, 1 << 16).unwrap();
assert_eq!(password_hash.len(), 32);
assert_eq!(password_hash.unprotected_as_bytes(), &[0u8; 32]);
let password_hash_again =
PasswordHash::from_encoded(password_hash.unprotected_as_encoded()).unwrap();
assert_eq!(password_hash, password_hash_again);
}
#[test]
fn test_password_hash_ne() {
let password_hash =
PasswordHash::from_slice(&[0u8; 32], &[0u8; 16], 3, 1 << 16).unwrap();
assert_eq!(password_hash.len(), 32);
assert_eq!(password_hash.unprotected_as_bytes(), &[0u8; 32]);
let password_hash_again =
PasswordHash::from_slice(&[1u8; 32], &[0u8; 16], 3, 1 << 16).unwrap();
assert_ne!(password_hash, password_hash_again);
}
#[test]
fn test_valid_encoded_password() {
let valid = "$argon2i$v=19$m=65536,t=3,p=1$cHBwcHBwcHBwcHBwcHBwcA$MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA";
assert!(PasswordHash::from_encoded(valid).is_ok());
}
#[test]
fn test_bad_encoding_missing_dollar() {
let first_missing = "argon2i$v=19$m=65536,t=3,p=1$cHBwcHBwcHBwcHBwcHBwcA$MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA";
let second_missing = "$argon2iv=19$m=65536,t=3,p=1$cHBwcHBwcHBwcHBwcHBwcA$MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA";
let third_missing = "$argon2i$v=19m=65536,t=3,p=1$cHBwcHBwcHBwcHBwcHBwcA$MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA";
let fourth_missing = "$argon2i$v=19$m=65536,t=3,p=1cHBwcHBwcHBwcHBwcHBwcA$MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA";
let fifth_missing = "$argon2i$v=19$m=65536,t=3,p=1$cHBwcHBwcHBwcHBwcHBwcAMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA";
assert!(PasswordHash::from_encoded(first_missing).is_err());
assert!(PasswordHash::from_encoded(second_missing).is_err());
assert!(PasswordHash::from_encoded(third_missing).is_err());
assert!(PasswordHash::from_encoded(fourth_missing).is_err());
assert!(PasswordHash::from_encoded(fifth_missing).is_err());
}
#[test]
fn test_bad_encoding_missing_comma() {
let first_missing = "$argon2i$v=19$m=65536t=3,p=1$cHBwcHBwcHBwcHBwcHBwcA$MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA";
let second_missing = "$argon2i$v=19$m=65536,t=3p=1$cHBwcHBwcHBwcHBwcHBwcA$MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA";
assert!(PasswordHash::from_encoded(first_missing).is_err());
assert!(PasswordHash::from_encoded(second_missing).is_err());
}
#[test]
fn test_bad_encoding_missing_equals() {
let first_missing = "$argon2i$v19$m=65536,t=3,p=1$cHBwcHBwcHBwcHBwcHBwcA$MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA";
let second_missing = "$argon2$iv=19$m65536,t=3,p=1$cHBwcHBwcHBwcHBwcHBwcA$MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA";
let third_missing = "$argon2i$v=19$m=65536,t3,p=1$cHBwcHBwcHBwcHBwcHBwcA$MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA";
let fourth_missing = "$argon2i$v=19$m=65536,t=3,p1$cHBwcHBwcHBwcHBwcHBwcA$MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA";
assert!(PasswordHash::from_encoded(first_missing).is_err());
assert!(PasswordHash::from_encoded(second_missing).is_err());
assert!(PasswordHash::from_encoded(third_missing).is_err());
assert!(PasswordHash::from_encoded(fourth_missing).is_err());
}
#[test]
fn test_bad_encoding_whitespace() {
let first = "$argon2i$v=19$m=65536,t=3, p=1$cHBwcHBwcHBwcHBwcHBwcA$MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA";
let second = " $argon2i$v=19$m=65536,t=3,p=1$cHBwcHBwcHBwcHBwcHBwcA$MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA";
let third = "$argon2i$v=19$m=65536,t=3,p=1$cHBwcHBwcHBwcHBwcHBwcA$MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA ";
assert!(PasswordHash::from_encoded(first).is_err());
assert!(PasswordHash::from_encoded(second).is_err());
assert!(PasswordHash::from_encoded(third).is_err());
}
#[test]
fn test_bad_encoding_invalid_threads() {
let one = "$argon2i$v=19$m=65536,t=3,p=1$cHBwcHBwcHBwcHBwcHBwcA$MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA";
let zero = "$argon2i$v=19$m=65536,t=3,p=0$cHBwcHBwcHBwcHBwcHBwcA$MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA";
let two = "$argon2i$v=19$m=65536,t=3,p=2$cHBwcHBwcHBwcHBwcHBwcA$MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA";
assert!(PasswordHash::from_encoded(one).is_ok());
assert!(PasswordHash::from_encoded(zero).is_err());
assert!(PasswordHash::from_encoded(two).is_err());
}
#[test]
fn test_bad_encoding_invalid_memory() {
let exact_min = "$argon2i$v=19$m=8,t=3,p=1$cHBwcHBwcHBwcHBwcHBwcA$MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA";
let less = "$argon2i$v=19$m=7,t=3,p=1$cHBwcHBwcHBwcHBwcHBwcA$MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA";
let u32_overflow = format!("$argon2i$v=19$m={},t=3,p=1$cHBwcHBwcHBwcHBwcHBwcA$MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA", u64::MAX);
assert!(PasswordHash::from_encoded(exact_min).is_ok());
assert!(PasswordHash::from_encoded(less).is_err());
assert!(PasswordHash::from_encoded(&u32_overflow).is_err());
}
#[test]
fn test_bad_encoding_invalid_iterations() {
let exact_min = "$argon2i$v=19$m=65536,t=3,p=1$cHBwcHBwcHBwcHBwcHBwcA$MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA";
let less = "$argon2i$v=19$m=65536,t=2,p=1$cHBwcHBwcHBwcHBwcHBwcA$MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA";
let u32_overflow = format!("$argon2i$v=19$m=65536,t={},p=1$cHBwcHBwcHBwcHBwcHBwcA$MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA", u64::MAX);
assert!(PasswordHash::from_encoded(exact_min).is_ok());
assert!(PasswordHash::from_encoded(less).is_err());
assert!(PasswordHash::from_encoded(&u32_overflow).is_err());
}
#[test]
fn test_bad_encoding_invalid_algo() {
let argon2id = "$argon2id$v=19$m=65536,t=3,p=1$cHBwcHBwcHBwcHBwcHBwcA$MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA";
let argon2d = "$argon2d$v=19$m=65536,t=3,p=1$cHBwcHBwcHBwcHBwcHBwcA$MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA";
let nothing = "$$v=19$m=65536,t=3,p=1$cHBwcHBwcHBwcHBwcHBwcA$MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA";
assert!(PasswordHash::from_encoded(argon2d).is_err());
assert!(PasswordHash::from_encoded(argon2id).is_err());
assert!(PasswordHash::from_encoded(nothing).is_err());
}
#[test]
fn test_bad_encoding_invalid_version() {
let v13 = "$argon2i$v=13$m=65536,t=3,p=1$cHBwcHBwcHBwcHBwcHBwcA$MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA";
let v0 = "$argon2i$v=0$m=65536,t=3,p=1$cHBwcHBwcHBwcHBwcHBwcA$MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA";
let nothing = "$argon2i$v=$m=65536,t=3,p=1$cHBwcHBwcHBwcHBwcHBwcA$MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA";
assert!(PasswordHash::from_encoded(v13).is_err());
assert!(PasswordHash::from_encoded(v0).is_err());
assert!(PasswordHash::from_encoded(nothing).is_err());
}
#[test]
fn test_bad_encoding_invalid_order() {
let version_first = "$v=19$argon2i$m=65536,t=3,p=1$cHBwcHBwcHBwcHBwcHBwcA$MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA";
let t_before_m = "$argon2i$v=19$t=3,m=65536,p=1$cHBwcHBwcHBwcHBwcHBwcA$MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA";
let p_before_t = "$argon2i$v=19$m=65536,p=1,t=3$cHBwcHBwcHBwcHBwcHBwcA$MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA";
let p_before_m = "$argon2i$v=19$p=1,m=65536,t=3$cHBwcHBwcHBwcHBwcHBwcA$MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA";
let pass_before_salt = "$argon2i$v=19$m=65536,t=3,p=1$MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA$cHBwcHBwcHBwcHBwcHBwcA";
let salt_first = "$cHBwcHBwcHBwcHBwcHBwcA$argon2i$v=19$m=65536,t=3,p=1$MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA";
let pass_first = "$MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA$argon2i$v=19$m=65536,t=3,p=1$cHBwcHBwcHBwcHBwcHBwcA";
assert!(PasswordHash::from_encoded(version_first).is_err());
assert!(PasswordHash::from_encoded(t_before_m).is_err());
assert!(PasswordHash::from_encoded(p_before_t).is_err());
assert!(PasswordHash::from_encoded(p_before_m).is_err());
assert!(PasswordHash::from_encoded(pass_before_salt).is_err());
assert!(PasswordHash::from_encoded(salt_first).is_err());
assert!(PasswordHash::from_encoded(pass_first).is_err());
}
#[test]
fn test_bad_encoding_invalid_salt() {
let exact = "$argon2i$v=19$m=65536,t=3,p=1$cHBwcHBwcHBwcHBwcHBwcA$MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA";
let nothing =
"$argon2i$v=19$m=65536,t=3,p=1$$MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA";
let above = "$argon2i$v=19$m=65536,t=3,p=1$cHBwcHBwcHBwcHBwcHBwcAA$MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA";
assert!(PasswordHash::from_encoded(exact).is_ok());
assert!(PasswordHash::from_encoded(nothing).is_err());
assert!(PasswordHash::from_encoded(above).is_err());
}
#[test]
fn test_bad_encoding_invalid_password() {
let exact = "$argon2i$v=19$m=65536,t=3,p=1$cHBwcHBwcHBwcHBwcHBwcA$MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA";
let nothing = "$argon2i$v=19$m=65536,t=3,p=1$cHBwcHBwcHBwcHBwcHBwcA$";
let above = "$argon2i$v=19$m=65536,t=3,p=1$cHBwcHBwcHBwcHBwcHBwcA$MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAA";
assert!(PasswordHash::from_encoded(exact).is_ok());
assert!(PasswordHash::from_encoded(nothing).is_err());
assert!(PasswordHash::from_encoded(above).is_err());
}
#[test]
fn test_bad_encoding_bad_parsing_integers() {
let j_instead_of_mem = "$argon2i$v=19$m=j,t=3,p=1$cHBwcHBwcHBwcHBwcHBwcA$MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA";
assert!(PasswordHash::from_encoded(j_instead_of_mem).is_err());
}
#[test]
fn test_bad_encoding_first_not_empty() {
let non_empty_first = "apples$argon2i$v=19$m=4096,t=3,p=1$cHBwcHBwcHBwcHBwcHBwcA$MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA";
assert!(PasswordHash::from_encoded(non_empty_first).is_err());
}
#[test]
fn test_bad_encoding_bad_p() {
let p_is_j = "$argon2i$v=19$m=4096,t=3,j=1$cHBwcHBwcHBwcHBwcHBwcA$MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA";
let p_gone = "$argon2i$v=19$m=4096,t=3,=1$cHBwcHBwcHBwcHBwcHBwcA$MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA";
assert!(PasswordHash::from_encoded(p_is_j).is_err());
assert!(PasswordHash::from_encoded(p_gone).is_err());
}
#[test]
fn test_decimal_value_reject_leading_zeroes() {
let valid = "$argon2i$v=19$m=65536,t=3,p=1$cHBwcHBwcHBwcHBwcHBwcA$MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA";
let invalid0 = "$argon2i$v=019$m=65536,t=3,p=1$cHBwcHBwcHBwcHBwcHBwcA$MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA";
let invalid1 = "$argon2i$v=19$m=065536,t=3,p=1$cHBwcHBwcHBwcHBwcHBwcA$MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA";
let invalid2 = "$argon2i$v=19$m=65536,t=03,p=1$cHBwcHBwcHBwcHBwcHBwcA$MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA";
let invalid3 = "$argon2i$v=19$m=65536,t=3,p=01$cHBwcHBwcHBwcHBwcHBwcA$MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA";
assert!(PasswordHash::from_encoded(valid).is_ok());
assert!(PasswordHash::from_encoded(invalid0).is_err());
assert!(PasswordHash::from_encoded(invalid1).is_err());
assert!(PasswordHash::from_encoded(invalid2).is_err());
assert!(PasswordHash::from_encoded(invalid3).is_err());
}
#[test]
fn test_bounds_max_min_encoded_len() {
let minimum = "$argon2i$v=19$m=8,t=3,p=1$cHBwcHBwcHBwcHBwcHBwcA$MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA";
assert_eq!(minimum.len(), PasswordHash::MIN_ENCODED_LEN);
let maximum = "$argon2i$v=19$m=1111111111,t=1111111111,p=1$cHBwcHBwcHBwcHBwcHBwcA$MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA";
assert_eq!(maximum.len(), PasswordHash::MAX_ENCODED_LEN);
let less = "$argon2i$v=19$m=8,t=3,p=1$cHBwcHBwcHBwcHBwcHBwc$MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA";
assert_eq!(less.len(), PasswordHash::MIN_ENCODED_LEN - 1);
let more = "$argon2i$v=19$m=1111111111,t=1111111111,p=1$cHBwcHBwcHBwcHBwcHBwcAA$MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA";
assert_eq!(more.len(), PasswordHash::MAX_ENCODED_LEN + 1);
assert!(PasswordHash::from_encoded(minimum).is_ok());
assert!(PasswordHash::from_encoded(maximum).is_ok());
assert!(PasswordHash::from_encoded(less).is_err());
assert!(PasswordHash::from_encoded(more).is_err());
}
#[test]
fn test_from_slice_password() {
assert!(PasswordHash::from_slice(&[0u8; 31], &[0u8; 16], 3, 1 << 16).is_err());
assert!(PasswordHash::from_slice(&[0u8; 32], &[0u8; 16], 3, 1 << 16).is_ok());
assert!(PasswordHash::from_slice(&[0u8; 33], &[0u8; 16], 3, 1 << 16).is_err());
}
#[test]
fn test_from_slice_salt() {
assert!(PasswordHash::from_slice(&[0u8; 32], &[0u8; 15], 3, 1 << 16).is_err());
assert!(PasswordHash::from_slice(&[0u8; 32], &[0u8; 16], 3, 1 << 16).is_ok());
assert!(PasswordHash::from_slice(&[0u8; 32], &[0u8; 17], 3, 1 << 16).is_err());
}
#[test]
fn test_from_slice_mem() {
assert!(PasswordHash::from_slice(&[0u8; 32], &[0u8; 16], 3, 7).is_err());
assert!(PasswordHash::from_slice(&[0u8; 32], &[0u8; 16], 3, 8).is_ok());
assert!(PasswordHash::from_slice(&[0u8; 32], &[0u8; 16], 3, 9).is_ok());
}
#[test]
fn test_from_slice_bad_iter() {
assert!(PasswordHash::from_slice(&[0u8; 32], &[0u8; 16], 2, 1 << 16).is_err());
assert!(PasswordHash::from_slice(&[0u8; 32], &[0u8; 16], 3, 1 << 16).is_ok());
assert!(PasswordHash::from_slice(&[0u8; 32], &[0u8; 16], 4, 1 << 16).is_ok());
}
#[quickcheck]
#[cfg(feature = "safe_api")]
fn prop_always_produce_valid_encoding(
password: Vec<u8>,
salt: Vec<u8>,
iterations: u32,
memory: u32,
) -> bool {
if let Ok(res) = PasswordHash::from_slice(&password[..], &salt[..], iterations, memory)
{
assert!(PasswordHash::from_encoded(res.unprotected_as_encoded()).is_ok());
assert!(!res.is_empty());
}
true
}
}
mod test_pwhash_and_verify {
use super::*;
#[test]
fn test_argon2i_verify() {
let password = Password::from_slice(&[0u8; 64]).unwrap();
let dk = hash_password(&password, 3, 4096).unwrap();
assert!(hash_password_verify(&dk, &password).is_ok());
assert!(!dk.is_empty());
}
#[test]
fn test_argon2i_verify_err_modified_password() {
let password = Password::from_slice(&[0u8; 64]).unwrap();
let dk = hash_password(&password, 3, 4096).unwrap();
let mut pwd_mod = dk.unprotected_as_bytes().to_vec();
pwd_mod[0..32].copy_from_slice(&[0u8; 32]);
let modified = PasswordHash::from_slice(&pwd_mod, dk.salt.as_ref(), 3, 4096).unwrap();
assert!(hash_password_verify(&modified, &password).is_err());
}
#[test]
fn test_argon2i_verify_err_modified_memory() {
let password = Password::from_slice(&[0u8; 64]).unwrap();
let dk = hash_password(&password, 3, 4096).unwrap();
let encoded = dk.unprotected_as_encoded();
let mut modified = encoded.to_string();
let memory_offset = modified.find("$m=4096").unwrap();
modified.replace_range(memory_offset..memory_offset + 7, "$m=2048");
let modified = PasswordHash::from_encoded(&modified).unwrap();
assert!(hash_password_verify(&modified, &password).is_err());
}
#[test]
fn test_argon2i_verify_err_modified_iterations() {
let password = Password::from_slice(&[0u8; 64]).unwrap();
let dk = hash_password(&password, 3, 4096).unwrap();
let encoded = dk.unprotected_as_encoded();
let mut modified = encoded.to_string();
let iterations_offset = modified.find(",t=3").unwrap();
modified.replace_range(iterations_offset..iterations_offset + 4, ",t=4");
let modified = PasswordHash::from_encoded(&modified).unwrap();
assert!(hash_password_verify(&modified, &password).is_err());
}
#[test]
fn test_argon2i_verify_err_modified_memory_and_iterations() {
let password = Password::from_slice(&[0u8; 64]).unwrap();
let dk = hash_password(&password, 3, 4096).unwrap();
let encoded = dk.unprotected_as_encoded();
let mut modified = encoded.to_string();
let memory_offset = modified.find("$m=4096").unwrap();
let iterations_offset = modified.find(",t=3").unwrap();
modified.replace_range(memory_offset..memory_offset + 7, "$m=2048");
modified.replace_range(iterations_offset..iterations_offset + 4, ",t=4");
let modified = PasswordHash::from_encoded(&modified).unwrap();
assert!(hash_password_verify(&modified, &password).is_err());
}
#[test]
fn test_argon2i_verify_err_modified_salt() {
let password = Password::from_slice(&[0u8; 64]).unwrap();
let dk = hash_password(&password, 3, 4096).unwrap();
let mut salt_mod = dk.salt.as_ref().to_vec();
salt_mod[0..16].copy_from_slice(&[0u8; 16]);
let modified =
PasswordHash::from_slice(dk.unprotected_as_bytes(), &salt_mod, 3, 4096).unwrap();
assert!(hash_password_verify(&modified, &password).is_err());
}
#[test]
fn test_argon2i_verify_err_modified_salt_and_password() {
let password = Password::from_slice(&[0u8; 64]).unwrap();
let dk = hash_password(&password, 3, 4096).unwrap();
let mut pwd_mod = dk.unprotected_as_bytes().to_vec();
let mut salt_mod = dk.salt.as_ref().to_vec();
pwd_mod[0..32].copy_from_slice(&[0u8; 32]);
salt_mod[0..16].copy_from_slice(&[0u8; 16]);
let modified = PasswordHash::from_slice(&pwd_mod, &salt_mod, 3, 4096).unwrap();
assert!(hash_password_verify(&modified, &password).is_err());
}
#[test]
fn test_argon2i_invalid_iterations() {
let password = Password::from_slice(&[0u8; 64]).unwrap();
assert!(hash_password(&password, MIN_ITERATIONS - 1, 4096).is_err());
}
#[test]
fn test_argon2i_invalid_memory() {
let password = Password::from_slice(&[0u8; 64]).unwrap();
assert!(hash_password(&password, MIN_ITERATIONS, MIN_MEMORY - 1).is_err());
}
}
}