Make argon2id default pw hasher - improve parameter detection (#1762)

* Make argon2id default pw hasher - improve parameter detection
* Remove print
This commit is contained in:
Firstyear 2023-06-22 12:09:09 +10:00 committed by GitHub
parent c4cceda768
commit a0b59c6072
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -38,7 +38,7 @@ pub struct TpmHandle {}
// NIST 800-63.b salt should be 112 bits -> 14 8u8.
const PBKDF2_SALT_LEN: usize = 24;
const PBKDF2_MIN_NIST_SALT_LEN: usize = 14;
pub const PBKDF2_MIN_NIST_SALT_LEN: usize = 14;
// Min number of rounds for a pbkdf2
pub const PBKDF2_MIN_NIST_COST: usize = 10000;
@ -51,9 +51,15 @@ const PBKDF2_SHA1_MIN_KEY_LEN: usize = 19;
const DS_SSHA512_SALT_LEN: usize = 8;
const DS_SSHA512_HASH_LEN: usize = 64;
const ARGON2_SALT_LEN: usize = 24;
// Taken from the argon2 library and rfc 9106
const ARGON2_VERSION: u32 = 19;
const ARGON2_SALT_LEN: usize = 16;
const ARGON2_KEY_LEN: usize = 32;
const ARGON2_MIN_RAM_KIB: u32 = 8 * 1024;
const ARGON2_MAX_RAM_KIB: u32 = 32 * 1024;
const ARGON2_MIN_T_COST: u32 = 2;
const ARGON2_MAX_T_COST: u32 = 4;
const ARGON2_MAX_P_COST: u32 = 1;
#[derive(Clone, Debug)]
pub enum CryptoError {
@ -190,7 +196,7 @@ impl CryptoPolicy {
// Get the cost per thousand rounds
let per_thou = (PBKDF2_MIN_NIST_COST * PBKDF2_BENCH_FACTOR) / 1000;
let t_per_thou = ubt / per_thou;
trace!("{}µs / 1000 rounds", t_per_thou);
trace!("{:010}µs / 1000 rounds", t_per_thou);
// Now we need the attacker work in nanos
let target = target_time.as_nanos() as usize;
@ -244,29 +250,79 @@ impl CryptoPolicy {
//
// To try to balance this we cap max ram at 32MB for now.
let mut t = Duration::ZERO;
// Default amount of ram we sacrifice per thread is 8MB
let mut m_cost = 8 * 1024;
// Default t/p from argon2 library.
let t_cost = 2;
let p_cost = 1;
let mut m_cost = ARGON2_MIN_RAM_KIB;
let mut t_cost = ARGON2_MIN_T_COST;
let p_cost = ARGON2_MAX_P_COST;
// Raise memory usage until an acceptable ram amount is reached.
while t < target_time && m_cost < ARGON2_MAX_RAM_KIB {
m_cost += 1024;
loop {
let params = if let Ok(p) = Params::new(m_cost, t_cost, p_cost, None) {
p
} else {
// Unable to proceed.
error!(
?m_cost,
?t_cost,
?p_cost,
"Parameters were not valid for argon2"
);
break;
};
if let Some(ubt) = Password::bench_argon2id(params) {
t = ubt;
trace!("{}µs for m_cost {}", t.as_nanos(), m_cost);
trace!("{}µs - t_cost {} m_cost {}", ubt.as_nanos(), t_cost, m_cost);
// Parameter adjustment
if ubt < target_time {
if m_cost < ARGON2_MAX_RAM_KIB {
// Help narrow in quicker.
let m_adjust = if target_time
.as_nanos()
.checked_div(ubt.as_nanos())
.unwrap_or(1)
>= 2
{
// Very far from target, double m_cost.
m_cost * 2
} else {
// Close! Increase in a small step
m_cost + (2 * 1024)
};
m_cost = if m_adjust > ARGON2_MAX_RAM_KIB {
ARGON2_MAX_RAM_KIB
} else {
m_adjust
};
continue;
} else if t_cost < ARGON2_MAX_T_COST {
// t=2 with m = 32MB is about the same as t=3 m=20MB, so we want to start with ram
// higher on these iterations. About 12MB appears to be one iteration. We use 8MB
// here though, just to give a little window under that for adjustment.
//
// Similar, once we hit t=4 we just need to have max ram.
let m_adjust = (t_cost.checked_sub(ARGON2_MIN_T_COST).unwrap_or(0)
* ARGON2_MIN_RAM_KIB)
+ ARGON2_MAX_RAM_KIB;
m_cost = if m_adjust > ARGON2_MAX_RAM_KIB {
ARGON2_MAX_RAM_KIB
} else {
m_adjust
};
t_cost += 1;
continue;
} else {
// Unable to proceed, parameters are maxed out.
warn!("Argon2 parameters have hit their maximums - this may be a bug!");
break;
}
} else {
// Found the target time.
break;
}
} else {
error!("Unable to perform bench of argon2id, stopping benchmark");
t = Duration::MAX;
break;
}
}
@ -612,7 +668,7 @@ impl TryFrom<&str> for Password {
return Err(());
}
let version = version.unwrap_or(19);
let version = version.unwrap_or(ARGON2_VERSION);
let version: Version = version.try_into().map_err(|_| {
error!("Failed to convert {} to valid argon2id version", version);
})?;
@ -789,7 +845,7 @@ impl Password {
#[inline]
pub fn new(policy: &CryptoPolicy, cleartext: &str) -> Result<Self, CryptoError> {
Self::new_pbkdf2(policy, cleartext)
Self::new_argon2id(policy, cleartext)
}
pub fn verify(&self, cleartext: &str) -> Result<bool, CryptoError> {
@ -1082,14 +1138,32 @@ impl Password {
pub fn requires_upgrade(&self) -> bool {
match &self.material {
Kdf::TPM_ARGON2ID { .. } => false,
Kdf::ARGON2ID { .. } => false,
Kdf::PBKDF2_SHA512(cost, salt, hash) | Kdf::PBKDF2(cost, salt, hash) => {
*cost < PBKDF2_MIN_NIST_COST
|| salt.len() < PBKDF2_MIN_NIST_SALT_LEN
|| hash.len() < PBKDF2_MIN_NIST_KEY_LEN
Kdf::ARGON2ID {
m_cost,
t_cost,
p_cost,
version,
salt,
key,
} => {
*version < ARGON2_VERSION ||
salt.len() < ARGON2_SALT_LEN ||
key.len() < ARGON2_KEY_LEN ||
// Can't multi-thread
*p_cost > ARGON2_MAX_P_COST ||
// Likely too long on cpu time.
*t_cost > ARGON2_MAX_T_COST ||
// Too much ram
*m_cost > ARGON2_MAX_RAM_KIB
}
Kdf::PBKDF2_SHA1(_, _, _) | Kdf::SSHA512(_, _) | Kdf::NT_MD4(_) => true,
// Only used in unixd today
Kdf::TPM_ARGON2ID { .. } => false,
// All now upgraded to argon2id
Kdf::PBKDF2(_, _, _)
| Kdf::PBKDF2_SHA512(_, _, _)
| Kdf::PBKDF2_SHA1(_, _, _)
| Kdf::SSHA512(_, _)
| Kdf::NT_MD4(_) => true,
}
}
@ -1253,7 +1327,7 @@ mod tests {
let im_pw = "{PBKDF2-SHA256}10000$henZGfPWw79Cs8ORDeVNrQ$1dTJy73v6n3bnTmTZFghxHXHLsAzKaAy8SksDfZBPIw";
let password = "password";
let r = Password::try_from(im_pw).expect("Failed to parse");
assert!(!r.requires_upgrade());
assert!(r.requires_upgrade());
assert!(r.verify(password).unwrap_or(false));
}
@ -1262,7 +1336,7 @@ mod tests {
let im_pw = "{PBKDF2-SHA512}10000$Je1Uw19Bfv5lArzZ6V3EPw$g4T/1sqBUYWl9o93MVnyQ/8zKGSkPbKaXXsT8WmysXQJhWy8MRP2JFudSL.N9RklQYgDPxPjnfum/F2f/TrppA";
let password = "password";
let r = Password::try_from(im_pw).expect("Failed to parse");
assert!(!r.requires_upgrade());
assert!(r.requires_upgrade());
assert!(r.verify(password).unwrap_or(false));
}
@ -1273,7 +1347,7 @@ mod tests {
let im_pw = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$IyTQMsvzB2JHDiWx8fq7Ew$VhYOA7AL0kbRXI5g2kOyyp8St1epkNj7WZyUY4pAIQQ";
let password = "password";
let r = Password::try_from(im_pw).expect("Failed to parse");
assert!(!r.requires_upgrade());
assert!(r.requires_upgrade());
assert!(r.verify(password).unwrap_or(false));
}