Show entropy to the user when inserting a password

Seems like according to the formula used (`E = L * log2(R)`, where `E` is the ntropy, `L` is the password length and `R` is the quantity of unique characters), a good value is a entropy higher than 60.

This is shown by using two distinct colors when rendering the entropy (dark orange when is lower than 60, and light green when is higher than 60).

Even though entropy is quite important, it would be more useful to take into account dictionaries when calculating the entropy, because raw bruteforce attacks are somewhat mitigated with the usage of a KDF.

Related #1
This commit is contained in:
Sofía Aritz 2023-03-06 00:03:41 +01:00
parent f4b5a0541d
commit b051b923fd
2 changed files with 33 additions and 4 deletions

View file

@ -11,12 +11,12 @@ use std::fs;
use std::time::SystemTime; use std::time::SystemTime;
use directories::ProjectDirs; use directories::ProjectDirs;
use eframe::{CreationContext, Frame}; use eframe::{CreationContext, Frame};
use egui::{Context, RichText, TextEdit, Widget}; use egui::{Color32, Context, RichText, TextEdit, Widget, WidgetText};
use log::info; 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::password::{check_password, entropy, generate_new_password};
use crate::saving::Saving; use crate::saving::Saving;
mod notes; mod notes;
@ -223,7 +223,20 @@ impl eframe::App for App {
ui.label(RichText::new("after starting the app, the password will be tested against the latest note").italics()); ui.label(RichText::new("after starting the app, the password will be tested against the latest note").italics());
ui.add_space(10.0); ui.add_space(10.0);
TextEdit::singleline(&mut self.password_buffer).password(true).ui(ui); ui.horizontal(|ui| {
TextEdit::singleline(&mut self.password_buffer).password(true).ui(ui);
if let Some(entropy) = entropy(&self.password_buffer) {
let text = if entropy < 60_f64 {
WidgetText::from(format!("entropy: {:.2}", entropy))
.color(Color32::from_rgb(220, 88, 42))
} else {
WidgetText::from(format!("entropy: {:.2}", entropy))
.color(Color32::from_rgb(144, 238, 144))
};
ui.label(text);
}
});
ui.add_space(7.5); ui.add_space(7.5);
if ui.button("start app").clicked() { if ui.button("start app").clicked() {

View file

@ -8,11 +8,27 @@ pub fn generate_new_password(password: &str) -> String {
let mut output_key = [0u8; 256]; let mut output_key = [0u8; 256];
hash_password(password.as_bytes(), &mut output); hash_password(password.as_bytes(), &mut output);
Argon2::default().hash_password_into(&password.as_bytes(), &output, &mut output_key).unwrap(); Argon2::default().hash_password_into(password.as_bytes(), &output, &mut output_key).unwrap();
hex::encode(output_key) hex::encode(output_key)
} }
pub fn entropy(password: &str) -> Option<f64> {
if password.is_empty() {
return None;
}
let length = password.len() as f64;
let unique_characters = {
let mut unique_chars: Vec<char> = password.chars().collect();
unique_chars.dedup();
unique_chars.len()
} as f64;
Some(length * unique_characters.log2())
}
pub fn check_password(password: &str, note: &Note) -> anyhow::Result<String> { pub fn check_password(password: &str, note: &Note) -> anyhow::Result<String> {
if note.decrypt(password).is_err() { if note.decrypt(password).is_err() {
let new_password = generate_new_password(password); let new_password = generate_new_password(password);