use argon2::password_hash::SaltString;
use clap::{ArgGroup, Parser};
use std::io::{self, BufRead, IsTerminal, Write};
#[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<String> {
let stdin = io::stdin();
if stdin.is_terminal() {
print!("Enter password: ");
io::stdout().flush()?;
let mut input = String::new();
stdin.read_line(&mut input)?;
Ok(input.trim().to_string())
} else {
let lines: Vec<String> = stdin.lock().lines().collect::<Result<_, _>>()?;
Ok(lines.join("\n"))
}
}
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().unwrap_or_else(|e| {
eprintln!("Error reading input: {}", e);
std::process::exit(1);
});
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();
let password_bytes = password.as_bytes();
use argon2::PasswordHasher;
let password_hash = argon2
.hash_password(password_bytes, 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 {
io::stdout().write_all(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());
println!("Verification ok");
}
Ok(())
}