From f149374e2c6682ea5b9b1d692b001d6ab5faea4a Mon Sep 17 00:00:00 2001 From: mrw1593 Date: Sun, 19 Mar 2023 12:24:28 -0400 Subject: Implement password hashing --- Cargo.lock | 59 +++++++++++++++++++++++++++++++++++-- Cargo.toml | 2 ++ src/api/liveops.rs | 1 + src/main.rs | 4 ++- src/services/crypto.rs | 80 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/services/db.rs | 1 + src/services/mod.rs | 1 + 7 files changed, 145 insertions(+), 3 deletions(-) create mode 100644 src/services/crypto.rs diff --git a/Cargo.lock b/Cargo.lock index 8cdffde..ece075a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -30,7 +30,7 @@ dependencies = [ "actix-service", "actix-utils", "ahash 0.8.3", - "base64", + "base64 0.21.0", "bitflags", "brotli", "bytes", @@ -236,6 +236,18 @@ dependencies = [ "alloc-no-stdlib", ] +[[package]] +name = "arrayref" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" + +[[package]] +name = "arrayvec" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" + [[package]] name = "atoi" version = "1.0.0" @@ -251,6 +263,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + [[package]] name = "base64" version = "0.21.0" @@ -269,6 +287,17 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "blake2b_simd" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c2f0dc9a68c6317d884f97cc36cf5a3d20ba14ce404227df55e1af708ab04bc" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq 0.2.5", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -347,6 +376,18 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3" +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + +[[package]] +name = "constant_time_eq" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13418e745008f7349ec7e449155f419a61b92b58a99cc3616942b926825ec76b" + [[package]] name = "convert_case" version = "0.4.0" @@ -1162,12 +1203,26 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rust-argon2" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50162d19404029c1ceca6f6980fe40d45c8b369f6f44446fa14bb39573b5bb9" +dependencies = [ + "base64 0.13.1", + "blake2b_simd", + "constant_time_eq 0.1.5", + "crossbeam-utils", +] + [[package]] name = "rust-pw-server" version = "0.1.0" dependencies = [ "actix-web", "exun", + "rand", + "rust-argon2", "sqlx", ] @@ -1198,7 +1253,7 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" dependencies = [ - "base64", + "base64 0.21.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index f18c794..c30af2e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,5 +7,7 @@ edition = "2021" [dependencies] actix-web = "4" +rust-argon2 = "1" +rand = { version = "0.8", features = [ "small_rng" ] } sqlx = { version = "0.6", features = [ "runtime-actix-rustls", "mysql", "uuid", "offline" ] } exun = "0.1" diff --git a/src/api/liveops.rs b/src/api/liveops.rs index de77eb7..ff44107 100644 --- a/src/api/liveops.rs +++ b/src/api/liveops.rs @@ -1,5 +1,6 @@ use actix_web::{get, web, HttpResponse, Scope}; +/// Simple ping #[get("ping")] async fn ping() -> HttpResponse { HttpResponse::Ok().finish() diff --git a/src/main.rs b/src/main.rs index 5104428..dc0f9a7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,9 +4,11 @@ use exun::RawUnexpected; mod api; mod services; +use services::*; + #[actix_web::main] async fn main() -> Result<(), RawUnexpected> { - let sql_pool = services::db::initialize("password_database", "dbuser", "Demo1234").await?; + let sql_pool = db::initialize("password_database", "dbuser", "Demo1234").await?; HttpServer::new(move || { App::new() .app_data(Data::new(sql_pool.clone())) diff --git a/src/services/crypto.rs b/src/services/crypto.rs new file mode 100644 index 0000000..11a5149 --- /dev/null +++ b/src/services/crypto.rs @@ -0,0 +1,80 @@ +use std::hash::Hash; + +use argon2::{hash_raw, verify_raw}; +use exun::RawUnexpected; + +/// A custom pepper used to hide passwords +static PEPPER: [u8; 16] = [ + 0x98, 0x7f, 0x6f, 0xce, 0x20, 0x76, 0x2c, 0x8a, 0xae, 0xf6, 0xee, 0x45, 0xb3, 0x6b, 0x1f, 0x69, +]; + +/// The configuration used for hashing and verifying passwords +static CONFIG: argon2::Config<'_> = argon2::Config { + hash_length: 256, + lanes: 4, + mem_cost: 5333, + time_cost: 4, + secret: &PEPPER, + + ad: &[], + thread_mode: argon2::ThreadMode::Sequential, + variant: argon2::Variant::Argon2i, + version: argon2::Version::Version13, +}; + +/// A password hash and salt for a user +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct PasswordHash { + hash: Box<[u8]>, + salt: Box<[u8]>, +} + +impl Hash for PasswordHash { + fn hash(&self, state: &mut H) { + for byte in self.hash.iter() { + state.write_u8(*byte) + } + } +} + +impl PasswordHash { + /// Hash a password using Argon2 + pub fn new(password: &str) -> Result { + let password = password.as_bytes(); + + let salt: [u8; 16] = rand::random(); + let salt = Box::from(salt); + + let hash = hash_raw(password, &salt, &CONFIG)?.into_boxed_slice(); + + Ok(Self { hash, salt }) + } + + /// Create this structure from a given hash and salt + pub fn from_hash_salt(hash: &[u8], salt: &[u8]) -> Self { + Self { + hash: Box::from(hash), + salt: Box::from(salt), + } + } + + /// Get the password hash + pub fn hash(&self) -> &[u8] { + &self.hash + } + + /// Get the salt used for the hash + pub fn salt(&self) -> &[u8] { + &self.salt + } + + /// Check if the given password is the one that was hashed + pub fn check_password(&self, password: &str) -> Result { + Ok(verify_raw( + password.as_bytes(), + &self.salt, + &self.hash, + &CONFIG, + )?) + } +} diff --git a/src/services/db.rs b/src/services/db.rs index baf73e9..a8e4918 100644 --- a/src/services/db.rs +++ b/src/services/db.rs @@ -1,6 +1,7 @@ use exun::*; use sqlx::MySqlPool; +/// Intialize the connection pool pub async fn initialize(db: &str, user: &str, password: &str) -> Result { let url = format!("mysql://{user}:{password}@localhost/{db}"); MySqlPool::connect(&url).await.unexpect() diff --git a/src/services/mod.rs b/src/services/mod.rs index dec1023..7163603 100644 --- a/src/services/mod.rs +++ b/src/services/mod.rs @@ -1 +1,2 @@ +pub mod crypto; pub mod db; -- cgit v1.2.3