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:
parent
e40c076202
commit
f4b5a0541d
3 changed files with 48 additions and 4 deletions
|
@ -15,4 +15,7 @@ serde_json = "1.0"
|
|||
rand = "0.8"
|
||||
pwbox = "0.5"
|
||||
directories = "4.0"
|
||||
native-dialog = "0.6"
|
||||
native-dialog = "0.6"
|
||||
hex = "0.4"
|
||||
sha2 = "0.10"
|
||||
argon2 = "0.5"
|
12
src/main.rs
12
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 {
|
||||
|
|
35
src/password/mod.rs
Normal file
35
src/password/mod.rs
Normal 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[..])
|
||||
}
|
Loading…
Reference in a new issue