summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormrw1593 <botahamec@outlook.com>2023-03-19 12:24:28 -0400
committermrw1593 <botahamec@outlook.com>2023-03-19 12:24:28 -0400
commitf149374e2c6682ea5b9b1d692b001d6ab5faea4a (patch)
tree0a54b08b0fe4ce409108ab1af5d1fdc7fa11157f
parentc00129570baaabe71a3778bc35820e441f51174b (diff)
Implement password hashing
-rw-r--r--Cargo.lock59
-rw-r--r--Cargo.toml2
-rw-r--r--src/api/liveops.rs1
-rw-r--r--src/main.rs4
-rw-r--r--src/services/crypto.rs80
-rw-r--r--src/services/db.rs1
-rw-r--r--src/services/mod.rs1
7 files changed, 145 insertions, 3 deletions
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",
@@ -237,6 +237,18 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -253,6 +265,12 @@ 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"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a"
@@ -270,6 +288,17 @@ 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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -348,6 +377,18 @@ 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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1163,11 +1204,25 @@ dependencies = [
]
[[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<H: std::hash::Hasher>(&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<Self, RawUnexpected> {
+ 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<bool, RawUnexpected> {
+ 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<MySqlPool, RawUnexpected> {
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;