use std::iter::SkipWhile;
use unicode_segmentation::UnicodeSegmentation;
use leet::LeetReplacementTable;
use grapheme::Grapheme;
use base_conversion::BaseConversion;
use self::base_conversion::{IterativeBaseConversion, SixteenBytes, ArbitraryBytes};
use super::Hasher;
mod base_conversion;
mod grapheme;
mod hmac;
pub(crate) mod leet;
impl<'y, H : super::HasherList> super::PasswordMaker<'y, H>{
pub(super) fn is_suitable_as_output_characters(characters : &str) -> bool {
characters.graphemes(true).nth(1).is_some()
}
pub(super) fn generate_password_verified_input(&self, data : String, key : String) -> String {
let modified_data = data + self.username + self.modifier;
let get_modified_key = move |i : usize| { if i == 0 {key.clone()} else {key.clone() + "\n" + &i.to_string()}};
match &self.post_leet {
None => Self::generate_password_verified_no_post_leet(&modified_data, get_modified_key, &self.assembly_settings, &self.password_part_parameters),
Some(leet_level) => Self::generate_password_verified_with_post_leet(&modified_data, get_modified_key,&self.assembly_settings , &self.password_part_parameters, leet_level),
}
}
fn generate_password_verified_no_post_leet<G : Fn(usize)->String>(modified_data : &str, get_modified_key : G, assembly_settings : &PasswordAssemblyParameters, password_part_parameters : &PasswordPartParameters) -> String {
let password = (0..).flat_map(|i| Self::generate_password_part(modified_data, get_modified_key(i), password_part_parameters));
combine_prefix_password_suffix(password, assembly_settings)
}
fn generate_password_verified_with_post_leet<G : Fn(usize)->String>(modified_data : &str, get_modified_key : G, assembly_settings : &PasswordAssemblyParameters, password_part_parameters : &PasswordPartParameters, post_leet : &LeetReplacementTable) -> String {
let suffix_length = assembly_settings.suffix_length;
let prefix_length = assembly_settings.prefix_length;
let needed_password_length = assembly_settings.password_length.saturating_sub(suffix_length).saturating_sub(prefix_length);
let append_strings_till_needed_length = |s: (String, usize),p : String| {
let new_length = s.1 + p.graphemes(true).count();
let st = s.0 + &p;
if new_length >= needed_password_length {
Err(st)
} else {
Ok((st, new_length))
}
};
let password = (0..)
.map(|i| Self::generate_password_part(modified_data, get_modified_key(i), password_part_parameters))
.map(|i| i.map(|g| g.get()).collect::<String>()) .map(|non_leeted_password| post_leet.leetify(&non_leeted_password)) .try_fold((String::new(), 0), append_strings_till_needed_length).unwrap_err();
combine_prefix_password_suffix(Grapheme::iter_from_str(&password), assembly_settings)
}
fn generate_password_part<'a>(data : &str, key : String, parameters : &'a PasswordPartParameters<'a>) -> GetGraphemesIterator<'a> {
match ¶meters.hash_algorithm{
AlgoSelection::V06(V06HmacOrNot::Hmac) =>
Self::generate_password_part_v06_hmac(data, key, ¶meters.pre_leet_level, ¶meters.characters),
AlgoSelection::V06(V06HmacOrNot::NonHmac) =>
Self::generate_password_part_v06(data, key, ¶meters.pre_leet_level, ¶meters.characters),
AlgoSelection::Modern(HmacOrNot::Hmac(a)) =>
Self::generate_password_part_modern_hmac(data, key, a, ¶meters.pre_leet_level, ¶meters.characters),
AlgoSelection::Modern(HmacOrNot::NonHmac(a)) =>
Self::generate_password_part_modern(data, key, a, ¶meters.pre_leet_level, ¶meters.characters),
}
}
fn generate_password_part_v06<'a>(
second_part : &str,
message : String,
pre_leet_level: &Option<LeetReplacementTable>,
characters : &'a Vec<Grapheme<'a>>,
) -> GetGraphemesIterator<'a> {
let message = message + second_part;
let message = pre_leet_level.as_ref().map(|l| l.leetify(&message)).unwrap_or(message);
let message = yeet_upper_bytes(&message).collect::<Vec<u8>>();
let hash = H::MD5::hash(&message);
let grapheme_indices = hash.convert_to_base(characters.len());
GetGraphemesIterator { graphemes : characters, inner: GetGraphemesIteratorInner::V06(grapheme_indices)}
}
fn generate_password_part_v06_hmac<'a>(
data : &str,
key : String,
pre_leet_level: &Option<LeetReplacementTable>,
characters : &'a Vec<Grapheme<'a>>,
) -> GetGraphemesIterator<'a> {
let key = pre_leet_level.as_ref().map(|l| l.leetify(&key)).unwrap_or(key);
let leetified_data = pre_leet_level.as_ref().map(|l| l.leetify(data));
let data = leetified_data.as_deref().unwrap_or(data);
let key = yeet_upper_bytes(&key);
let data = yeet_upper_bytes(data);
let hash = hmac::hmac::<H::MD5,_>(&key.collect::<Vec<_>>(), data);
let grapheme_indices = hash.convert_to_base(characters.len());
GetGraphemesIterator { graphemes : characters, inner: GetGraphemesIteratorInner::V06(grapheme_indices)}
}
fn generate_password_part_modern_hmac<'a>(
data : &str,
key : String,
algo : &Algorithm,
pre_leet_level: &Option<LeetReplacementTable>,
characters : &'a Vec<Grapheme<'a>>,
) -> GetGraphemesIterator<'a> {
let key = pre_leet_level.as_ref().map(|l| l.leetify(&key)).unwrap_or(key);
let leetified_data = pre_leet_level.as_ref().map(|l| l.leetify(data));
let data = leetified_data.as_deref().unwrap_or(data);
let grapheme_indices = match algo {
Algorithm::Md4 =>
GetGraphemesIteratorInner::Modern16(modern_hmac_to_grapheme_indices::<H::MD4>(&key, data, characters.len()).skip_while(is_zero)),
Algorithm::Md5 =>
GetGraphemesIteratorInner::Modern16(modern_hmac_to_grapheme_indices::<H::MD5>(&key, data, characters.len()).skip_while(is_zero)),
Algorithm::Sha1 =>
GetGraphemesIteratorInner::Modern20(modern_hmac_to_grapheme_indices::<H::SHA1>(&key, data, characters.len()).skip_while(is_zero)),
Algorithm::Sha256 =>
GetGraphemesIteratorInner::Modern32(modern_hmac_to_grapheme_indices::<H::SHA256>(&key, data, characters.len()).skip_while(is_zero)),
Algorithm::Ripemd160 =>
GetGraphemesIteratorInner::Modern20(modern_hmac_to_grapheme_indices::<H::RIPEMD160>(&key, data, characters.len()).skip_while(is_zero)),
};
GetGraphemesIterator { graphemes : characters, inner: grapheme_indices}
}
fn generate_password_part_modern<'a>(
second_part : &str,
message : String,
algo : &Algorithm,
pre_leet_level: &Option<LeetReplacementTable>,
characters : &'a Vec<Grapheme<'a>>,
) -> GetGraphemesIterator<'a> {
let message = message + second_part;
let message = pre_leet_level.as_ref().map(|l| l.leetify(&message)).unwrap_or(message);
let grapheme_indices = match algo {
Algorithm::Md4 =>
GetGraphemesIteratorInner::Modern16(modern_message_to_grapheme_indices::<H::MD4>(&message, characters.len()).skip_while(is_zero)),
Algorithm::Md5 =>
GetGraphemesIteratorInner::Modern16(modern_message_to_grapheme_indices::<H::MD5>(&message,characters.len()).skip_while(is_zero)),
Algorithm::Sha1 =>
GetGraphemesIteratorInner::Modern20(modern_message_to_grapheme_indices::<H::SHA1>(&message,characters.len()).skip_while(is_zero)),
Algorithm::Sha256 =>
GetGraphemesIteratorInner::Modern32(modern_message_to_grapheme_indices::<H::SHA256>(&message,characters.len()).skip_while(is_zero)),
Algorithm::Ripemd160 =>
GetGraphemesIteratorInner::Modern20(modern_message_to_grapheme_indices::<H::RIPEMD160>(&message,characters.len()).skip_while(is_zero)),
};
GetGraphemesIterator { graphemes : characters, inner: grapheme_indices}
}
}
pub(super) struct PasswordAssemblyParameters<'a> {
suffix : &'a str,
prefix : &'a str,
password_length : usize,
suffix_length : usize,
prefix_length : usize,
}
impl<'a> PasswordAssemblyParameters<'a> {
pub(super) fn from_public_parameters(prefix : &'a str, suffix : &'a str, password_length : usize) -> Self{
PasswordAssemblyParameters {
suffix,
prefix,
password_length,
suffix_length: Grapheme::iter_from_str(suffix).count(),
prefix_length: Grapheme::iter_from_str(prefix).count(),
}
}
}
fn combine_prefix_password_suffix<'a, T : Iterator<Item=Grapheme<'a>>>(password: T, assembly_settings : &PasswordAssemblyParameters<'a>) -> String {
let mut result = String::with_capacity(assembly_settings.password_length);
result.extend(Grapheme::iter_from_str(assembly_settings.prefix)
.chain(password)
.take(assembly_settings.password_length.saturating_sub(assembly_settings.suffix_length))
.chain(Grapheme::iter_from_str(assembly_settings.suffix))
.take(assembly_settings.password_length) .map(|g| g.get()));
result
}
#[allow(clippy::trivially_copy_pass_by_ref)] fn is_zero(i : &usize) -> bool {
*i == 0
}
type BaseConversion16 = IterativeBaseConversion<SixteenBytes,usize>;
type BaseConversion16Modern = SkipWhile<BaseConversion16,fn(&usize)->bool>;
type BaseConversion20 = IterativeBaseConversion<ArbitraryBytes<5>,usize>;
type BaseConversion20Modern = SkipWhile<BaseConversion20,fn(&usize)->bool>;
type BaseConversion32 = IterativeBaseConversion<ArbitraryBytes<8>,usize>;
type BaseConversion32Modern = SkipWhile<BaseConversion32,fn(&usize)->bool>;
enum GetGraphemesIteratorInner {
Modern16(BaseConversion16Modern),
Modern20(BaseConversion20Modern),
Modern32(BaseConversion32Modern),
V06(BaseConversion16)
}
struct GetGraphemesIterator<'a> {
graphemes : &'a Vec<Grapheme<'a>>,
inner : GetGraphemesIteratorInner
}
impl<'a> Iterator for GetGraphemesIterator<'a> {
type Item = Grapheme<'a>;
fn next(&mut self) -> Option<Self::Item> {
let idx = match &mut self.inner {
GetGraphemesIteratorInner::Modern16(i) => i.next(),
GetGraphemesIteratorInner::Modern20(i) => i.next(),
GetGraphemesIteratorInner::Modern32(i) => i.next(),
GetGraphemesIteratorInner::V06(i) => i.next(),
};
idx.and_then(|idx| self.graphemes.get(idx).cloned())
}
}
fn modern_hmac_to_grapheme_indices<T>(key : &str, data: &str, divisor : usize) -> <<T as Hasher>::Output as BaseConversion>::Output
where T:Hasher,
<T as Hasher>::Output: BaseConversion + AsRef<[u8]>
{
hmac::hmac::<T,_>(key.as_bytes(), data.bytes()).convert_to_base(divisor)
}
fn modern_message_to_grapheme_indices<T>(data: &str, divisor : usize) -> <<T as Hasher>::Output as BaseConversion>::Output
where T:Hasher,
<T as Hasher>::Output: BaseConversion
{
T::hash(data.as_bytes()).convert_to_base(divisor)
}
pub(super) struct PasswordPartParameters<'a>{
hash_algorithm : AlgoSelection,
pre_leet_level : Option<LeetReplacementTable>,
characters : Vec<Grapheme<'a>>,
}
impl<'a> PasswordPartParameters<'a>{
pub(super) fn from_public_parameters(hash_algorithm : super::HashAlgorithm, leet : super::UseLeetWhenGenerating, characters : &'a str) -> Self {
use super::UseLeetWhenGenerating;
let hash_algorithm = AlgoSelection::from_public_parameters(hash_algorithm);
PasswordPartParameters{
characters: match &hash_algorithm {
AlgoSelection::V06(_) => Grapheme::iter_from_str("0123456789abcdef").collect(),
AlgoSelection::Modern(_) => Grapheme::iter_from_str(characters).collect(),
},
pre_leet_level: match leet {
UseLeetWhenGenerating::NotAtAll
| UseLeetWhenGenerating::After{..} => None,
UseLeetWhenGenerating::Before { level }
| UseLeetWhenGenerating::BeforeAndAfter { level } => Some(LeetReplacementTable::get(level)),
},
hash_algorithm,
}
}
}
enum Algorithm {
Md4,
Md5,
Sha1,
Sha256,
Ripemd160,
}
enum HmacOrNot{
Hmac(Algorithm),
NonHmac(Algorithm),
}
enum V06HmacOrNot{
Hmac,
NonHmac,
}
enum AlgoSelection{
V06(V06HmacOrNot),
Modern(HmacOrNot),
}
impl AlgoSelection {
fn from_public_parameters(settings_algorithm : super::HashAlgorithm) -> Self {
use super::HashAlgorithm;
match settings_algorithm {
HashAlgorithm::Md5Version06 => AlgoSelection::V06(V06HmacOrNot::NonHmac),
HashAlgorithm::HmacMd5Version06 => AlgoSelection::V06(V06HmacOrNot::Hmac),
HashAlgorithm::Md4 => AlgoSelection::Modern(HmacOrNot::NonHmac(Algorithm::Md4)),
HashAlgorithm::HmacMd4 => AlgoSelection::Modern(HmacOrNot::Hmac(Algorithm::Md4)),
HashAlgorithm::Md5 => AlgoSelection::Modern(HmacOrNot::NonHmac(Algorithm::Md5)),
HashAlgorithm::HmacMd5 => AlgoSelection::Modern(HmacOrNot::Hmac(Algorithm::Md5)),
HashAlgorithm::Sha1 => AlgoSelection::Modern(HmacOrNot::NonHmac(Algorithm::Sha1)),
HashAlgorithm::HmacSha1 => AlgoSelection::Modern(HmacOrNot::Hmac(Algorithm::Sha1)),
HashAlgorithm::Sha256 => AlgoSelection::Modern(HmacOrNot::NonHmac(Algorithm::Sha256)),
HashAlgorithm::HmacSha256 => AlgoSelection::Modern(HmacOrNot::Hmac(Algorithm::Sha256)),
HashAlgorithm::Ripemd160 => AlgoSelection::Modern(HmacOrNot::NonHmac(Algorithm::Ripemd160)),
HashAlgorithm::HmacRipemd160 => AlgoSelection::Modern(HmacOrNot::Hmac(Algorithm::Ripemd160)),
}
}
}
#[allow(clippy::cast_possible_truncation)] fn yeet_upper_bytes(input : &str) -> impl Iterator<Item=u8> + Clone + '_ {
input.encode_utf16().map(|wide_char| wide_char as u8)
}
#[cfg(test)]
mod passwordmaker_tests {
use super::*;
#[test]
fn test_combine_prefix_password_suffix(){
let parameters = PasswordAssemblyParameters::from_public_parameters("prefi", "suffi", 15);
let result = combine_prefix_password_suffix(Grapheme::iter_from_str("passwo"), ¶meters);
assert_eq!(&result, "prefipasswsuffi");
}
#[test]
fn test_combine_prefix_password_suffix_too_short(){
let parameters = PasswordAssemblyParameters::from_public_parameters("prefi", "suffi", 8);
let result = combine_prefix_password_suffix(Grapheme::iter_from_str("passwo"), ¶meters);
assert_eq!(&result, "presuffi");
}
#[test]
fn test_yeet_upper_bytes(){
let testinput = "€©ĦÆÆ";
let result = yeet_upper_bytes(testinput).collect::<Vec<_>>();
assert_eq!(result, vec![0xac,0xa9,0x26,0xc6,0xc6]);
}
}