Review/audit security of passphrase sample
sts10 opened this issue · comments
Tidy offers the option for users to generate 5 6-word passphrase samples from its newly created list (--samples
).
Here is that rather convuluted function:
use rand::seq::SliceRandom;
/// Print 5 sample 6-word passphrases from the newly created
/// word list.
pub fn generate_samples(
list: &[String],
ignore_ending_metadata_delimiter: Option<char>,
ignore_starting_metadata_delimiter: Option<char>,
) -> Vec<String> {
let mut samples: Vec<String> = vec![];
for _n in 0..30 {
match list.choose(&mut rand::thread_rng()) {
Some(word) => {
match (
ignore_ending_metadata_delimiter,
ignore_starting_metadata_delimiter,
) {
(Some(delimiter), None) => {
let delimiter = parse_delimiter(delimiter).unwrap();
samples
.push(split_and_vectorize(word, &delimiter.to_string())[0].to_string())
}
(None, Some(delimiter)) => {
let delimiter = parse_delimiter(delimiter).unwrap();
samples
.push(split_and_vectorize(word, &delimiter.to_string())[1].to_string())
}
(Some(_delimiter1), Some(_delimiter2)) => {
panic!("Can't have starting and ending delimiters")
}
(None, None) => samples.push(word.to_string()),
}
}
None => panic!("Couldn't pick a random word"),
}
}
samples
}
While I do not intend for Tidy users to use these generated sample passphrases as actual passphrases, I was wondering if there's anything we can do in the code to make these passphrases more cryptographically secure.
I've refactored this code a bit by splitting it into two functions:
use rand::seq::SliceRandom;
/// Print 5 sample 6-word passphrases from the newly created
/// word list.
pub fn generate_samples(
list: &[String],
ignore_ending_metadata_delimiter: Option<char>,
ignore_starting_metadata_delimiter: Option<char>,
) -> Vec<String> {
let mut samples: Vec<String> = vec![];
for _n in 0..30 {
match list.choose(&mut rand::thread_rng()) {
Some(word) => samples.push(clean_word_of_metadata_using_delimiter(
word,
ignore_ending_metadata_delimiter,
ignore_starting_metadata_delimiter,
)),
None => panic!("Couldn't pick a random word"),
}
}
samples
}
/// Removes "metadata" from word, as specified by ending or starting
/// delimiter
fn clean_word_of_metadata_using_delimiter(
word: &str,
ignore_ending_metadata_delimiter: Option<char>,
ignore_starting_metadata_delimiter: Option<char>,
) -> String {
match (
ignore_ending_metadata_delimiter,
ignore_starting_metadata_delimiter,
) {
(Some(delimiter), None) => {
let delimiter = parse_delimiter(delimiter).unwrap();
split_and_vectorize(word, &delimiter.to_string())[1].to_string()
}
(None, Some(delimiter)) => {
let delimiter = parse_delimiter(delimiter).unwrap();
split_and_vectorize(word, &delimiter.to_string())[0].to_string()
}
(Some(_delimiter1), Some(_delimiter2)) => {
panic!("Can't have starting and ending delimiters")
}
(None, None) => word.to_string(),
}
}
While I do not intend for Tidy users to use these generated sample passphrases as actual passphrases, I was wondering if there's anything we can do in the code to make these passphrases more cryptographically secure.
As far as I can tell, the security of the passphrases in this case hinges on 2 things:
rand::thread_rng()
- The size word list itself
The first one you're relying on a library that would be fairly catastrophic for the ecosystem if it was broken, the second one is variable, but you could add some warnings along the lines of
Warning: passphrase has an entropy below n bits
But yeah, it would be pretty funny for someone to use tidy as their passphrase generator 😉
The size word list itself
At one point I thought about having it print words until each passphrase was, say, 70 bits of entropy or more. So for a 7,776-word list, it'd print 6 words per sample, but for a short, 1,296-word list, it'd print 7 words per sample.
Pros: Would add a visual illustration of the importance/effect of list length. Cons: Forces me to choose an acceptable level of entropy. Plus it's a bit complex/out-of-scope code-wise. And it might encourage users to use Tidy's samples as a passphrase generator....