diff --git a/Cargo.lock b/Cargo.lock index 3767c73..c605446 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" @@ -1668,7 +1668,7 @@ dependencies = [ [[package]] name = "slowkey" -version = "2.0.0" +version = "2.1.0" dependencies = [ "balloon-hash", "base64 0.21.7", diff --git a/Cargo.toml b/Cargo.toml index 8cace83..fca599a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ authors = ["Leonid Beder "] edition = "2021" name = "slowkey" -version = "2.0.0" +version = "2.1.0" [dependencies] balloon-hash = "0.4.0" diff --git a/README.md b/README.md index d3615cd..e7f459e 100755 --- a/README.md +++ b/README.md @@ -130,7 +130,7 @@ Options: ```sh Continue derivation process from an existing checkpoint -Usage: slowkey restore-from-checkpoint [OPTIONS] --checkpoint +Usage: slowkey restore-from-checkpoint [OPTIONS] Options: -i, --iterations @@ -145,6 +145,8 @@ Options: Specifies the number of most recent checkpoints to keep, while automatically deleting older ones [default: 1] --checkpoint Path to an existing checkpoint from which to resume the derivation process + --interactive + Input checkpoint data interactively (instead of providing the path to an existing checkpoint) --base64 Show the result in Base64 (in addition to hex) --base58 @@ -433,7 +435,7 @@ Please input all data either in raw or hex format starting with the 0x prefix ✔ Enter your checkpoint/output encryption key · ******** Checkpoint: - Version: 1: + Version: 2: Iterations: 5: Data (please highlight to see): 0x7ce6792307959432459050b666260a72c7105d18e66c31cc59d3044fb827f482 Previous Iteration's Data (please highlight to see): 0xf131df94fd3c0294685d19097f9c331bd41abafdcc972695cce89d0d21707ec2 @@ -487,7 +489,7 @@ Please input all data either in raw or hex format starting with the 0x prefix ✔ Enter your checkpoint/output encryption key · ******** Checkpoint: - Version: 1: + Version: 2: Iterations: 5: Data (please highlight to see): 0x7ce6792307959432459050b666260a72c7105d18e66c31cc59d3044fb827f482 Previous Iteration's Data (please highlight to see): 0xf131df94fd3c0294685d19097f9c331bd41abafdcc972695cce89d0d21707ec2 @@ -529,6 +531,71 @@ Total running time: 39s Average iteration time: 1s 993ms ``` +You can also provide checkpoint data in an interactive way by specifying the `--interactive` flag: + +> slowkey restore-from-checkpoint -i 10 --interactive + +```sh +Please input all data either in raw or hex format starting with the 0x prefix + +✔ Enter your checkpoint/output encryption key · ******** + +Please enter the checkpoint data manually: + +Version: 2 + +Length: 32 +Iteration: 5 +Data: 0x7ce6792307959432459050b666260a72c7105d18e66c31cc59d3044fb827f482 +Previous data: 0xf131df94fd3c0294685d19097f9c331bd41abafdcc972695cce89d0d21707ec2 + +Scrypt n: 1048576 +Scrypt r: 8 +Scrypt p: 1 + +Argon2id m_cost: 2097152 +Argon2id t_cost: 2 + +Balloon Hash s_cost: 131072 +Balloon Hash t_cost: 1 + +Checkpoint: + Version: 2: + Iterations: 5: + Data (please highlight to see): 0x7ce6792307959432459050b666260a72c7105d18e66c31cc59d3044fb827f482 + Previous Iteration's Data (please highlight to see): 0xf131df94fd3c0294685d19097f9c331bd41abafdcc972695cce89d0d21707ec2 + +SlowKey Parameters: + Iterations: 10 + Length: 32 + Scrypt: (n: 1048576, r: 8, p: 1) + Argon2id: (version: 19, m_cost: 2097152, t_cost: 2) + Balloon Hash: (hash: SHA512, s_cost: 131072, t_cost: 1) + +✔ Enter your salt · ******** + +Salt is: s...t + +✔ Enter your password · ******** + +Password is: p...d + +Verifying the checkpoint... + +The password, salt and internal data are correct + +████████████████████████████████████████████████████████████████████████████████ 10/10 100% (0s) + +Iteration time moving average (10): 2s 610ms, last iteration time: 2s 625ms + +Key is (please highlight to see): 0xda158bedf00e5abba900e0c027c249912e3ad5ce54304fdb54f1939ddb14232a + +Start time: 2024-12-10 08:47:27 +End time: 2024-12-10 08:47:40 +Total running time: 13s +Average iteration time: 1s 305ms +``` + ### Outputs By default, the tool outputs they key in a hexadecimal format, but the tool also supports both [Base64](https://en.wikipedia.org/wiki/Base64) and [Base58](https://en.wikipedia.org/wiki/Binary-to-text_encoding#Base58) formats optionally: diff --git a/src/main.rs b/src/main.rs index 8aa3eca..f289101 100755 --- a/src/main.rs +++ b/src/main.rs @@ -20,7 +20,7 @@ use base64::{engine::general_purpose, Engine as _}; use chrono::{DateTime, Utc}; use clap::{Parser, Subcommand}; use crossterm::style::Stylize; -use dialoguer::{theme::ColorfulTheme, Confirm, Password}; +use dialoguer::{theme::ColorfulTheme, Confirm, Input, Password}; use humantime::format_duration; use indicatif::{MultiProgress, ProgressBar, ProgressStyle}; use mimalloc::MiMalloc; @@ -38,7 +38,12 @@ use std::{ use utils::{ algorithms::{argon2id::Argon2idOptions, balloon_hash::BalloonHashOptions, scrypt::ScryptOptions}, chacha20poly1305::ChaCha20Poly1305, - checkpoints::checkpoint::{Checkpoint, CheckpointData, CheckpointOptions, OpenCheckpointOptions}, + checkpoints::{ + checkpoint::{ + Checkpoint, CheckpointData, CheckpointOptions, CheckpointSlowKeyOptions, OpenCheckpointOptions, SlowKeyData, + }, + version::Version, + }, outputs::output::{OpenOutputOptions, Output, OutputOptions}, }; @@ -195,7 +200,14 @@ enum Commands { long, help = "Path to an existing checkpoint from which to resume the derivation process" )] - checkpoint: PathBuf, + checkpoint: Option, + + #[arg( + long, + action = clap::ArgAction::SetTrue, + help = "Input checkpoint data interactively (instead of providing the path to an existing checkpoint)" + )] + interactive: bool, #[arg( long, @@ -475,6 +487,102 @@ fn get_output_key() -> Vec { key } +fn get_checkpoint_data() -> CheckpointData { + println!("Please enter the checkpoint data manually:\n"); + + let version: u8 = Input::new().with_prompt("Version").interact_text().unwrap(); + let version = Version::from(version); + + println!(); + + let length: usize = Input::new().with_prompt("Length").interact_text().unwrap(); + if length < SlowKeyOptions::MIN_KEY_SIZE { + panic!( + "length {} is shorter than the min value of {}", + SlowKeyOptions::MIN_KEY_SIZE, + length + ); + } else if length > SlowKeyOptions::MAX_KEY_SIZE { + panic!( + "length {} is greater than the max value of {}", + SlowKeyOptions::MAX_KEY_SIZE, + length + ); + } + + let iteration: usize = Input::new().with_prompt("Iteration").interact_text().unwrap(); + let iteration = iteration - 1; + if iteration < SlowKeyOptions::MIN_ITERATIONS { + panic!( + "iteration {} is shorter than the min value of {}", + SlowKeyOptions::MIN_ITERATIONS, + iteration + ); + } else if iteration > SlowKeyOptions::MAX_ITERATIONS { + panic!( + "iteration {} is greater than the max value of {}", + SlowKeyOptions::MAX_ITERATIONS, + iteration + ); + } + + let data: String = Input::new().with_prompt("Data").interact_text().unwrap(); + let data = if data.starts_with(HEX_PREFIX) { + hex::decode(data.strip_prefix(HEX_PREFIX).unwrap()).unwrap() + } else { + data.as_bytes().to_vec() + }; + + let prev_data = if iteration > 1 { + let prev_data: String = Input::new().with_prompt("Previous data").interact_text().unwrap(); + let prev_data = if prev_data.starts_with(HEX_PREFIX) { + hex::decode(prev_data.strip_prefix(HEX_PREFIX).unwrap()).unwrap() + } else { + prev_data.as_bytes().to_vec() + }; + + Some(prev_data) + } else { + None + }; + + println!(); + + let scrypt_n: u64 = Input::new().with_prompt("Scrypt n").interact_text().unwrap(); + let scrypt_r: u32 = Input::new().with_prompt("Scrypt r").interact_text().unwrap(); + let scrypt_p: u32 = Input::new().with_prompt("Scrypt p").interact_text().unwrap(); + let scrypt = ScryptOptions::new(scrypt_n, scrypt_r, scrypt_p); + + println!(); + + let argon2id_m_cost: u32 = Input::new().with_prompt("Argon2id m_cost").interact_text().unwrap(); + let argon2id_t_cost: u32 = Input::new().with_prompt("Argon2id t_cost").interact_text().unwrap(); + let argon2id = Argon2idOptions::new(argon2id_m_cost, argon2id_t_cost); + + println!(); + + let balloon_s_cost: u32 = Input::new().with_prompt("Balloon Hash s_cost").interact_text().unwrap(); + let balloon_t_cost: u32 = Input::new().with_prompt("Balloon Hash t_cost").interact_text().unwrap(); + let balloon_hash = BalloonHashOptions::new(balloon_s_cost, balloon_t_cost); + + println!(); + + CheckpointData { + version, + data: SlowKeyData { + iteration, + data, + prev_data, + slowkey: CheckpointSlowKeyOptions { + length, + scrypt, + argon2id, + balloon_hash, + }, + }, + } +} + fn show_hint(data: &str, description: &str, hex: bool) { let len = data.len(); @@ -820,6 +928,7 @@ fn main() { checkpoint_interval, max_checkpoints_to_keep, checkpoint, + interactive, base64, base58, iteration_moving_window, @@ -827,10 +936,17 @@ fn main() { print_input_instructions(); let output_key = get_output_key(); - let checkpoint_data = Checkpoint::get_checkpoint(&OpenCheckpointOptions { - key: output_key.clone(), - path: checkpoint, - }); + + let checkpoint_data = match checkpoint { + Some(path) => Checkpoint::get_checkpoint(&OpenCheckpointOptions { + key: output_key.clone(), + path, + }), + None => match interactive { + true => get_checkpoint_data(), + false => panic!("Missing checkpoint path"), + }, + }; if iterations <= checkpoint_data.data.iteration { panic!( diff --git a/src/utils/algorithms/scrypt.rs b/src/utils/algorithms/scrypt.rs index 3629bc2..ca737b2 100644 --- a/src/utils/algorithms/scrypt.rs +++ b/src/utils/algorithms/scrypt.rs @@ -21,7 +21,11 @@ impl ScryptOptions { pub const DEFAULT_P: u32 = 1; pub fn new(n: u64, r: u32, p: u32) -> Self { - // Note that there is no need to check if either n, r or p are in bounds, since both are bound by the maximum + if n == 0 || (n & (n - 1)) != 0 { + panic!("Invalid n"); + } + + // Note that there is no need to check if either r or p are in bounds, since both are bound by the maximum // and the minimum values for this type Self { n, r, p }