From 75230347429e6e6188100f1d406776fc58653d78 Mon Sep 17 00:00:00 2001 From: ManOfGoldForever Date: Fri, 30 Jan 2026 14:36:01 -0500 Subject: [PATCH] Added zeroizing of passwords --- Cargo.toml | 1 + src/main.rs | 61 +++++++++++++++++++++++++++++++------------------- src/storage.rs | 18 ++++++++++----- 3 files changed, 51 insertions(+), 29 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 98528f0..8100f0a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,3 +11,4 @@ hex = "0.4.3" rpassword = "7.3" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" +zeroize = { version = "1.8.2", features = ["derive"] } diff --git a/src/main.rs b/src/main.rs index 3a4e5e9..6c4cc1d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,8 @@ mod storage; use args::{Cli, Commands}; use clap::Parser; use rpassword::read_password; +use std::io::Write; +use zeroize::Zeroize; fn main() { let cli: Cli = Cli::parse(); @@ -13,55 +15,62 @@ fn main() { let encryption_salt = storage::get_or_create_salt(salt_path); - let encryption_key: [u8; 32]; + let mut encryption_key = if !std::path::Path::new(master_hash_path).exists() { + print!("No master password found. Setup required.\n"); + print!("Enter New Master Password: "); + std::io::stdout().flush().expect("Flush failed"); + let mut masterp1 = read_password().expect("Failed to read password"); - if !std::path::Path::new(master_hash_path).exists() { - print!("Enter Master Password : "); - std::io::Write::flush(&mut std::io::stdout()).expect("Flush failed"); - let masterp1 = read_password().expect("Failed to read password"); - - print!("Confirm Master Password : "); - std::io::Write::flush(&mut std::io::stdout()).expect("Flush failed"); - let masterp2 = read_password().expect("Failed to read password"); + print!("Confirm Master Password: "); + std::io::stdout().flush().expect("Flush failed"); + let mut masterp2 = read_password().expect("Failed to read password"); if masterp1 == masterp2 { println!("Creating new Master Password..."); let hash = storage::hash_master_password(&masterp1); std::fs::write(master_hash_path, hash).expect("Failed to save hash"); - encryption_key = storage::derive_key(&masterp1, &encryption_salt); + masterp2.zeroize(); + storage::derive_key(&mut masterp1, &encryption_salt) } else { - println!("Passwords did not match! Please try again."); - return; + masterp1.zeroize(); + masterp2.zeroize(); + panic!("Passwords did not match!"); } } else { print!("Enter Master Password: "); - std::io::Write::flush(&mut std::io::stdout()).expect("Flush failed"); - let input = read_password().expect("Read failed"); + std::io::stdout().flush().expect("Flush failed"); + let mut input = read_password().expect("Read failed"); let saved_hash = std::fs::read_to_string(master_hash_path).expect("Failed to read hash"); + if !storage::verify_master_password(&input, &saved_hash) { + input.zeroize(); println!("Wrong Master Password! Access Denied."); return; } - encryption_key = storage::derive_key(&input, &encryption_salt); - } + storage::derive_key(&mut input, &encryption_salt) + }; let mut passwords = storage::load_passwords(file_path, &encryption_key); match &cli.command { Commands::Add(args) => { print!("Enter password for {} : ", args.name); - std::io::Write::flush(&mut std::io::stdout()).expect("Flush failed"); - let p1 = read_password().expect("Failed to read password"); + std::io::stdout().flush().expect("Flush failed"); + let mut p1 = read_password().expect("Failed to read password"); + print!("Confirm Password : "); - std::io::Write::flush(&mut std::io::stdout()).expect("Flush failed"); - let p2 = read_password().expect("Failed to read password"); + std::io::stdout().flush().expect("Flush failed"); + let mut p2 = read_password().expect("Failed to read password"); + if p1 == p2 { - passwords.insert(args.name.clone(), p1); + passwords.insert(args.name.clone(), p1.clone()); storage::save_passwords(file_path, &passwords, &encryption_key); println!("Saved successfully!"); } else { - println!("Passwords did not match! Please try again."); + println!("Passwords did not match!"); } + p1.zeroize(); + p2.zeroize(); } Commands::Get(args) => match passwords.get(&args.name) { Some(pw) => println!("Password for {} : {}", args.name, pw), @@ -80,6 +89,12 @@ fn main() { } println!("-----------------------"); } - } // _ => {} + } + } + + encryption_key.zeroize(); + + for (_, v) in passwords.iter_mut() { + v.zeroize(); } } diff --git a/src/storage.rs b/src/storage.rs index 6e172b5..f9879d6 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -10,6 +10,7 @@ use chacha20poly1305::{ }; use std::collections::HashMap; use std::fs; +use zeroize::Zeroize; pub fn get_or_create_salt(path: &str) -> String { if std::path::Path::new(path).exists() { @@ -24,7 +25,7 @@ pub fn get_or_create_salt(path: &str) -> String { } } -pub fn derive_key(password: &str, salt: &str) -> [u8; 32] { +pub fn derive_key(password: &mut String, salt: &str) -> [u8; 32] { let mut key = [0u8; 32]; let argon2 = Argon2::default(); @@ -43,7 +44,7 @@ pub fn derive_key(password: &str, salt: &str) -> [u8; 32] { } else { panic!("Argon2 output too short! We need 32 bytes."); } - + password.zeroize(); key } @@ -60,16 +61,19 @@ pub fn load_passwords(path: &str, key: &[u8; 32]) -> HashMap { let nonce = XNonce::from_slice(nonce_bytes); let cipher = XChaCha20Poly1305::new(key.into()); - let plaintext = cipher + let mut plaintext = cipher .decrypt(nonce, ciphertext) .expect("Decryption failed! Data corrupted or wrong key."); - let json_string = String::from_utf8(plaintext).expect("Invalid UTF-8"); - serde_json::from_str(&json_string).unwrap_or_default() + let mut json_string = String::from_utf8(plaintext.clone()).expect("Invalid UTF-8"); + let passwords = serde_json::from_str(&json_string).unwrap_or_default(); + plaintext.zeroize(); + json_string.zeroize(); + passwords } pub fn save_passwords(path: &str, data: &HashMap, key: &[u8; 32]) { - let json = serde_json::to_string(data).expect("Failed to serialize"); + let mut json = serde_json::to_string(data).expect("Failed to serialize"); let cipher = XChaCha20Poly1305::new(key.into()); let nonce = XChaCha20Poly1305::generate_nonce(&mut OsRng); @@ -78,6 +82,8 @@ pub fn save_passwords(path: &str, data: &HashMap, key: &[u8; 32] .encrypt(&nonce, json.as_bytes()) .expect("Encryption failed"); + json.zeroize(); + let mut combined = nonce.to_vec(); combined.extend(ciphertext); fs::write(path, combined).expect("Failed to write to file");