works! breaking change
This commit is contained in:
parent
9e7d8c3494
commit
70693fe768
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "duralumin"
|
||||
version = "0.2.0"
|
||||
version = "0.3.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
|
@ -33,6 +33,10 @@ osshkeys = {git = "https://github.com/noonebtw/rust-osshkeys.git", branch = "mas
|
|||
sha2 = {version = "0.9", optional = true}
|
||||
rpassword = {version = "5.0", optional = true}
|
||||
zeroize = {version = "1.5"}
|
||||
rust-argon2 = "1.0"
|
||||
argon2 = "0.5.3"
|
||||
thiserror = "1.0"
|
||||
anyhow = "1.0"
|
||||
|
||||
itertools = "0.13.0"
|
||||
russh-keys = "0.44.0"
|
||||
ed25519-dalek = "2.1.1"
|
|
@ -1,11 +1,4 @@
|
|||
use std::{io::Write, str::FromStr};
|
||||
|
||||
use clap::Parser;
|
||||
use libduralumin::ed25519::randomart;
|
||||
use osshkeys::PublicParts;
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
use crate::keygen::HashType;
|
||||
|
||||
/// program that generates ed25519 keypairs seeded by a passphrase and an optional ID.
|
||||
#[derive(Parser)]
|
||||
|
@ -15,27 +8,10 @@ use crate::keygen::HashType;
|
|||
author = "No One <noonebtw@nirgendwo.xyz>"
|
||||
)]
|
||||
struct Opts {
|
||||
#[clap(short, long, default_value = "id_ed25519")]
|
||||
#[clap(short, long, default_value = "duralumin")]
|
||||
file: String,
|
||||
}
|
||||
|
||||
fn fix_newline_ref(line: &mut String) {
|
||||
if line.ends_with('\n') {
|
||||
line.pop();
|
||||
|
||||
if line.ends_with('\r') {
|
||||
line.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn fix_newline(mut line: String) -> String {
|
||||
fix_newline_ref(&mut line);
|
||||
|
||||
line
|
||||
}
|
||||
|
||||
pub mod keygen {
|
||||
use std::str::FromStr;
|
||||
|
||||
|
@ -119,11 +95,7 @@ pub mod keygen {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn with_hash_type_and_iterations(
|
||||
self,
|
||||
hash_type: HashType,
|
||||
iterations: i16,
|
||||
) -> Self {
|
||||
pub fn with_hash_type_and_iterations(self, hash_type: HashType, iterations: i16) -> Self {
|
||||
self.with_hash_type(hash_type).with_iterations(iterations)
|
||||
}
|
||||
|
||||
|
@ -144,17 +116,12 @@ pub mod keygen {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn with_passphrase<Z: Into<Zeroizing<String>>>(
|
||||
mut self,
|
||||
passphrase: Z,
|
||||
) -> Self {
|
||||
pub fn with_passphrase<Z: Into<Zeroizing<String>>>(mut self, passphrase: Z) -> Self {
|
||||
self.passphrase = passphrase.into();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build_keypair(
|
||||
self,
|
||||
) -> OsshResult<((Zeroizing<String>, String), KeyPair)> {
|
||||
pub fn build_keypair(self) -> OsshResult<((Zeroizing<String>, String), KeyPair)> {
|
||||
let hash_seed = Zeroizing::new(
|
||||
self.passphrase
|
||||
.chars()
|
||||
|
@ -162,42 +129,38 @@ pub mod keygen {
|
|||
.collect::<String>(),
|
||||
);
|
||||
|
||||
let seed = match self.hash_type {
|
||||
let mut seed = [0u8; 32];
|
||||
match self.hash_type {
|
||||
HashType::Sha256 => {
|
||||
let mut digest = sha2::Sha256::digest(hash_seed.as_bytes());
|
||||
|
||||
assert!(self.iterations <= 1);
|
||||
for _ in 0..(self.iterations - 1) {
|
||||
digest = sha2::Sha256::digest(&digest);
|
||||
digest = sha2::Sha256::digest(digest.as_slice());
|
||||
}
|
||||
digest.to_vec()
|
||||
|
||||
seed.copy_from_slice(&digest[..32]);
|
||||
}
|
||||
HashType::Argon2 => {
|
||||
// TODO: make more random salt
|
||||
let salt = b"thissaltneedsupdating";
|
||||
|
||||
let config = argon2::Config {
|
||||
variant: argon2::Variant::Argon2i,
|
||||
version: argon2::Version::Version13,
|
||||
mem_cost: 65536,
|
||||
time_cost: self.iterations as u32,
|
||||
lanes: 4,
|
||||
thread_mode: argon2::ThreadMode::Parallel,
|
||||
hash_length: 32,
|
||||
..Default::default()
|
||||
};
|
||||
let argon = argon2::Argon2::new(
|
||||
argon2::Algorithm::Argon2i,
|
||||
argon2::Version::V0x13,
|
||||
argon2::Params::new(65536, self.iterations as u32, 4, Some(32))
|
||||
.expect("argon2 params"),
|
||||
);
|
||||
|
||||
argon2::hash_raw(hash_seed.as_bytes(), salt, &config)
|
||||
.expect("hash passphrase")
|
||||
argon
|
||||
.hash_password_into(hash_seed.as_bytes(), salt, &mut seed)
|
||||
.expect("hash passphrase");
|
||||
}
|
||||
}
|
||||
.try_into()
|
||||
.expect("unwrap seed into [u8; 32]");
|
||||
};
|
||||
|
||||
let rng = rand_chacha::ChaChaRng::from_seed(seed).gen::<[u8; 32]>();
|
||||
|
||||
let keypair = Ed25519KeyPair::from_seed(&rng)
|
||||
.map(|key| Into::<KeyPair>::into(key))?;
|
||||
let keypair = Ed25519KeyPair::from_seed(&rng).map(|key| Into::<KeyPair>::into(key))?;
|
||||
|
||||
let private_key = Zeroizing::new(keypair.serialize_openssh(
|
||||
self.encrypt_key.then(|| self.passphrase.as_str()),
|
||||
|
@ -209,107 +172,39 @@ pub mod keygen {
|
|||
}
|
||||
}
|
||||
|
||||
fn read_line() -> std::io::Result<String> {
|
||||
let mut line = String::new();
|
||||
std::io::stdin().read_line(&mut line)?;
|
||||
fix_newline_ref(&mut line);
|
||||
|
||||
Ok(line)
|
||||
}
|
||||
|
||||
fn read_non_empty_line() -> std::io::Result<Option<String>> {
|
||||
let line = read_line()?;
|
||||
|
||||
Ok(if line.is_empty() { None } else { Some(line) })
|
||||
}
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
let opts = Opts::parse();
|
||||
println!("Generating ed25519 ssh keypair:");
|
||||
|
||||
let ((private_key, public_key), keypair) = keygen::SshKeyBuilder::default()
|
||||
.with_passphrase({
|
||||
print!("Enter a passphrase: ");
|
||||
std::io::stdout().flush()?;
|
||||
let passphrase = Zeroizing::new(rpassword::read_password()?);
|
||||
print!("Re-enter the same passphrase: ");
|
||||
std::io::stdout().flush()?;
|
||||
let passphrase2 = Zeroizing::new(rpassword::read_password()?);
|
||||
|
||||
if passphrase == passphrase2 {
|
||||
Ok(passphrase)
|
||||
} else {
|
||||
println!("passphrases do not match.");
|
||||
Err(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidInput,
|
||||
"passphrase did not match.",
|
||||
))
|
||||
}
|
||||
}?)
|
||||
.with_maybe_tag({
|
||||
print!("Enter an optional ID []: ");
|
||||
std::io::stdout().flush()?;
|
||||
|
||||
read_non_empty_line()?
|
||||
})
|
||||
.with_encrypt({
|
||||
print!("Encrypt keypair with passphrase? [Y/n]: ");
|
||||
std::io::stdout().flush()?;
|
||||
|
||||
let encrypting = read_line()? != "n";
|
||||
if encrypting {
|
||||
println!("Encrypting keypair..")
|
||||
};
|
||||
|
||||
encrypting
|
||||
})
|
||||
.with_hash_type({
|
||||
print!("Use hash algorithm (sha256, argon2)[argon2]: ");
|
||||
std::io::stdout().flush()?;
|
||||
|
||||
read_non_empty_line()?
|
||||
.map(|line| HashType::from_str(&line))
|
||||
.unwrap_or(Ok(HashType::Argon2))?
|
||||
})
|
||||
.with_iterations({
|
||||
print!("Number of hashing iterations [4]: ");
|
||||
std::io::stdout().flush()?;
|
||||
|
||||
read_non_empty_line()?
|
||||
.map(|line| i16::from_str(&line))
|
||||
.unwrap_or(Ok(4))?
|
||||
})
|
||||
.build_keypair()?;
|
||||
|
||||
let fingerprint =
|
||||
keypair.fingerprint(osshkeys::keys::FingerprintHash::SHA256)?;
|
||||
let desc = libduralumin::key_gen::cli::keygen_desc_from_stdin()?;
|
||||
let keypair = libduralumin::key_gen::generate_key(desc)?;
|
||||
|
||||
println!(
|
||||
"Your key fingerprint is: Sha256:{}",
|
||||
base64::encode(&fingerprint)
|
||||
keypair.fingerprint_base64()
|
||||
);
|
||||
|
||||
let randomart = randomart::RandomArt::from_digest(&fingerprint)
|
||||
.render("ED25519 256", "SHA256")?;
|
||||
|
||||
println!("RandomArt:\n{}", randomart);
|
||||
println!(
|
||||
"RandomArt:\n{}",
|
||||
keypair.randomart().render("ED25519 256", "SHA256")?
|
||||
);
|
||||
|
||||
let private_path = opts.file.clone();
|
||||
let public_path = opts.file.clone() + ".pub";
|
||||
|
||||
let (private_key, public_key) = keypair.encode_keys()?;
|
||||
std::fs::write(&private_path, private_key)?;
|
||||
|
||||
std::fs::write(&public_path, public_key)?;
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
use std::fs::Permissions;
|
||||
#[cfg(target_family = "unix")]
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
{
|
||||
use std::fs::Permissions;
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
std::fs::set_permissions(private_path, Permissions::from_mode(0o0600))?;
|
||||
#[cfg(target_family = "unix")]
|
||||
std::fs::set_permissions(public_path, Permissions::from_mode(0o0600))?;
|
||||
std::fs::set_permissions(private_path, Permissions::from_mode(0o0600))?;
|
||||
std::fs::set_permissions(public_path, Permissions::from_mode(0o0600))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
369
src/key_gen.rs
Normal file
369
src/key_gen.rs
Normal file
|
@ -0,0 +1,369 @@
|
|||
use russh_keys::PublicKeyBase64;
|
||||
use sha2::Digest;
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
use crate::ed25519::randomart;
|
||||
|
||||
pub mod cli {
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
use crate::key_gen::{HashDesc, KeygenDesc};
|
||||
|
||||
fn fix_newline_ref(line: &mut String) {
|
||||
if line.ends_with('\n') {
|
||||
line.pop();
|
||||
|
||||
if line.ends_with('\r') {
|
||||
line.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn fix_newline(mut line: String) -> String {
|
||||
fix_newline_ref(&mut line);
|
||||
|
||||
line
|
||||
}
|
||||
pub fn read_line() -> std::io::Result<String> {
|
||||
let mut line = String::new();
|
||||
std::io::stdin().read_line(&mut line)?;
|
||||
fix_newline_ref(&mut line);
|
||||
|
||||
Ok(line)
|
||||
}
|
||||
|
||||
pub fn read_non_empty_line() -> std::io::Result<Option<String>> {
|
||||
let line = read_line()?;
|
||||
|
||||
Ok(if line.is_empty() { None } else { Some(line) })
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum Error {
|
||||
#[error(transparent)]
|
||||
Io(#[from] std::io::Error),
|
||||
#[error(transparent)]
|
||||
ParseInt(#[from] std::num::ParseIntError),
|
||||
#[error("Mismatching secret.")]
|
||||
MismatchingSecret,
|
||||
#[error("Invalid hash method.")]
|
||||
InvalidHashMethod,
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
fn read_passphrase() -> Result<Zeroizing<String>> {
|
||||
let passphrase = Zeroizing::new(rpassword::prompt_password_stdout("Enter a passphrase: ")?);
|
||||
let passphrase2 =
|
||||
Zeroizing::new(rpassword::prompt_password_stdout("Enter a passphrase: ")?);
|
||||
|
||||
if passphrase == passphrase2 {
|
||||
println!(
|
||||
"Passphrase entropy (rough estimate): {:.2}",
|
||||
crate::entropy(&passphrase)
|
||||
);
|
||||
Ok(passphrase)
|
||||
} else {
|
||||
Err(Error::MismatchingSecret)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_argon_desc() -> Result<HashDesc> {
|
||||
print!("Use argon2 variant (argon2i, argon2d, argon2id) [argon2id]: ");
|
||||
let variant = match read_non_empty_line()?.as_ref().map(|s| s.as_str()) {
|
||||
Some("argon2i") => argon2::Algorithm::Argon2i,
|
||||
Some("argon2d") => argon2::Algorithm::Argon2d,
|
||||
Some("argon2id") | None => argon2::Algorithm::Argon2id,
|
||||
_ => {
|
||||
return Err(Error::InvalidHashMethod);
|
||||
}
|
||||
};
|
||||
|
||||
print!("Use argon2 version (16,19) [argon2id]: ");
|
||||
let version = match read_non_empty_line()?.as_ref().map(|s| s.as_str()) {
|
||||
Some("16") | Some("10") => argon2::Version::V0x10,
|
||||
Some("19") | Some("13") | None => argon2::Version::V0x13,
|
||||
_ => {
|
||||
return Err(Error::InvalidHashMethod);
|
||||
}
|
||||
};
|
||||
|
||||
print!("memory size (memory cost) (KiB if no unit specified) (min 19 MiB) [64 MiB]: ");
|
||||
let memory = 'mem: {
|
||||
let Some(line) = read_non_empty_line()? else {
|
||||
break 'mem 64 * 1024;
|
||||
};
|
||||
let line = line.trim();
|
||||
let chars = line.char_indices();
|
||||
let digits_end = chars
|
||||
.take_while(|(_, c)| ('0'..='9').contains(c))
|
||||
.map(|(i, _)| i)
|
||||
.last()
|
||||
.unwrap_or(line.len());
|
||||
let digits = &line[..digits_end];
|
||||
let unit = &line[digits_end..];
|
||||
|
||||
let number = digits.parse::<u32>()?;
|
||||
let factor = match unit {
|
||||
"KiB" | "kib" | "" => 1,
|
||||
"MiB" | "mib" => 1024,
|
||||
_ => {
|
||||
return Err(Error::InvalidHashMethod);
|
||||
}
|
||||
};
|
||||
|
||||
number * factor
|
||||
};
|
||||
|
||||
print!("Iterations (time cost) [4]: ");
|
||||
let iterations = read_non_empty_line()?
|
||||
.map(|s| s.parse::<u16>())
|
||||
.unwrap_or(Ok(4))?;
|
||||
|
||||
print!("Lanes (parallelism cost) [1]: ");
|
||||
let lanes = read_non_empty_line()?
|
||||
.map(|s| s.parse::<u16>())
|
||||
.unwrap_or(Ok(1))?;
|
||||
|
||||
Ok(HashDesc::Argon {
|
||||
variant,
|
||||
version,
|
||||
memory,
|
||||
time: iterations as u32,
|
||||
lanes: lanes as u32,
|
||||
hash_length: 32,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn keygen_desc_from_stdin() -> Result<super::KeygenDesc> {
|
||||
let passphrase = read_passphrase()?;
|
||||
|
||||
print!("Enter an optional ID []: ");
|
||||
let tag = read_non_empty_line()?.map(|s| Zeroizing::new(s));
|
||||
|
||||
print!("Encrypt keypair with passphrase? [Y/n]: ");
|
||||
let encrypt = read_line()? == "Y";
|
||||
|
||||
print!("Use hash algorithm (sha256, argon2) [argon2]: ");
|
||||
let hash = match read_non_empty_line()?.as_ref().map(|s| s.as_str()) {
|
||||
Some("argon2") | None => read_argon_desc()?,
|
||||
Some("sha256") => {
|
||||
print!("Iterations [4]: ");
|
||||
let iterations = read_non_empty_line()?
|
||||
.map(|s| s.parse::<u16>())
|
||||
.unwrap_or(Ok(4))?;
|
||||
HashDesc::Sha256 {
|
||||
iterations: iterations as u32,
|
||||
}
|
||||
}
|
||||
_ => return Err(Error::InvalidHashMethod),
|
||||
};
|
||||
|
||||
Ok(KeygenDesc {
|
||||
hash,
|
||||
salt: KeygenDesc::SALT.to_vec(),
|
||||
tag,
|
||||
passphrase,
|
||||
encrypt,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error("Argon2 Error: {0}")]
|
||||
Argon2(argon2::Error),
|
||||
#[error(transparent)]
|
||||
SshKeys(#[from] russh_keys::Error),
|
||||
}
|
||||
|
||||
impl From<argon2::Error> for Error {
|
||||
fn from(value: argon2::Error) -> Self {
|
||||
Self::Argon2(value)
|
||||
}
|
||||
}
|
||||
|
||||
pub type Result<T> = core::result::Result<T, Error>;
|
||||
|
||||
pub struct KeyPair {
|
||||
pub passphrase: Option<Zeroizing<String>>,
|
||||
pub private_key: ed25519_dalek::SigningKey,
|
||||
pub public_key: ed25519_dalek::VerifyingKey,
|
||||
}
|
||||
|
||||
impl KeyPair {
|
||||
pub fn fingerprint(&self) -> Vec<u8> {
|
||||
sha2::Sha256::digest(self.public_key.as_bytes()).to_vec()
|
||||
}
|
||||
|
||||
pub fn fingerprint_base64(&self) -> String {
|
||||
base64::encode(&self.fingerprint())
|
||||
}
|
||||
|
||||
pub fn randomart(&self) -> randomart::RandomArt {
|
||||
randomart::RandomArt::from_digest(&self.fingerprint())
|
||||
}
|
||||
|
||||
pub fn encode_keys(&self) -> Result<(Zeroizing<String>, String)> {
|
||||
let keypair = russh_keys::key::KeyPair::Ed25519(self.private_key.clone());
|
||||
let public_key = keypair.clone_public_key()?;
|
||||
|
||||
let mut private_key = Vec::new();
|
||||
match &self.passphrase {
|
||||
Some(passphrase) => {
|
||||
russh_keys::encode_pkcs8_pem_encrypted(
|
||||
&keypair,
|
||||
passphrase.as_bytes(),
|
||||
1,
|
||||
&mut private_key,
|
||||
)?;
|
||||
}
|
||||
None => {
|
||||
russh_keys::encode_pkcs8_pem(&keypair, &mut private_key)?;
|
||||
}
|
||||
}
|
||||
|
||||
let private_key = Zeroizing::new(core::str::from_utf8(&private_key).unwrap().to_string());
|
||||
let public_key = public_key.public_key_base64();
|
||||
Ok((private_key, public_key))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_key(desc: KeygenDesc) -> Result<KeyPair> {
|
||||
let seed = Zeroizing::new(
|
||||
desc.passphrase
|
||||
.chars()
|
||||
.chain(desc.tag.unwrap_or_default().chars())
|
||||
.collect::<String>(),
|
||||
);
|
||||
|
||||
let mut hash = [0u8; 32];
|
||||
match desc.hash {
|
||||
HashDesc::Sha256 { iterations } => {
|
||||
let mut hasher = sha2::Sha256::new();
|
||||
hasher.update(seed.as_bytes());
|
||||
hasher.update(desc.salt.as_slice());
|
||||
|
||||
let mut digest = hasher.finalize();
|
||||
|
||||
for _ in 0..(iterations - 1) {
|
||||
digest = sha2::Sha256::digest(digest.as_slice());
|
||||
}
|
||||
|
||||
hash.copy_from_slice(&digest[..32]);
|
||||
}
|
||||
HashDesc::Argon {
|
||||
variant,
|
||||
version,
|
||||
memory,
|
||||
time,
|
||||
lanes,
|
||||
hash_length,
|
||||
} => {
|
||||
let argon = argon2::Argon2::new(
|
||||
variant,
|
||||
version,
|
||||
argon2::Params::new(memory, time, lanes, Some(hash_length as usize))?,
|
||||
);
|
||||
|
||||
argon.hash_password_into(seed.as_bytes(), &desc.salt, &mut hash)?;
|
||||
}
|
||||
};
|
||||
|
||||
// let hash = rand_chacha::ChaChaRng::from_seed(hash).gen::<[u8; 32]>();
|
||||
let private_key = ed25519_dalek::SigningKey::from_bytes(&hash);
|
||||
let public_key = private_key.verifying_key();
|
||||
|
||||
return Ok(KeyPair {
|
||||
passphrase: desc.encrypt.then_some(desc.passphrase),
|
||||
private_key,
|
||||
public_key,
|
||||
});
|
||||
// let keypair = russh_keys::key::KeyPair::Ed25519(private_key);
|
||||
// let public_key = keypair.clone_public_key().expect("pubkey");
|
||||
// let fingerprint = public_key.fingerprint();
|
||||
|
||||
// let pubbytes = public_key.public_key_bytes();
|
||||
|
||||
// let randomart = randomart::RandomArt::from_digest(sha2::Sha256::digest(&pubbytes).as_slice())
|
||||
// .render("ED25519 256", "SHA256")
|
||||
// .expect("randomart");
|
||||
|
||||
// let mut private_key = Vec::new();
|
||||
// if desc.encrypt {
|
||||
// russh_keys::encode_pkcs8_pem_encrypted(
|
||||
// &keypair,
|
||||
// desc.passphrase.as_bytes(),
|
||||
// 1,
|
||||
// &mut private_key,
|
||||
// )
|
||||
// .expect("writing private key");
|
||||
// } else {
|
||||
// russh_keys::encode_pkcs8_pem(&keypair, &mut private_key).expect("writing private key");
|
||||
// }
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct KeygenDesc {
|
||||
pub hash: HashDesc,
|
||||
pub salt: Vec<u8>,
|
||||
pub tag: Option<Zeroizing<String>>,
|
||||
pub passphrase: Zeroizing<String>,
|
||||
pub encrypt: bool,
|
||||
}
|
||||
|
||||
impl Default for KeygenDesc {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
hash: Default::default(),
|
||||
salt: Self::SALT.to_vec(),
|
||||
tag: None,
|
||||
passphrase: Zeroizing::new("password123".to_string()),
|
||||
encrypt: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl KeygenDesc {
|
||||
/// known random salt.
|
||||
pub const SALT: &'static [u8; 32] = b"10ru4YBD5wvdQZc2Mw7Brx45jCxGUM3F";
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum HashDesc {
|
||||
Sha256 {
|
||||
iterations: u32,
|
||||
},
|
||||
Argon {
|
||||
variant: argon2::Algorithm,
|
||||
version: argon2::Version,
|
||||
memory: u32,
|
||||
time: u32,
|
||||
lanes: u32,
|
||||
hash_length: u32,
|
||||
},
|
||||
}
|
||||
|
||||
impl Default for HashDesc {
|
||||
fn default() -> Self {
|
||||
Self::Argon {
|
||||
variant: argon2::Algorithm::Argon2id,
|
||||
version: argon2::Version::V0x13,
|
||||
memory: 65536,
|
||||
time: 4,
|
||||
lanes: 1,
|
||||
hash_length: 32,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{generate_key, KeygenDesc};
|
||||
|
||||
#[test]
|
||||
fn test_edkey() {
|
||||
generate_key(KeygenDesc::default());
|
||||
}
|
||||
}
|
51
src/lib.rs
51
src/lib.rs
|
@ -1,3 +1,5 @@
|
|||
use itertools::Itertools;
|
||||
|
||||
#[cfg(feature = "passphrase-gen")]
|
||||
pub mod passphrase_gen;
|
||||
|
||||
|
@ -6,3 +8,52 @@ pub mod password_gen;
|
|||
|
||||
#[cfg(feature = "ed25519")]
|
||||
pub mod ed25519;
|
||||
|
||||
#[cfg(feature = "ed25519")]
|
||||
pub mod key_gen;
|
||||
|
||||
fn entropy(pass: &str) -> f32 {
|
||||
let mut r = 0;
|
||||
if pass.chars().any(|c| c.is_ascii_lowercase()) {
|
||||
r += 26;
|
||||
}
|
||||
if pass.chars().any(|c| c.is_ascii_uppercase()) {
|
||||
r += 26;
|
||||
}
|
||||
if pass.chars().any(|c| c.is_ascii_punctuation()) {
|
||||
r += 36;
|
||||
}
|
||||
if pass.chars().any(|c| c.is_ascii_digit()) {
|
||||
r += 10;
|
||||
}
|
||||
let len = pass.chars().count();
|
||||
let other = pass
|
||||
.chars()
|
||||
.filter(|c| {
|
||||
!(c.is_ascii_lowercase()
|
||||
|| c.is_ascii_uppercase()
|
||||
|| c.is_ascii_digit()
|
||||
|| c.is_ascii_punctuation())
|
||||
})
|
||||
.dedup()
|
||||
.count();
|
||||
|
||||
((r + other) as f32).log2() * len as f32
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::entropy;
|
||||
|
||||
#[test]
|
||||
fn test_entropy() {
|
||||
println!(
|
||||
"{}",
|
||||
entropy("this is a long passphrase with quite a few words!")
|
||||
);
|
||||
println!(
|
||||
"{}",
|
||||
entropy("This Is A Long Passphrase With Quite A Few Words!")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue