A while back I wrote about Diceware, a system for generating password using dice and a word list. I also include a Ruby script that use virtual dice.
The diceware passwords of reasonable length are strong, they have high entropy. However, most password security requirements aren’t based on entropy, but instead are made up, with people throwing in requirements for capital letters, numbers, and symbols because they sound good.
Well, we don’t get to set policy, so I’ve modified my script to generate passwords with numbers and symbols as separators. I’m using the Diceware method which lays these characters out in a grid and then uses two rolls generate coordinates in the grid:
SYMBOLS = [%w[1 ~ ! # $ % ^],
%w[2 & * ( ) - =],
%w[3 + [ ] \ { }],
%w[4 : ; " ' < >],
%w[5 ? / 0 1 2 3],
%w[6 4 5 6 7 8 9]]
x,y = rand(6), rand(6)
separator = SYMBOLS[x][y]
You could make that simpler with:
separator = SYMBOLS.sample.sample
but I’m sticking with the original dice concept.
If we have count words, we need count-1 separators, which we can get with:
separators = (count.to_i - 1).times.map { SYMBOLS[rand(6)][rand(6)] }
Once we have words and separators we need to interleave them together. This
is a job for Array#zip
%w[one two three].zip([1,2])
=> [["one", 1], ["two", 2], ["three", nil]]
Because zip
returns an array of arrays when need to flatten
it
out.
[["one", 1], ["two", 2], ["three", nil]].flatten
=> ["one", 1, "two", 2, "three", nil]
and join
them:
["one", 1, "two", 2, "three", nil].join
=> "one1two2three"
Throw in a option to toggle separators v.s. spaces, and the final results is:
#!/usr/bin/env ruby
require 'optparse'
dice_file = '/Users/spike/Documents/diceware.wordlist.asc.asc'
SYMBOLS = [%w[1 ~ ! # $ % ^],
%w[2 & * ( ) - =],
%w[3 + [ ] \ { }],
%w[4 : ; " ' < >],
%w[5 ? / 0 1 2 3],
%w[6 4 5 6 7 8 9]]
alphanumeric = false
OptionParser.new do |opts|
opts.banner = "Usage: diceware [-s] [word-count]"
opts.on("-s", "--separators", "Use alphanumeric separators instead of spaces") do
alphanumeric = true
end
end.parse!
count = ARGV[0] || 2
if alphanumeric
separators = (count.to_i - 1).times.map { SYMBOLS[rand(6)][rand(6)] }
else
separators = [' '] * (count.to_i - 1)
end
words = {}
f = File.open(dice_file, "r")
f.each_line do |line|
next unless (line =~ /^\d\d\d\d\d/)
key,word = line.split
words[key] = word
end
passwords = count.to_i.times.map do
roll = (0..4).map{ 1 + rand(6) }.join
words[roll]
end
puts passwords.zip(separators).flatten.join
Disclaimer, this is a secure approach, but you can only trust random number generators so far. If you want a really secure approach break out the physical dice and do this with pen and paper.
Comments