From 154a38a0138129c7b86e4583da6ad432da181b98 Mon Sep 17 00:00:00 2001 From: Ethan O'Brien Date: Sat, 6 Jun 2026 15:52:19 -0500 Subject: [PATCH] Move to argon2 for password hashes --- Cargo.lock | 130 ++++++++++++++++++++++---- Cargo.toml | 2 + src/router/global.rs | 1 + src/router/userdata/user/migration.rs | 68 +++++++------- 4 files changed, 148 insertions(+), 53 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fd463be..68697e6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -205,7 +205,7 @@ checksum = "66bd29a732b644c0431c6140f370d097879203d79b80c94a6747ba0872adaef8" dependencies = [ "cipher", "cpubits", - "cpufeatures", + "cpufeatures 0.3.0", ] [[package]] @@ -294,6 +294,18 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +[[package]] +name = "argon2" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" +dependencies = [ + "base64ct", + "blake2", + "cpufeatures 0.2.17", + "password-hash", +] + [[package]] name = "autocfg" version = "1.5.0" @@ -324,6 +336,24 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "block-buffer" version = "0.12.0" @@ -427,8 +457,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" dependencies = [ "cfg-if", - "cpufeatures", - "rand_core", + "cpufeatures 0.3.0", + "rand_core 0.10.1", ] [[package]] @@ -437,7 +467,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e34d8227fe1ba289043aeb13792056ff80fd6de1a9f49137a5f499de8e8c78ea" dependencies = [ - "crypto-common", + "crypto-common 0.2.1", "inout", ] @@ -535,6 +565,15 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae" +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + [[package]] name = "cpufeatures" version = "0.3.0" @@ -562,11 +601,21 @@ dependencies = [ "cpubits", "ctutils", "num-traits", - "rand_core", + "rand_core 0.10.1", "serdect", "zeroize", ] +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + [[package]] name = "crypto-common" version = "0.2.1" @@ -584,7 +633,7 @@ checksum = "21f41f23de7d24cdbda7f0c4d9c0351f99a4ceb258ef30e5c1927af8987ffe5a" dependencies = [ "crypto-bigint", "libm", - "rand_core", + "rand_core 0.10.1", ] [[package]] @@ -666,15 +715,26 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer 0.10.4", + "crypto-common 0.1.7", + "subtle", +] + [[package]] name = "digest" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" dependencies = [ - "block-buffer", + "block-buffer 0.12.0", "const-oid", - "crypto-common", + "crypto-common 0.2.1", "ctutils", ] @@ -730,6 +790,7 @@ version = "1.1.0" dependencies = [ "actix-web", "aes", + "argon2", "base64", "cbc", "cc", @@ -750,6 +811,7 @@ dependencies = [ "objc2-foundation", "pem", "rand", + "rand_core 0.6.4", "rsa", "rusqlite", "sha1", @@ -844,6 +906,16 @@ dependencies = [ "slab", ] +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getrandom" version = "0.2.17" @@ -876,7 +948,7 @@ dependencies = [ "cfg-if", "libc", "r-efi 6.0.0", - "rand_core", + "rand_core 0.10.1", "wasip2", "wasip3", ] @@ -959,7 +1031,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" dependencies = [ - "digest", + "digest 0.11.3", ] [[package]] @@ -1544,6 +1616,17 @@ dependencies = [ "windows-link", ] +[[package]] +name = "password-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +dependencies = [ + "base64ct", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "pem" version = "3.0.6" @@ -1686,7 +1769,16 @@ checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" dependencies = [ "chacha20", "getrandom 0.4.2", - "rand_core", + "rand_core 0.10.1", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", ] [[package]] @@ -1768,10 +1860,10 @@ dependencies = [ "const-oid", "crypto-bigint", "crypto-primes", - "digest", + "digest 0.11.3", "pkcs1", "pkcs8", - "rand_core", + "rand_core 0.10.1", "sha1", "signature", "spki", @@ -1951,8 +2043,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" dependencies = [ "cfg-if", - "cpufeatures", - "digest", + "cpufeatures 0.3.0", + "digest 0.11.3", ] [[package]] @@ -1962,8 +2054,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" dependencies = [ "cfg-if", - "cpufeatures", - "digest", + "cpufeatures 0.3.0", + "digest 0.11.3", ] [[package]] @@ -1988,8 +2080,8 @@ version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d567dcbaf0049cb8ac2608a76cd95ff9e4412e1899d389ee400918ca7537f5" dependencies = [ - "digest", - "rand_core", + "digest 0.11.3", + "rand_core 0.10.1", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 3baa123..285947e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,8 @@ mime_guess = "2.0.5" include_dir = "0.7.4" jzon = "0.12.5" csv = "1.3" +argon2 = "0.5.3" +rand_core = { version = "0.6", features = ["getrandom"] } [target.aarch64-linux-android.dependencies] jni = { version = "0.22.4", features = ["invocation", "default"], optional = true } diff --git a/src/router/global.rs b/src/router/global.rs index 06cae1b..7ece742 100644 --- a/src/router/global.rs +++ b/src/router/global.rs @@ -75,6 +75,7 @@ pub fn parse_platform(header: &str) -> &str { "android" => "Android", "ios" => "iOS", "windows" => "Windows", + "windowsplayer" => "Windows", _ => "Android", } } diff --git a/src/router/userdata/user/migration.rs b/src/router/userdata/user/migration.rs index 800eb83..30ff15b 100644 --- a/src/router/userdata/user/migration.rs +++ b/src/router/userdata/user/migration.rs @@ -1,3 +1,7 @@ +use argon2::{ + password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString}, + Argon2 +}; use rusqlite::params; use jzon::{JsonValue, object}; use crate::router::userdata; @@ -55,46 +59,27 @@ pub fn get_acc_token(uid: i64) -> String { } } -fn generate_salt() -> Vec { - let mut rng = rand::rng(); - let mut bytes = vec![0u8; 16]; - rng.fill(&mut bytes[..]); - bytes -} - fn hash_password(password: &str) -> String { - if password.is_empty() { return String::new(); }; - let salt = &generate_salt(); - let mut hasher = Sha256::new(); - hasher.update(password.as_bytes()); - hasher.update(salt); - let hashed_password = hasher.finalize(); - - let salt_hash = [&salt[..], &hashed_password[..]].concat(); - general_purpose::STANDARD.encode(salt_hash) + if password.is_empty() { return String::new(); } + let salt = SaltString::generate(&mut OsRng); + Argon2::default() + .hash_password(password.as_bytes(), &salt) + .unwrap() + .to_string() } -pub fn verify_password(password: &str, salted_hash: &str) -> bool { - if password.is_empty() || salted_hash.is_empty() { +pub fn verify_password(password: &str, hash: &str) -> bool { + if password.is_empty() || hash.is_empty() { return false; } - let bytes = general_purpose::STANDARD.decode(salted_hash); - if bytes.is_err() { - return password == salted_hash; + if !hash.starts_with("$argon2") { + return legacy_verify_password(password, hash); } - let bytes = bytes.unwrap(); - if bytes.len() < 17 { - return password == salted_hash; - } - let (salt, hashed_password) = bytes.split_at(16); - let hashed_password = &hashed_password[0..32]; - - let mut hasher = Sha256::new(); - hasher.update(password.as_bytes()); - hasher.update(salt); - let input_hash = hasher.finalize(); - - input_hash.as_slice() == hashed_password + let parsed = match PasswordHash::new(hash) { + Ok(h) => h, + Err(_) => return false, + }; + Argon2::default().verify_password(password.as_bytes(), &parsed).is_ok() } pub fn setup_sql(conn: &rusqlite::Connection) -> Result<(), rusqlite::Error> { @@ -141,6 +126,7 @@ pub fn setup_sql(conn: &rusqlite::Connection) -> Result<(), rusqlite::Error> { Ok(()) } +// For migrating from a legacy database fn code_to_uid(code: &str) -> String { code .replace('7', "") @@ -155,3 +141,17 @@ fn code_to_uid(code: &str) -> String { .replace('U', "9") .replace('M', "0") } + +fn legacy_verify_password(password: &str, salted_hash: &str) -> bool { + let bytes = match general_purpose::STANDARD.decode(salted_hash) { + Ok(b) if b.len() >= 17 => b, + _ => return password == salted_hash, + }; + let (salt, hashed_password) = bytes.split_at(16); + let hashed_password = &hashed_password[0..32]; + let mut hasher = Sha256::new(); + hasher.update(password.as_bytes()); + hasher.update(salt); + let input_hash = hasher.finalize(); + input_hash.as_slice() == hashed_password +}