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.
This commit is contained in:
Sofía Aritz 2023-03-05 17:35:04 +01:00
parent e40c076202
commit f4b5a0541d
3 changed files with 48 additions and 4 deletions

View file

@ -16,3 +16,6 @@ rand = "0.8"
pwbox = "0.5" pwbox = "0.5"
directories = "4.0" directories = "4.0"
native-dialog = "0.6" native-dialog = "0.6"
hex = "0.4"
sha2 = "0.10"
argon2 = "0.5"

View file

@ -16,10 +16,12 @@ use log::info;
use native_dialog::MessageDialog; use native_dialog::MessageDialog;
use simple_logger::SimpleLogger; use simple_logger::SimpleLogger;
use crate::notes::{Note, NoteMetadata}; use crate::notes::{Note, NoteMetadata};
use crate::password::{check_password, generate_new_password};
use crate::saving::Saving; use crate::saving::Saving;
mod notes; mod notes;
mod saving; mod saving;
mod password;
#[derive(PartialEq)] #[derive(PartialEq)]
enum CurrentMode { enum CurrentMode {
@ -101,7 +103,9 @@ impl eframe::App for App {
if !self.tested_password && self.password.is_some() { if !self.tested_password && self.password.is_some() {
if let Some(note) = self.notes.get(0) { 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() let _ = MessageDialog::new()
.set_title("invalid password") .set_title("invalid password")
.set_text("failed to verify the password against an existing note. please try again") .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; self.tested_password = false;
return; return;
} }
} else {
self.tested_password = true; self.password = Some(generate_new_password(self.password.as_ref().unwrap()))
} }
self.tested_password = true;
} }
if self.update_notes_next { if self.update_notes_next {

35
src/password/mod.rs Normal file
View file

@ -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<String> {
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[..])
}