use argon2::password_hash::SaltString;
use clap::{ArgGroup, Parser};
use std::io::{self, IsTerminal, Read, Write};
use zeroize::Zeroizing;
#[derive(Parser, Debug)]
#[command(
name = "argon2",
about = "(Rust implementation)",
disable_help_flag = false
)]
#[command(group(ArgGroup::new("variant").args(&["i", "d", "id"])))]
#[command(group(ArgGroup::new("memory").args(&["m", "k"])))]
#[command(group(ArgGroup::new("output_format").args(&["e", "r"])))]
struct Args {
salt: Option<String>,
#[arg(short = 'i', long, default_value_t = false)]
i: bool,
#[arg(short = 'd', long, default_value_t = false)]
d: bool,
#[arg(long = "id", default_value_t = false)]
id: bool,
#[arg(short = 't', default_value_t = 3)]
t: u32,
#[arg(short = 'm', default_value_t = 12)]
m: u32,
#[arg(short = 'k')]
k: Option<u32>,
#[arg(short = 'p', default_value_t = 1)]
p: u32,
#[arg(short = 'l', default_value_t = 32)]
l: u32,
#[arg(short = 'e', default_value_t = false)]
e: bool,
#[arg(short = 'r', default_value_t = false)]
r: bool,
#[arg(short = 'v', default_value_t = 13)]
v: u32,
}
fn get_input() -> io::Result<Zeroizing<Vec<u8>>> {
let stdin = io::stdin();
if stdin.is_terminal() {
print!("Enter password: ");
io::stdout().flush()?;
let mut input = Zeroizing::new(String::new());
stdin.read_line(&mut input)?;
while input.ends_with('\n') || input.ends_with('\r') {
input.pop();
}
Ok(Zeroizing::new(input.as_bytes().to_vec()))
} else {
let mut input = Zeroizing::new(Vec::new());
stdin.lock().read_to_end(&mut input)?;
Ok(input)
}
}
fn validate_salt(salt: &str) -> Result<(), String> {
if salt.len() < 8 {
return Err("Invalid salt: minimum length is 8 characters".to_string());
}
Ok(())
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let args_env = std::env::args();
let new_args: Vec<String> = args_env
.map(|arg| {
if arg == "-id" {
"--id".to_string()
} else {
arg
}
})
.collect();
let args = Args::parse_from(new_args);
let salt_string = match &args.salt {
Some(salt) => {
validate_salt(salt)?;
SaltString::encode_b64(salt.as_bytes()).map_err(|e| format!("Invalid salt: {}", e))?
}
None => SaltString::generate(&mut rand_core::OsRng),
};
let password = get_input().map_err(|e| format!("Error reading input: {}", e))?;
let algorithm = if args.d {
argon2::Algorithm::Argon2d
} else if args.id {
argon2::Algorithm::Argon2id
} else {
argon2::Algorithm::Argon2i
};
let memory_kib = if let Some(k) = args.k { k } else { 1 << args.m };
let params = argon2::Params::new(memory_kib, args.t, args.p, Some(args.l as usize))
.map_err(|e| format!("Invalid parameters: {}", e))?;
let version = match args.v {
10 => argon2::Version::V0x10,
13 => argon2::Version::V0x13,
_ => return Err(format!("Invalid version: {} (expected 10 or 13)", args.v).into()),
};
let argon2 = argon2::Argon2::new(algorithm, version, params);
let start = std::time::Instant::now();
use argon2::{PasswordHasher, PasswordVerifier};
let password_hash = argon2
.hash_password(&password, salt_string.as_salt())
.map_err(|e| format!("Hashing failed: {}", e))?;
let duration = start.elapsed();
if args.e {
println!("{}", password_hash);
} else if args.r {
if let Some(hash) = password_hash.hash {
println!("{}", hex::encode(hash.as_bytes()));
}
} else {
println!("Type: {:?}", algorithm);
println!("Iterations: {}", args.t);
println!("Memory: {} KiB", memory_kib);
println!("Parallelism: {}", args.p);
if let Some(hash) = password_hash.hash {
println!("Hash: {}", hex::encode(hash.as_bytes()));
}
println!("Encoded: {}", password_hash);
println!("{:.3} seconds", duration.as_secs_f64());
argon2
.verify_password(&password, &password_hash)
.map_err(|e| format!("Verification failed: {}", e))?;
println!("Verification ok");
}
Ok(())
}