Non-encrypted confidential information
Need
Protection of stored passwords with a slow, salted password hashing function
Context
• Usage of Rust 1.75+ for building application services
• Usage of the actix-web crate for handling HTTP requests
• Usage of the diesel crate with PostgreSQL for database access
• Usage of the argon2 crate for password hashing
Description
1. Non compliant code
use actix_web::{post, web, HttpResponse};
use diesel::prelude::*;
use diesel::r2d2::{ConnectionManager, Pool};
use serde::Deserialize;
diesel::table! {
users (id) {
id -> Int4,...The `sign_up` handler below receives a username and a password and inserts both into the `users` table with Diesel, exactly as the user typed them. Anyone who can read that table sees every password in clear text: a database administrator, a backup operator, an attacker who exploits a SQL injection, or anyone who obtains a leaked backup. Because people reuse passwords, the damage goes beyond this application: the same credentials can be tried against email, banking and corporate accounts. Encoding the value with Base64 before storing it, as some code does, gives no protection. Base64 is a reversible encoding, not encryption, and decoding it takes one function call.
2. Steps
• Never insert passwords into the database in clear text or with a reversible encoding such as Base64.
• Hash passwords with Argon2id (`argon2` crate) or bcrypt (`bcrypt` crate) before calling Diesel `insert_into(...).values(...)`.
• Generate a random salt per password with `SaltString::generate(&mut OsRng)`, or let `bcrypt::hash` generate it.
• Store the full PHC or bcrypt string, and verify logins with `Argon2::verify_password` or `bcrypt::verify` instead of comparing values in SQL.
• Rename the column to `password_hash` so that no code path treats its content as a password.
• Hash existing plaintext passwords with a migration and ask the affected users to change them.
3. Secure code example
use actix_web::{post, web, HttpResponse};
use argon2::password_hash::rand_core::OsRng;
use argon2::password_hash::{PasswordHasher, SaltString};
use argon2::Argon2;
use diesel::prelude::*;
use diesel::r2d2::{ConnectionManager, Pool};
use serde::Deserialize;
...The corrected handler never stores the password. It hashes the value with Argon2id through the `argon2` crate and inserts the resulting PHC string into a `password_hash` column. Argon2id is a password hashing function designed to be slow and to require a configurable amount of memory, which makes large-scale guessing on GPUs or dedicated hardware expensive. `SaltString::generate` draws a random salt for every user from the operating system generator, so two users with the same password get different hashes and precomputed tables are useless. The PHC string stores the algorithm, its parameters and the salt next to the hash. At login, `PasswordHash::new` parses it and `Argon2::verify_password` checks the submitted password against it, so the original value is never stored anywhere.
References
• 020. Non-encrypted confidential information