From d38a5c3e4a6df85a4eba13a097c827063fc7cbd5 Mon Sep 17 00:00:00 2001 From: lbeder Date: Mon, 21 Oct 2024 21:39:37 +0100 Subject: [PATCH] Add previous data to each checkpoint so it would be possible to use it for checkpoint verification --- src/main.rs | 156 +++++++++------------------- src/slowkey.rs | 94 +++++++++++------ src/utils/argon2id.rs | 7 +- src/utils/checkpoints/checkpoint.rs | 78 +++++++++++++- src/utils/outputs/output.rs | 17 +++ src/utils/scrypt.rs | 7 +- 6 files changed, 203 insertions(+), 156 deletions(-) diff --git a/src/main.rs b/src/main.rs index b050edb..1d3d1b8 100755 --- a/src/main.rs +++ b/src/main.rs @@ -14,7 +14,7 @@ mod slowkey; use crate::{ slowkey::{SlowKey, SlowKeyOptions, TEST_VECTORS}, - utils::{argon2id::Argon2id, scrypt::ScryptOptions, sodium_init::initialize}, + utils::{scrypt::ScryptOptions, sodium_init::initialize}, }; use base64::{engine::general_purpose, Engine as _}; use chrono::{DateTime, Utc}; @@ -36,7 +36,7 @@ use std::{ use utils::{ argon2id::Argon2idOptions, chacha20poly1305::ChaCha20Poly1305, - checkpoints::checkpoint::{Checkpoint, CheckpointOptions, OpenCheckpointOptions}, + checkpoints::checkpoint::{Checkpoint, CheckpointData, CheckpointOptions, OpenCheckpointOptions}, outputs::output::{OpenOutputOptions, Output, OutputOptions}, }; @@ -334,8 +334,7 @@ fn main() { // Initialize libsodium initialize(); - println!("SlowKey v{VERSION}"); - println!(); + println!("SlowKey v{VERSION}\n"); let cli = Cli::parse(); @@ -357,15 +356,15 @@ fn main() { max_checkpoints_to_keep, }) => { println!( - "Please input all data either in raw or hex format starting with the {} prefix", + "Please input all data either in raw or hex format starting with the {} prefix\n", HEX_PREFIX ); - println!(); let slowkey_opts: SlowKeyOptions; let mut output_key: Option> = None; let mut checkpoint: Option = None; + let mut restore_from_checkpoint_data: Option = None; let mut offset: usize = 0; let mut offset_data = Vec::new(); @@ -387,6 +386,8 @@ fn main() { ); } + println!("{}\n", &checkpoint_data); + slowkey_opts = SlowKeyOptions { iterations, length: checkpoint_data.data.slowkey.length, @@ -397,14 +398,7 @@ fn main() { offset = checkpoint_data.data.iteration + 1; offset_data.clone_from(&checkpoint_data.data.data); - println!( - "{}: version: {}, iteration: {}, data (please highlight to see): {}", - "Checkpoint".yellow(), - u8::from(checkpoint_data.version), - offset.to_string().cyan(), - format!("0x{}", hex::encode(&offset_data)).black().on_black() - ); - println!(); + restore_from_checkpoint_data = Some(checkpoint_data) } else { slowkey_opts = SlowKeyOptions::new( iterations, @@ -445,32 +439,30 @@ fn main() { })); println!( - "Checkpoint will be created every {} iterations and saved to the \"{}\" checkpoints directory", + "Checkpoint will be created every {} iterations and saved to the \"{}\" checkpoints directory\n", checkpointing_interval.to_string().cyan(), &dir.to_string_lossy().cyan() ); - println!(); } + println!("{}\n", &slowkey_opts); + let salt = get_salt(); let password = get_password(); - println!( - "{}: iterations: {}, length: {}, {}: (n: {}, r: {}, p: {}), {}: (version: {}, m_cost: {}, t_cost: {})", - "SlowKey".yellow(), - &slowkey_opts.iterations.to_string().cyan(), - &slowkey_opts.length.to_string().cyan(), - "Scrypt".green(), - &slowkey_opts.scrypt.n.to_string().cyan(), - &slowkey_opts.scrypt.r.to_string().cyan(), - &slowkey_opts.scrypt.p.to_string().cyan(), - "Argon2id".green(), - Argon2id::VERSION.to_string().cyan(), - &slowkey_opts.argon2id.m_cost.to_string().cyan(), - &slowkey_opts.argon2id.t_cost.to_string().cyan(), - ); + if let Some(checkpoint_data) = restore_from_checkpoint_data { + if checkpoint_data.data.iteration > 0 { + println!("Verifying checkpoint..."); - println!(); + if !checkpoint_data.verify(&salt, &password) { + panic!("The password or salt provided for the checkpoint is incorrect!"); + } + + println!(); + } else { + println!("{}: Unable to verify the first checkpoint\n", "Warning".dark_yellow()); + } + } let mb = MultiProgress::new(); @@ -502,6 +494,7 @@ fn main() { let start_time = SystemTime::now(); let running_time = Instant::now(); let slowkey = SlowKey::new(&slowkey_opts); + let mut prev_data = Vec::new(); let handle = thread::spawn(move || { let key = slowkey.derive_key_with_callback( @@ -513,20 +506,29 @@ fn main() { // Create a checkpoint if we've reached the checkpoint interval if checkpointing_interval != 0 && (current_iteration + 1) % checkpointing_interval == 0 { if let Some(checkpoint) = &mut checkpoint { - checkpoint.create_checkpoint(&salt, current_iteration, current_data); + checkpoint.create_checkpoint( + &salt, + current_iteration, + current_data, + if current_iteration == 0 { None } else { Some(&prev_data) }, + ); } if let Some(ref mut cpb) = &mut cpb { let hash = Checkpoint::hash_checkpoint(&salt, current_iteration, current_data); cpb.set_message(format!( - "\nCreated checkpoint #{} with data hash (salted) {}\n", + "\nCreated checkpoint #{} with data hash (salted) {}", (current_iteration + 1).to_string().cyan(), format!("0x{}", hex::encode(hash)).cyan() )); } } + // Store the current data in order to store it in the checkpoint for future verification of the + // parameters + prev_data.clone_from(current_data); + pb.inc(1); }, ); @@ -544,11 +546,8 @@ fn main() { let key = handle.join().unwrap(); - println!(); - println!(); - println!( - "Key is (please highlight to see): {}", + "\n\nKey is (please highlight to see): {}", format!("0x{}", hex::encode(&key)).black().on_black() ); @@ -571,8 +570,7 @@ fn main() { if let Some(out) = out { out.save(length, &key); - println!("Saved encrypted output to \"{}\"", &out.path.to_str().unwrap().cyan(),); - println!(); + println!("Saved encrypted output to \"{}\"\n", &out.path.to_str().unwrap().cyan(),); } println!( @@ -590,20 +588,18 @@ fn main() { .cyan() ); println!( - "Total running time: {}", + "Total running time: {}\n", format_duration(Duration::new(running_time.elapsed().as_secs(), 0)) .to_string() .cyan() ); - println!(); }, Some(Commands::ShowCheckpoint { checkpoint }) => { println!( - "Please input all data either in raw or hex format starting with the {} prefix", + "Please input all data either in raw or hex format starting with the {} prefix\n", HEX_PREFIX ); - println!(); let output_key = get_output_key(); @@ -612,40 +608,15 @@ fn main() { path: checkpoint, }); - let offset = checkpoint_data.data.iteration + 1; - let offset_data = checkpoint_data.data.data; - - println!( - "{}: version: {}, iteration: {}, data (please highlight to see): {}", - "Checkpoint".yellow(), - u8::from(checkpoint_data.version), - offset.to_string().cyan(), - format!("0x{}", hex::encode(offset_data)).black().on_black() - ); - - let slowkey_opts = checkpoint_data.data.slowkey.clone(); - - println!( - "{}: length: {}, {}: (n: {}, r: {}, p: {}), {}: (version: {}, m_cost: {}, t_cost: {})", - "SlowKey Parameters".yellow(), - &slowkey_opts.length.to_string().cyan(), - "Scrypt".green(), - &slowkey_opts.scrypt.n.to_string().cyan(), - &slowkey_opts.scrypt.r.to_string().cyan(), - &slowkey_opts.scrypt.p.to_string().cyan(), - "Argon2id".green(), - Argon2id::VERSION.to_string().cyan(), - &slowkey_opts.argon2id.m_cost.to_string().cyan(), - &slowkey_opts.argon2id.t_cost.to_string().cyan(), - ); + println!("{}\n", &checkpoint_data); + println!("{}\n", &checkpoint_data.data.slowkey); }, Some(Commands::ShowOutput { output }) => { println!( - "Please input all data either in raw or hex format starting with the {} prefix", + "Please input all data either in raw or hex format starting with the {} prefix\n", HEX_PREFIX ); - println!(); let output_key = get_output_key(); @@ -654,51 +625,20 @@ fn main() { path: output, }); - println!( - "{}: iteration: {}, data (please highlight to see): {}", - "Output".yellow(), - output_data.data.iteration, - format!("0x{}", hex::encode(output_data.data.data)).black().on_black() - ); - - let slowkey_opts = output_data.data.slowkey.clone(); - - println!( - "{}: iterations: {}, length: {}, {}: (n: {}, r: {}, p: {}), {}: (version: {}, m_cost: {}, t_cost: {})", - "SlowKey Parameters".yellow(), - &slowkey_opts.iterations.to_string().cyan(), - &slowkey_opts.length.to_string().cyan(), - "Scrypt".green(), - &slowkey_opts.scrypt.n.to_string().cyan(), - &slowkey_opts.scrypt.r.to_string().cyan(), - &slowkey_opts.scrypt.p.to_string().cyan(), - "Argon2id".green(), - Argon2id::VERSION.to_string().cyan(), - &slowkey_opts.argon2id.m_cost.to_string().cyan(), - &slowkey_opts.argon2id.t_cost.to_string().cyan(), - ); + println!("{}\n", &output_data); + println!("{}\n", &output_data.data.slowkey); }, Some(Commands::Test {}) => { for test_vector in TEST_VECTORS.iter() { - let scrypt = &test_vector.opts.scrypt; - let argon2id = &test_vector.opts.argon2id; - println!( - "{}: iterations: {}, length: {}, {}: (n: {}, r: {}, p: {}), {}: (version: {}, m_cost: {}, t_cost: {}), salt: \"{}\", password: \"{}\"", + "{}: iterations: {}\n length: {}\n salt: \"{}\", password: \"{}\"\n{}", "SlowKey".yellow(), test_vector.opts.iterations.to_string().cyan(), test_vector.opts.length.to_string().cyan(), - "Scrypt".green(), - scrypt.n.to_string().cyan(), - scrypt.r.to_string().cyan(), - scrypt.p.to_string().cyan(), - "Argon2id".green(), - Argon2id::VERSION.to_string().cyan(), - argon2id.m_cost.to_string().cyan(), - argon2id.t_cost.to_string().cyan(), from_utf8(&test_vector.salt).unwrap().cyan(), from_utf8(&test_vector.password).unwrap().cyan(), + test_vector.opts ); let slowkey = SlowKey::new(&test_vector.opts); @@ -709,9 +649,7 @@ fn main() { test_vector.offset, ); - println!("Derived key: {}", format!("0x{}", hex::encode(&key)).cyan()); - - println!(); + println!("Derived key: {}\n", format!("0x{}", hex::encode(&key)).cyan()); } }, None => {}, diff --git a/src/slowkey.rs b/src/slowkey.rs index d1f5fd5..da3e80a 100755 --- a/src/slowkey.rs +++ b/src/slowkey.rs @@ -1,7 +1,10 @@ +use std::fmt::{self, Display, Formatter}; + use crate::utils::{ argon2id::{Argon2id, Argon2idOptions}, scrypt::{Scrypt, ScryptOptions}, }; +use crossterm::style::Stylize; use serde::{Deserialize, Serialize}; use sha2::Sha512; use sha3::{Digest, Keccak512}; @@ -51,12 +54,35 @@ impl SlowKeyOptions { Self { iterations, length, - scrypt: scrypt.clone(), - argon2id: argon2id.clone(), + scrypt: *scrypt, + argon2id: *argon2id, } } } +impl Display for SlowKeyOptions { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + let output = format!( + "{}:\n {}: {}\n {}: {}\n {}: (n: {}, r: {}, p: {})\n {}: (version: {}, m_cost: {}, t_cost: {})", + "SlowKey Parameters".yellow(), + "Iterations".green(), + &self.iterations.to_string().cyan(), + "Length".green(), + &self.length.to_string().cyan(), + "Scrypt".green(), + &self.scrypt.n.to_string().cyan(), + &self.scrypt.r.to_string().cyan(), + &self.scrypt.p.to_string().cyan(), + "Argon2id".green(), + Argon2id::VERSION.to_string().cyan(), + &self.argon2id.m_cost.to_string().cyan(), + &self.argon2id.t_cost.to_string().cyan() + ); + + write!(f, "{}", output) + } +} + impl Default for SlowKeyOptions { fn default() -> Self { Self { @@ -125,38 +151,6 @@ impl SlowKey { } } - fn double_hash(&self, salt: &[u8], password: &[u8], res: &mut Vec) { - // Calculate the SHA2 hash of the result and the inputs - res.extend_from_slice(salt); - res.extend_from_slice(password); - - let mut sha512 = Sha512::new(); - sha512.update(&res); - *res = sha512.finalize().to_vec(); - - // Calculate the SHA3 hash of the result and the inputs - res.extend_from_slice(salt); - res.extend_from_slice(password); - - let mut keccack512 = Keccak512::new(); - keccack512.update(&res); - *res = keccack512.finalize().to_vec(); - } - - fn scrypt(&self, salt: &[u8], password: &[u8], res: &mut Vec) { - res.extend_from_slice(salt); - res.extend_from_slice(password); - - *res = self.scrypt.hash(salt, res); - } - - fn argon2id(&self, salt: &[u8], password: &[u8], res: &mut Vec) { - res.extend_from_slice(salt); - res.extend_from_slice(password); - - *res = self.argon2id.hash(salt, res); - } - pub fn derive_key_with_callback)>( &self, salt: &[u8], password: &[u8], offset_data: &[u8], offset: usize, mut callback: F, ) -> Vec { @@ -193,6 +187,38 @@ impl SlowKey { pub fn derive_key(&self, salt: &[u8], password: &[u8], offset_data: &[u8], offset: usize) -> Vec { self.derive_key_with_callback(salt, password, offset_data, offset, |_, _| {}) } + + fn double_hash(&self, salt: &[u8], password: &[u8], res: &mut Vec) { + // Calculate the SHA2 hash of the result and the inputs + res.extend_from_slice(salt); + res.extend_from_slice(password); + + let mut sha512 = Sha512::new(); + sha512.update(&res); + *res = sha512.finalize().to_vec(); + + // Calculate the SHA3 hash of the result and the inputs + res.extend_from_slice(salt); + res.extend_from_slice(password); + + let mut keccack512 = Keccak512::new(); + keccack512.update(&res); + *res = keccack512.finalize().to_vec(); + } + + fn scrypt(&self, salt: &[u8], password: &[u8], res: &mut Vec) { + res.extend_from_slice(salt); + res.extend_from_slice(password); + + *res = self.scrypt.hash(salt, res); + } + + fn argon2id(&self, salt: &[u8], password: &[u8], res: &mut Vec) { + res.extend_from_slice(salt); + res.extend_from_slice(password); + + *res = self.argon2id.hash(salt, res); + } } #[cfg(test)] diff --git a/src/utils/argon2id.rs b/src/utils/argon2id.rs index 1fb06fe..b63cacc 100644 --- a/src/utils/argon2id.rs +++ b/src/utils/argon2id.rs @@ -1,7 +1,7 @@ use libsodium_sys::{crypto_pwhash_ALG_ARGON2ID13, crypto_pwhash_argon2id}; use serde::{Deserialize, Serialize}; -#[derive(PartialEq, Debug, Clone, Serialize, Deserialize)] +#[derive(PartialEq, Debug, Clone, Copy, Serialize, Deserialize)] pub struct Argon2idOptions { pub m_cost: u32, pub t_cost: u32, @@ -62,10 +62,7 @@ impl Argon2id { const BYTES_IN_KIB: usize = 1024; pub fn new(length: usize, opts: &Argon2idOptions) -> Self { - Self { - length, - opts: opts.clone(), - } + Self { length, opts: *opts } } pub fn hash(&self, salt: &[u8], password: &[u8]) -> Vec { diff --git a/src/utils/checkpoints/checkpoint.rs b/src/utils/checkpoints/checkpoint.rs index 8054e68..e85464d 100644 --- a/src/utils/checkpoints/checkpoint.rs +++ b/src/utils/checkpoints/checkpoint.rs @@ -1,17 +1,19 @@ use crate::{ - slowkey::SlowKeyOptions, + slowkey::{SlowKey, SlowKeyOptions}, utils::{ - argon2id::Argon2idOptions, + argon2id::{Argon2id, Argon2idOptions}, chacha20poly1305::{ChaCha20Poly1305, Nonce}, scrypt::ScryptOptions, }, }; +use crossterm::style::Stylize; use glob::{glob_with, MatchOptions}; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; use std::{ collections::VecDeque, + fmt::{self, Display, Formatter}, fs::{remove_file, File}, io::{BufReader, BufWriter, Read, Write}, path::{Path, PathBuf}, @@ -45,6 +47,27 @@ pub struct CheckpointSlowKeyOptions { pub argon2id: Argon2idOptions, } +impl Display for CheckpointSlowKeyOptions { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + let output = format!( + "{}:\n {}: {}\n {}: (n: {}, r: {}, p: {})\n {}: (version: {}, m_cost: {}, t_cost: {})", + "SlowKey Parameters".yellow(), + "Length".green(), + &self.length.to_string().cyan(), + "Scrypt".green(), + &self.scrypt.n.to_string().cyan(), + &self.scrypt.r.to_string().cyan(), + &self.scrypt.p.to_string().cyan(), + "Argon2id".green(), + Argon2id::VERSION.to_string().cyan(), + &self.argon2id.m_cost.to_string().cyan(), + &self.argon2id.t_cost.to_string().cyan() + ); + + write!(f, "{}", output) + } +} + impl From for CheckpointSlowKeyOptions { fn from(options: SlowKeyOptions) -> Self { CheckpointSlowKeyOptions { @@ -59,6 +82,7 @@ impl From for CheckpointSlowKeyOptions { pub struct SlowKeyData { pub iteration: usize, pub data: Vec, + pub prev_data: Option>, pub slowkey: CheckpointSlowKeyOptions, } @@ -68,6 +92,52 @@ pub struct CheckpointData { pub data: SlowKeyData, } +impl CheckpointData { + pub fn verify(&self, salt: &[u8], password: &[u8]) -> bool { + // Use the checkpoint's previous data to derive the current data and return if it matches + let options = SlowKeyOptions { + iterations: 2, + length: self.data.slowkey.length, + scrypt: self.data.slowkey.scrypt, + argon2id: self.data.slowkey.argon2id, + }; + + let prev_data = match &self.data.prev_data { + Some(data) => data, + None => panic!("Unable to verify the checkpoint!"), + }; + + let slowkey = SlowKey::new(&options); + let key = slowkey.derive_key(salt, password, prev_data, 1); + + key == self.data.data + } +} + +impl Display for CheckpointData { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + let prev_data = match &self.data.prev_data { + Some(data) => hex::encode(data), + None => "".to_string(), + }; + + let output = format!( + "{}:\n {}: {}:\n {}: {}:\n {} (please highlight to see): {}\n {} (please highlight to see): {}", + "Checkpoint".yellow(), + "Version".green(), + u8::from(self.version.clone()), + "Iteration".green(), + (self.data.iteration + 1).to_string().cyan(), + "Data".green(), + format!("0x{}", hex::encode(&self.data.data)).black().on_black(), + "Previous Iteration's Data".green(), + format!("0x{}", prev_data).black().on_black() + ); + + write!(f, "{}", output) + } +} + pub struct Checkpoint { dir: PathBuf, data: CheckpointData, @@ -125,6 +195,7 @@ impl Checkpoint { data: SlowKeyData { iteration: 0, data: Vec::new(), + prev_data: None, slowkey: opts.slowkey.clone().into(), }, }, @@ -134,7 +205,7 @@ impl Checkpoint { } } - pub fn create_checkpoint(&mut self, salt: &[u8], iteration: usize, data: &[u8]) { + pub fn create_checkpoint(&mut self, salt: &[u8], iteration: usize, data: &[u8], prev_data: Option<&[u8]>) { let hash = Self::hash_checkpoint(salt, iteration, data); let padding = self.checkpoint_extension_padding; let checkpoint_path = Path::new(&self.dir) @@ -146,6 +217,7 @@ impl Checkpoint { data: SlowKeyData { iteration, data: data.to_vec(), + prev_data: prev_data.map(|slice| slice.to_vec()), slowkey: self.data.data.slowkey.clone(), }, }; diff --git a/src/utils/outputs/output.rs b/src/utils/outputs/output.rs index 0f68b11..e9b5167 100644 --- a/src/utils/outputs/output.rs +++ b/src/utils/outputs/output.rs @@ -1,5 +1,7 @@ +use crossterm::style::Stylize; use serde::{Deserialize, Serialize}; use std::{ + fmt::{self, Display, Formatter}, fs::File, io::{BufReader, BufWriter, Read, Write}, path::PathBuf, @@ -31,6 +33,21 @@ pub struct OutputData { pub data: SlowKeyData, } +impl Display for OutputData { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + let output = format!( + "{}:\n {}: {}\n {} (please highlight to see): {}\n", + "Output".yellow(), + "Iteration".green(), + self.data.iteration, + "Data".green(), + format!("0x{}", hex::encode(&self.data.data)).black().on_black() + ); + + write!(f, "{}", output) + } +} + #[derive(Serialize, Deserialize)] pub struct SlowKeyData { pub iteration: usize, diff --git a/src/utils/scrypt.rs b/src/utils/scrypt.rs index 05afd4d..c4f40f1 100644 --- a/src/utils/scrypt.rs +++ b/src/utils/scrypt.rs @@ -1,7 +1,7 @@ use libsodium_sys::crypto_pwhash_scryptsalsa208sha256_ll; use serde::{Deserialize, Serialize}; -#[derive(PartialEq, Debug, Clone, Serialize, Deserialize)] +#[derive(PartialEq, Debug, Clone, Copy, Serialize, Deserialize)] pub struct ScryptOptions { pub n: u64, pub r: u32, @@ -45,10 +45,7 @@ pub struct Scrypt { impl Scrypt { pub fn new(length: usize, opts: &ScryptOptions) -> Self { - Self { - length, - opts: opts.clone(), - } + Self { length, opts: *opts } } pub fn hash(&self, salt: &[u8], password: &[u8]) -> Vec {