added notes about bits of entropy for each generator
This commit is contained in:
+22
-6
@@ -10,7 +10,7 @@ const NUMBER_CHARS: &[u8] = b"0123456789";
|
|||||||
// still exclude symbols that can trip up URLs and CLIs
|
// still exclude symbols that can trip up URLs and CLIs
|
||||||
// excludes: backtick(`), quote('), dquote("), slash(/), bslash(\)
|
// excludes: backtick(`), quote('), dquote("), slash(/), bslash(\)
|
||||||
// pipe (|), arrows (<), (>), brackets ([), (]), ({), (})
|
// pipe (|), arrows (<), (>), brackets ([), (]), ({), (})
|
||||||
const SYMBOL_CHARS: &[u8] = b"!@#$%^&*()?,.-_=+~";
|
const SYMBOL_CHARS: &[u8] = b"!@#$%^&*()?,.-_=+~"; // 18 chars
|
||||||
|
|
||||||
// saves some boilerplate manual bitflags and less clunky to use
|
// saves some boilerplate manual bitflags and less clunky to use
|
||||||
bitflags! {
|
bitflags! {
|
||||||
@@ -73,6 +73,12 @@ fn random_ascii(rng: &mut OsRng, char_set: &[u8]) -> u8 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// todo: protect from small len
|
// todo: protect from small len
|
||||||
|
//
|
||||||
|
// How many bits of entropy?
|
||||||
|
// max_chars_available -> 80
|
||||||
|
// log2(possible_symbols^len) yields bits of entropy
|
||||||
|
// so say len is 20 and we use all 80 chars:
|
||||||
|
// that's log2(80^20) -> 126.4 bits of entropy per password
|
||||||
fn generate_random_chars(config: PasswordConfig, rng: &mut OsRng) -> String {
|
fn generate_random_chars(config: PasswordConfig, rng: &mut OsRng) -> String {
|
||||||
// String is a thin wrapper around Vec<u8> so we can expect re-allocation
|
// String is a thin wrapper around Vec<u8> so we can expect re-allocation
|
||||||
// and copy only when we exceed the capacity of the vector
|
// and copy only when we exceed the capacity of the vector
|
||||||
@@ -80,18 +86,23 @@ fn generate_random_chars(config: PasswordConfig, rng: &mut OsRng) -> String {
|
|||||||
let mut res = String::with_capacity(config.len);
|
let mut res = String::with_capacity(config.len);
|
||||||
let mut chars : Vec<u8> = Vec::new();
|
let mut chars : Vec<u8> = Vec::new();
|
||||||
if config.charset.contains(CharSet::UPPERCASE) {
|
if config.charset.contains(CharSet::UPPERCASE) {
|
||||||
chars.extend_from_slice(UPPERCASE_CHARS);
|
chars.extend_from_slice(UPPERCASE_CHARS); // 26 chars
|
||||||
}
|
}
|
||||||
if config.charset.contains(CharSet::LOWERCASE) {
|
if config.charset.contains(CharSet::LOWERCASE) {
|
||||||
chars.extend_from_slice(LOWERCASE_CHARS);
|
chars.extend_from_slice(LOWERCASE_CHARS); // 26 chars
|
||||||
}
|
}
|
||||||
if config.charset.contains(CharSet::NUMBERS) {
|
if config.charset.contains(CharSet::NUMBERS) {
|
||||||
chars.extend_from_slice(NUMBER_CHARS);
|
chars.extend_from_slice(NUMBER_CHARS); // 10 chars
|
||||||
}
|
}
|
||||||
if config.charset.contains(CharSet::SYMBOLS) {
|
if config.charset.contains(CharSet::SYMBOLS) {
|
||||||
chars.extend_from_slice(SYMBOL_CHARS);
|
chars.extend_from_slice(SYMBOL_CHARS); // 18 chars
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// How many bits of entropy?
|
||||||
|
// max_chars_available -> 80
|
||||||
|
// log2(possible_symbols^len) yields bits of entropy
|
||||||
|
// so say len is 20 and we use all 80 chars
|
||||||
|
// that's log2(80^20) -> 126.4 bits of entropy per password
|
||||||
for _ in 0..config.len {
|
for _ in 0..config.len {
|
||||||
res.push(random_ascii(rng, &chars) as char);
|
res.push(random_ascii(rng, &chars) as char);
|
||||||
}
|
}
|
||||||
@@ -102,6 +113,11 @@ fn generate_random_chars(config: PasswordConfig, rng: &mut OsRng) -> String {
|
|||||||
|
|
||||||
// we can take a slice of "words" because we don't care about:
|
// we can take a slice of "words" because we don't care about:
|
||||||
// capacity, mutation (push/pop), allocation strategy
|
// capacity, mutation (push/pop), allocation strategy
|
||||||
|
//
|
||||||
|
// How many bits of entropy?
|
||||||
|
// log2(104078) -> 16.6 bits per word derived from word_list size
|
||||||
|
// log2(18) -> 4.1 bits for separator assuming seporator is one of our SYMBOLs
|
||||||
|
// total 70.5 bits of entropy
|
||||||
fn generate_passphrase(phrase_count: usize,
|
fn generate_passphrase(phrase_count: usize,
|
||||||
word_list: &[&str], separator: char, rng: &mut OsRng) -> String {
|
word_list: &[&str], separator: char, rng: &mut OsRng) -> String {
|
||||||
let mut res = String::new(); // don't bother saving allocations with a bad guess
|
let mut res = String::new(); // don't bother saving allocations with a bad guess
|
||||||
@@ -119,7 +135,6 @@ fn random_word<'a>(word_list: &[&'a str], rng: &mut OsRng) -> &'a str {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Some questions:
|
// Some questions:
|
||||||
// todo: “How many bits of entropy does your generator produce?”
|
|
||||||
// todo: “What if I need reproducibility?”
|
// todo: “What if I need reproducibility?”
|
||||||
fn main() {
|
fn main() {
|
||||||
// todo: not cross-platform
|
// todo: not cross-platform
|
||||||
@@ -128,6 +143,7 @@ fn main() {
|
|||||||
.expect("could not read dictionary");
|
.expect("could not read dictionary");
|
||||||
|
|
||||||
// filter words here for only ascii
|
// filter words here for only ascii
|
||||||
|
// word_list length is 104078
|
||||||
let word_list: Vec<&str> = content.lines().filter(|w|
|
let word_list: Vec<&str> = content.lines().filter(|w|
|
||||||
w.is_ascii()).collect();
|
w.is_ascii()).collect();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user