1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
//! A set of memorable words, with convenience passphrase functions
//!
//! The list of words is just [`WORDS`](WORDS).
//!
//! You may likely prefer to use one of the provided passphrase
//! generators.  These accept a number of bits of entropy desired.
//! This corresponds to the `log2` of the number of passphrases that
//! should be possible to generate.  This interface allows the number
//! of memorable words to be adjusted without making your code any
//! less secure.  You get to decide how secore the passphrase needs to
//! be.  If your concern is a network attacker, then you might think
//! that the 44 bits of entropy [advertized by
//! xkcd](https://m.xkcd.com/936) is sufficient.  If you are concerned
//! about an offline attack on a hashed password file, then you'd
//! better ask for more entropy or ensure that you use a seriously
//! slow hashing algorithm and a good salt.
//!
//! Note that while the algorithm guarantees the number of bits of
//! entropy you request, it doesn't go far above that.  So the fewer
//! bits you request, the easier to remember the result should be.

mod words;

/// The list of memorable words
///
/// This list is ordered from "most memorable" to "least" so if you
/// want to use a subset you may use the first `N` words.
pub const WORDS: &[&str] = words::LIST;

/// A javascript script that generates passphrases.
///
/// If it is prefered to generate passphrases on the client rather
/// than the server, this string provides javascript code that
/// generates passphrases in the same way that the rust code does.
/// The functions have `passphrase_` prepended to them,
/// e.g. `passphrase_camel_case`, and accept a number of bits just
/// like the rust functions do.
pub const JAVASCRIPT: &str = include_str!("../memorable-wordlist.js");

const NUM_BITS: usize = 14;

fn words_for_bits(bits: usize) -> impl Iterator<Item=&'static str> {
    use rand::seq::SliceRandom;

    let num_words = if bits % NUM_BITS == 0 { bits/NUM_BITS } else { bits/NUM_BITS + 1 };
    let number_per_word = (bits as f64/num_words as f64).exp2() as usize;
    let mut words = Vec::with_capacity(num_words);
    let mut rng = rand::thread_rng();
    for _ in 0..num_words {
        words.push(WORDS[0..number_per_word].choose(&mut rng).unwrap());
    }
    words.into_iter().map(|&x| x)
}

/// Generate a space-delimited passphrase
///
/// The passphrase will be generated with `bits` amount of entropy.
pub fn space_delimited(bits: usize) -> String {
    let mut code = String::new();
    for w in words_for_bits(bits) {
        code.push_str(w);
        code.push(' ');
    }
    code.pop();
    code
}

/// Generate a snake_case passphrase
///
/// The passphrase will be generated with `bits` amount of entropy.
pub fn snake_case(bits: usize) -> String {
    let mut code = String::new();
    for w in words_for_bits(bits) {
        code.push_str(w);
        code.push('_');
    }
    code.pop();
    code
}

/// Generate a kebab-case passphrase
///
/// The passphrase will be generated with `bits` amount of entropy.
pub fn kebab_case(bits: usize) -> String {
    let mut code = String::new();
    for w in words_for_bits(bits) {
        code.push_str(w);
        code.push('-');
    }
    code.pop();
    code
}


/// Generate a CamelCase passphrase
///
/// The passphrase will be generated with `bits` amount of entropy.
pub fn camel_case(bits: usize) -> String {
    let mut code = String::new();
    for w in words_for_bits(bits) {
        let mut c = w.chars();
        code.push(c.next().unwrap().to_uppercase().next().unwrap());
        code.push_str(c.as_str());
    }
    code
}

#[test]
fn has_length() {
    assert!(space_delimited(41).len() > 4);
    assert!(snake_case(40).len() > 4);
    assert!(camel_case(40).len() > 4);
    assert!(kebab_case(40).len() > 4);
}

#[test]
fn num_bits_correct() {
    assert!(1 << NUM_BITS <= WORDS.len());
}