use clap::{ArgGroup, Parser};
use std::io::{self, BufRead, IsTerminal, Write};
use argon2::password_hash::SaltString;
#[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: 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 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 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 salt_string = SaltString::encode_b64(args.salt.as_bytes())
.map_err(|e| format!("Invalid salt: {}", e))?;
let argon2 = argon2::Argon2::new(
algorithm,
argon2::Version::V0x13,
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(())
}