From f4b5a0541d425a2505aecbd9135ba66ef329db9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sof=C3=ADa=20Aritz?= Date: Sun, 5 Mar 2023 17:35:04 +0100 Subject: [PATCH] Improve security of encrypted notes Closes #1. The password is hashed using Argon2, with the salt being `SHA256(password)`. The output hash is then encoded using hex. The password-checking function checks if the "note database" is encrypted using the plain password (and returns it) or if it is encrypted using the hashed+salted password (and returns the value). This allows older databases to work properly. A migration path may be added in the future. --- Cargo.toml | 5 ++++- src/main.rs | 12 +++++++++--- src/password/mod.rs | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 4 deletions(-) create mode 100644 src/password/mod.rs diff --git a/Cargo.toml b/Cargo.toml index 9e73892..0be7408 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,4 +15,7 @@ serde_json = "1.0" rand = "0.8" pwbox = "0.5" directories = "4.0" -native-dialog = "0.6" \ No newline at end of file +native-dialog = "0.6" +hex = "0.4" +sha2 = "0.10" +argon2 = "0.5" \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index eaf1ae3..01e559b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,10 +16,12 @@ use log::info; use native_dialog::MessageDialog; use simple_logger::SimpleLogger; use crate::notes::{Note, NoteMetadata}; +use crate::password::{check_password, generate_new_password}; use crate::saving::Saving; mod notes; mod saving; +mod password; #[derive(PartialEq)] enum CurrentMode { @@ -101,7 +103,9 @@ impl eframe::App for App { if !self.tested_password && self.password.is_some() { if let Some(note) = self.notes.get(0) { - if note.decrypt(self.password.as_ref().unwrap()).is_err() { + if let Ok(password) = check_password(self.password.as_ref().unwrap(), note) { + self.password = Some(password); + } else { let _ = MessageDialog::new() .set_title("invalid password") .set_text("failed to verify the password against an existing note. please try again") @@ -111,9 +115,11 @@ impl eframe::App for App { self.tested_password = false; return; } - - self.tested_password = true; + } else { + self.password = Some(generate_new_password(self.password.as_ref().unwrap())) } + + self.tested_password = true; } if self.update_notes_next { diff --git a/src/password/mod.rs b/src/password/mod.rs new file mode 100644 index 0000000..6aa3f70 --- /dev/null +++ b/src/password/mod.rs @@ -0,0 +1,35 @@ +use anyhow::bail; +use argon2::Argon2; +use sha2::{Sha256, Digest}; +use crate::notes::Note; + +pub fn generate_new_password(password: &str) -> String { + let mut output = [0_u8; 32]; + let mut output_key = [0u8; 256]; + + hash_password(password.as_bytes(), &mut output); + Argon2::default().hash_password_into(&password.as_bytes(), &output, &mut output_key).unwrap(); + + hex::encode(output_key) +} + +pub fn check_password(password: &str, note: &Note) -> anyhow::Result { + if note.decrypt(password).is_err() { + let new_password = generate_new_password(password); + if note.decrypt(&new_password).is_err() { + bail!("invalid password"); + } else { + Ok(new_password) + } + } else { + Ok(password.into()) + } +} + +fn hash_password(password: &[u8], output: &mut [u8]) { + let mut hasher = Sha256::new(); + hasher.update(password); + let result = hasher.finalize(); + + output.clone_from_slice(&result[..]) +} \ No newline at end of file