module Pwqgen
Ruby implementation of Openwall passwdqc
vim: set expandtab sw=2 ts=2:ft=ruby
vim: set expandtab sw=2 ts=2:ft=ruby
Constants
- NUMERIC_SEPARATORS
numeric separators
- SEPARATORS
default separators
- VERSION
version number
- WORDSET
This list has been downcased, since passwdqc pwqgen can uncapitalize already capitalized words
comment directly from passwdqc:
4096 English words for generation of easy to memorize random passphrases. This list comes from the MakePass passphrase generator developed by Dianelos Georgoudis <dianelos at tecapro.com>, which was announced on sci.crypt on 1997/10/24. Here's a relevant excerpt from that posting:
The 4096 words in the word list were chosen according to the following criteria:
-
each word must contain between 3 and 6 characters
-
each word must be a common English word
-
each word should be clearly different from each other word, orthographically or semantically
The MakePass word list has been placed in the public domain
At least two other sci.crypt postings by Dianelos Georgoudis also state that the word list is in the public domain, and so did the web page at:
web.archive.org/web/%2a/http://www.tecapro.com/makepass.html
which existed until 2006 and is available from the Wayback Machine as of this writing (March 2010). Specifically, the web page said:
> The MakePass word list has been placed in the public domain. To download > a copy click here. You can use the MakePass word list for many other > purposes.
“To download a copy click here” was a link to free/makepass.lst, which is currently available via the Wayback Machine:
web.archive.org/web/%2a/http://www.tecapro.com/free/makepass.lst
Even though the original description of the list stated that “each word must contain between 3 and 6 characters”, there were two 7-character words: “England” and “Germany”. For use in passwdqc, these have been replaced with “erase” and “gag”.
The code in passwdqc_check.c and passwdqc_random.c makes the following assumptions about this list:
-
there are exactly 4096 words;
-
the words are of up to 6 characters long;
-
although some words may contain capital letters, no two words differ by
the case of characters alone (e.g., converting the list to all-lowercase would yield a list of 4096 unique words);
-
the words contain alphabetical characters only;
-
if an entire word on this list matches the initial substring of other
word(s) on the list, it is placed immediately before those words (e.g., “bake”, “baker”, “bakery”).
Additionally, the default minimum passphrase length of 11 characters specified in passwdqc_parse.c has been chosen such that a passphrase consisting of any three words from this list with two separator characters will pass the minimum length check. In other words, this default assumes that no word is shorter than 3 characters.
-
Public Class Methods
if n is a power of 2, return log2(n), else return nil
Arguments:
n: (Integer)
# File lib/pwqgen/pwqgen.rb, line 71 def log2(n) return nil if n <= 0 # return nil unless n is a power of 2 return nil unless (n & (n - 1)).zero? bits = 0 while n > 255 n >>= 8 bits += 8 end while n > 0 n >>= 1 bits += 1 end bits - 1 end
Pwqgen.pwqgen
- generate a random passphrase using the pwqgen algorithm
Arguments:
n_words: (Integer) random_generator: (Proc) - Proc or method reference - this should take one argument, n, and return n random bytes as a String of length n. Default is to use Sysrandom.random_bytes separators: (String) - separators for use between words. Default is Pwqgen::SEPARATORS. Array of one character strings random_capitalize: (Boolean) - whether or not to "randomly" capitalize words. Default is true.
Examples:
require 'pwqgen' require 'securerandom' # only for the second and third examples # Five words. Default behaviour puts Pwqgen.pwqgen(n_words: 5) # use Securerandom instead of Sysrandom and with custom separators puts Pwqgen.pwqgen(n_words: 5, random_generator: SecureRandom.method(:random_bytes), random_capitalize: false, separators: %w(2 3 4 |) ) # OR puts Pwqgen.pwqgen(n_words: 5, random_generator: proc { |x| SecureRandom.random_bytes(x) }, random_capitalize: false, separators: %w(2 3 4 |) ) # produces "adam-adam-adam-adam" as the random generator always returns 0 puts Pwqgen.pwqgen(n_words: 4, random_generator: Proc.new {|x| "\000" * x }})
# File lib/pwqgen/pwqgen.rb, line 44 def pwqgen( n_words:, random_generator: proc { |x| Sysrandom.random_bytes(x) }, separators: SEPARATORS, random_capitalize: true ) # validate arguments raise ArgumentError, "n_words must be an integer: #{n_words.inspect}" unless n_words.is_a? Integer raise ArgumentError, "n_words must be a positive integer: #{n_words.inspect}" if n_words < 1 raise ArgumentError, "invalid random_generator: #{random_generator.inspect}" unless random_generator.respond_to? :call raise ArgumentError, "separators must be Array of length 2**n n<=12, of one characters strings: #{separators.inspect}" \ unless (separators.is_a? Array) && separators.inject(true) { |a, e| a && (e.is_a? String) && e.length == 1 } \ && log2(separators.length) random_separators = Array.new(n_words - 1) { random_separator(random_generator: random_generator, separators: separators) } random_words = Array.new(n_words) { random_word(random_generator: random_generator, random_capitalize: random_capitalize) } # interleave random_words and random_separators. Relies on nil.to_s is empty string random_words.inject('') do |a, e| a + e + random_separators.shift.to_s end end
Private Class Methods
Get n_bits bits from the random generator. This squanders some randomness, since it gets the required number of bytes and throws away bits that are not needed. Returns an Integer between 0 and 2**n_bits - 1. Arguments:
random_generator: (Proc) Proc or method reference for random_bytes n_bits: (Integer)
# File lib/pwqgen/pwqgen.rb, line 99 def random_bits(random_generator:, n_bits:) bits = 0 random_generator.call(((n_bits - 1) / 8) + 1).unpack('C*').each_with_index { |byte, i| bits |= byte << (8 * i) } bits & ((1 << n_bits) - 1) # throw away the bits we do not need end
generate a separator “randomly” chosen from separators
# File lib/pwqgen/pwqgen.rb, line 114 def random_separator(random_generator:, separators:) separators[random_bits(random_generator: random_generator, n_bits: log2(separators.length))] end