I am using this Argon2 library in Rust for password hashing:
https://docs.rs/argon2/latest/argon2/
let salt = SaltString::generate(&mut OsRng);
let argon2 = Argon2::default();
let password_bytes = form.password.as_bytes();
let password_hash = argon2.hash_password(&password_bytes, &salt).unwrap().to_string();
println!("password_hash: {}",password_hash);
let parsed_hash = PasswordHash::new(&password_hash).unwrap();
if Argon2::default().verify_password(&password_bytes, &parsed_hash).is_ok() {
println!("Valid!");
}
The password_hash
generated looks like this:
$argon2id$v=19$m=19456,t=2,p=1$UWyFFxDMETxLo1q7BFjIEQ$i3MwCW0H7fZjFG7hMwKAPZgWs4hjo/foEUCwLsqp9uY
It always seems to have the prefix $argon2id$v=19$m=19456,t=2,p=1$
Can I remove that prefix before storing it in the database to save space? And then just hardcode it to add it before verifying?
What do those things mean?
Is it always going to be the same?
Modern password hashing algorithms contain "cost parameters" which trade performance against security - put simply, you want verifying the hash to be too slow for an attacker to try lots of possible passwords, but fast enough that a legitimate user won't think your application is broken.
In order to generate a matching hash when the correct password is provided, you need to know the exact parameters used to generate the stored hash. As you say, you could hard-code those parameters, but that means you can never change them - in particular, as computers get faster, you want your hash to get slower.
So, common practice is to store the parameters used alongside each hash; then if you change them for new hashes, you can still verify old hashes. Some libraries also allow you to check whether the stored hash uses the up-to-date values for the parameters whenever the user logs in - while you have the correct password, you can save a new, more secure hash.
Similarly, hashing algorithms are sometimes found to be broken, or simply become too easy for attackers to brute-force. At that point, you might want to change away from Argon2 to something completely different. Again, you need to know which of your stored hashes are using Argon2, and which are using the new algorithm, so common practice is to indicate the algorithm used at the very beginning of the hash.
For Argon2, the format of these parameters is actually part of the published standard, so all implementations of Argon2 will generate, and expect, these exact prefixes. While you could strip them off, there's really very little to gain by doing so, and a lot to lose.
As for what the parameters actually mean, you can a brief summary in the Rust documentation you linked, specifically in the argon2::Params
struct:
m_cost
: memory size in 1 KiB blocks. Between 8*p_cost
and (2^32)-1
.t_cost
: number of iterations. Between 1
and (2^32)-1
p_cost
: degree of parallelism. Between 1
and (2^24)-1
.