#![cfg_attr(feature = "bench", feature(test))]
#![deny(dead_code)]
#![deny(missing_debug_implementations)]
#![deny(missing_docs)]
#![forbid(unsafe_code)]
#![warn(unreachable_pub)]
#![doc(
html_favicon_url = "https://raw.githubusercontent.com/sebastienrousseau/vault/main/assets/mini-functions/icons/ico-psph.svg",
html_logo_url = "https://raw.githubusercontent.com/sebastienrousseau/vault/main/assets/mini-functions/icons/ico-psph.svg",
html_root_url = "https://docs.rs/psph"
)]
#![crate_name = "psph"]
#![crate_type = "lib"]
extern crate cmn;
pub use cmn::constants::*;
pub use cmn::words::WORD_LIST;
extern crate hsh;
pub use hsh::Hash;
extern crate vrd;
pub use vrd::Random;
use std::collections::{HashMap, HashSet};
use std::convert::TryInto;
use std::f64;
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, PartialOrd)]
pub struct Password {
passphrase: String,
special_chars: Vec<char>,
separator: String,
}
impl Password {
pub fn entropy(&self) -> f64 {
let l = self.len() as f64;
l * (94.0_f64.log2()).round()
}
pub fn hash(&self) -> String {
let mut hash = Hash::new();
hash.set_password(&format!(
"{}{}",
self.passphrase,
self.special_chars.iter().collect::<String>()
));
let hash_value = hash.hash();
hash_value.to_string()
}
pub fn hash_length(&self) -> usize {
self.hash().len()
}
pub fn is_empty(&self) -> bool {
self.passphrase.is_empty()
}
pub fn len(&self) -> usize {
self.passphrase.len()
}
pub fn new(len: u8, separator: &str, special_chars: Vec<char>) -> Self {
let mut rng = Random::default();
let mut words: Vec<String> = Vec::new();
let ascii: Vec<char> = SPECIAL_CHARS.to_vec();
let mut word_set = HashSet::new();
let mut seen_chars = HashMap::new();
while words.len() < len.into() {
let mut word = if let Some(w) = Random::choose(&mut rng, WORD_LIST) {
w
} else {
""
};
while words.contains(&word.to_string()) {
word = if let Some(w) = Random::choose(&mut rng, WORD_LIST) {
w
} else {
""
};
let word_seen_chars: &mut HashSet<char> =
seen_chars.entry(word.to_lowercase()).or_default();
let mut has_repeated_chars = false;
for c in word.to_lowercase().chars() {
if !word_seen_chars.insert(c) {
has_repeated_chars = true;
break;
}
}
if has_repeated_chars {
continue;
}
if word_set.contains(&word.to_lowercase()) {
continue;
}
word_set.insert(word.to_lowercase());
}
let mut random_letter = Random::char(&mut rng);
while word.contains(random_letter) {
random_letter = Random::char(&mut rng);
}
let mut word = word.to_owned();
let chars = word.chars().enumerate().collect::<Vec<_>>();
for (i, c) in chars {
if i == 0 || !c.is_alphabetic() {
continue;
}
let lower = c.to_lowercase().next().unwrap();
word.remove(i);
word.insert(i, lower);
word.insert(i + 1, lower.to_uppercase().next().unwrap());
}
let first_letter = word.chars().next().unwrap().to_uppercase().next().unwrap();
word.remove(0);
word.insert(0, first_letter);
let nb = rng.range(HASH_COST.try_into().unwrap(), 99);
word.push(random_letter);
word.push(*Random::choose(&mut rng, &ascii).unwrap());
word.push_str(&nb.to_string());
let mut chars: Vec<char> = word.chars().collect();
let index = rng.range(0, (chars.len() - 1).try_into().unwrap()) as usize;
chars[index] = *Random::choose(&mut rng, &special_chars).unwrap();
word = chars.into_iter().collect();
words.push(word);
}
Self {
passphrase: words.join(separator),
special_chars,
separator: separator.to_string(),
}
}
pub fn passphrase(&self) -> &str {
&self.passphrase
}
pub fn password_length(&self) -> usize {
self.passphrase.len()
}
pub fn set_passphrase(&mut self, passphrase: &str) {
self.passphrase = passphrase.to_string();
}
}
impl std::fmt::Display for Password {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.passphrase)
}
}
impl Default for Password {
fn default() -> Self {
Self::new(4, "-", SPECIAL_CHARS.to_vec())
}
}