Implement password hashing using Argon2, with PHC string format as storage encoding.

This commit is contained in:
Luca Palmieri 2021-08-29 22:09:43 +02:00
parent 312ee4aa89
commit 7d515138d5
5 changed files with 78 additions and 13 deletions

52
Cargo.lock generated
View file

@ -250,6 +250,17 @@ version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28b2cd92db5cbd74e8e5028f7e27dd7aa3090e89e4f2a197cc7c8dfb69c7063b"
[[package]]
name = "argon2"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d805bb12b532be9ce066df7913311f43716b41d8d780e9322113e8a6ae7c41ab"
dependencies = [
"base64ct",
"blake2",
"password-hash",
]
[[package]]
name = "arrayvec"
version = "0.5.2"
@ -305,6 +316,12 @@ version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
[[package]]
name = "base64ct"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a32fd6af2b5827bce66c29053ba0e7c42b9dcab01835835058558c10851a46b"
[[package]]
name = "bitflags"
version = "1.2.1"
@ -323,6 +340,17 @@ dependencies = [
"wyz",
]
[[package]]
name = "blake2"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a4e37d16930f5459780f5621038b6382b9bb37c19016f39fb6b5808d831f174"
dependencies = [
"crypto-mac 0.8.0",
"digest",
"opaque-debug",
]
[[package]]
name = "block-buffer"
version = "0.9.0"
@ -537,6 +565,16 @@ dependencies = [
"lazy_static",
]
[[package]]
name = "crypto-mac"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab"
dependencies = [
"generic-array",
"subtle",
]
[[package]]
name = "crypto-mac"
version = "0.10.0"
@ -930,7 +968,7 @@ version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1441c6b1e930e2817404b5046f1f989899143a12bf92de603b69f4e0aee1e15"
dependencies = [
"crypto-mac",
"crypto-mac 0.10.0",
"digest",
]
@ -1380,6 +1418,17 @@ dependencies = [
"winapi",
]
[[package]]
name = "password-hash"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ad7268ef9bc463fddde8361d358fbfae1aeeb1fb62eca111cd8c763bf1c5891"
dependencies = [
"base64ct",
"rand_core 0.6.2",
"subtle",
]
[[package]]
name = "paste"
version = "1.0.5"
@ -2701,6 +2750,7 @@ dependencies = [
"actix-rt",
"actix-web",
"anyhow",
"argon2",
"base64",
"chrono",
"claim",

View file

@ -36,6 +36,7 @@ tracing-actix-web = "0.4.0-beta.8"
anyhow = "1.0.40"
base64 = "0.13.0"
sha3 = "0.9"
argon2 = { version = "0.3", features = ["std"] }
[dev-dependencies]
once_cell = "1.7.2"

View file

@ -0,0 +1 @@
ALTER TABLE users ADD COLUMN salt TEXT NOT NULL;

View file

@ -0,0 +1 @@
ALTER TABLE users DROP COLUMN salt;

View file

@ -4,7 +4,7 @@ use crate::routes::error_chain_fmt;
use actix_web::http::{HeaderMap, HeaderValue, StatusCode};
use actix_web::{web, HttpResponse, ResponseError};
use anyhow::Context;
use sha3::Digest;
use argon2::{Argon2, PasswordHash, PasswordVerifier};
use sqlx::PgPool;
#[derive(serde::Deserialize)]
@ -88,26 +88,38 @@ async fn validate_credentials(
credentials: Credentials,
pool: &PgPool,
) -> Result<uuid::Uuid, PublishError> {
let password_hash = sha3::Sha3_256::digest(credentials.password.as_bytes());
let password_hash = format!("{:x}", password_hash);
let user_id: Option<_> = sqlx::query!(
let row: Option<_> = sqlx::query!(
r#"
SELECT user_id
SELECT user_id, password_hash
FROM users
WHERE username = $1 AND password_hash = $2
WHERE username = $1
"#,
credentials.username,
password_hash
)
.fetch_optional(pool)
.await
.context("Failed to performed a query to validate auth credentials.")
.context("Failed to performed a query to retrieve stored credentials.")
.map_err(PublishError::UnexpectedError)?;
user_id
.map(|row| row.user_id)
.ok_or_else(|| anyhow::anyhow!("Invalid username or password."))
.map_err(PublishError::AuthError)
let (expected_password_hash, user_id) = match row {
Some(row) => (row.password_hash, row.user_id),
None => {
return Err(PublishError::AuthError(anyhow::anyhow!(
"Unknown username."
)))
}
};
let expected_password_hash = PasswordHash::new(&expected_password_hash)
.context("Failed to parse hash in PHC string format.")
.map_err(PublishError::UnexpectedError)?;
Argon2::default()
.verify_password(credentials.password.as_bytes(), &expected_password_hash)
.context("Invalid password.")
.map_err(PublishError::AuthError)?;
Ok(user_id)
}
#[tracing::instrument(