passphrase 0.1.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- checksums.yaml.gz.sig +1 -0
- data.tar.gz.sig +1 -0
- data/CHANGELOG.md +35 -0
- data/LICENSE +19 -0
- data/README.md +178 -0
- data/bin/passphrase +3 -10
- data/lib/passphrase.rb +11 -0
- data/lib/passphrase/CLI.rb +76 -0
- data/lib/passphrase/default.rb +15 -0
- data/lib/passphrase/diceware_method.rb +67 -0
- data/lib/passphrase/diceware_random.rb +117 -0
- data/lib/passphrase/language_query.rb +26 -0
- data/lib/passphrase/passphrase.rb +80 -0
- data/lib/passphrase/version.rb +3 -12
- data/lib/passphrase/word_query.rb +21 -0
- data/lib/passphrase/wordlist/words.sqlite3 +0 -0
- data/spec/passphrase/cli_spec.rb +47 -0
- data/spec/passphrase/diceware_method_spec.rb +61 -0
- data/spec/passphrase/diceware_random_spec.rb +124 -0
- data/spec/passphrase/language_query_spec.rb +48 -0
- data/spec/passphrase/passphrase_spec.rb +118 -0
- data/spec/passphrase/word_query_spec.rb +30 -0
- data/spec/spec_helper.rb +91 -0
- metadata +168 -148
- metadata.gz.sig +0 -0
- data/Gemfile +0 -14
- data/Gemfile.lock +0 -16
- data/LICENSE.txt +0 -20
- data/README.rdoc +0 -62
- data/Rakefile +0 -33
- data/VERSION +0 -1
- data/lib/passphrase/data.rb +0 -1632
- data/lib/passphrase/generator.rb +0 -81
- data/lib/passphrase/options.rb +0 -65
- data/lib/passphrase/random.rb +0 -56
- data/lib/passphrase/wordlist.rb +0 -30
- data/test/test_options.rb +0 -33
- data/test/test_wordlist.rb +0 -17
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 04b13682c8ca0850aec87893eacd4e3ef53bcdd8
|
4
|
+
data.tar.gz: e758d10b4ae02a3b76906512fbd545fd21f461dc
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ed1766c82ecf11aab014e0b41fdccb00504f13456671130b5da70f013369d855673a2a4e84548a5c65edda150161b797503e1bcc62e606f99a2d01c29e05e626
|
7
|
+
data.tar.gz: 59d155d31002868739678ddd654ebb2196ebc74e5a852a56afa216f69e18871b8ae311e0194a9de5850fee1dd3329b68b9b1dcc72a2994be83b0442c0cbd8577
|
checksums.yaml.gz.sig
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
|��)�VMg�Ų�<R�)�S�MLy�|�9����/�����ɟ��.�&:��,����<�Kζ�?��7&�t]f��K1�uN��sc���S��I��x<т�iBtGo&J�O���~�[�w��ٕ�O�}�I�ˋ��+�Hg��ŅEG{<��~�R|��5p�.�gMn�,���u��z|(����8;w�.��ౙ���w3`]�/.�u��Ѓ��_��-�Xa�Ϭ�"�u��j �[��<N�nC
|
data.tar.gz.sig
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# Change Log
|
2
|
+
All notable changes to this project will be documented in this file. This
|
3
|
+
project adheres to [Semantic Versioning](http://semver.org/).
|
4
|
+
|
5
|
+
## Version 1.0.0 - 2015-04-06
|
6
|
+
This release is a complete re-write. None of the code from the previous
|
7
|
+
release was retained.
|
8
|
+
|
9
|
+
### Added
|
10
|
+
- Added YARD-style documentation throughout.
|
11
|
+
- Added an RSpec test suite using the newer syntax that comes with
|
12
|
+
version ~> 3.0.
|
13
|
+
- Added option `--use-random|-r` for specifying RANDOM.ORG as a source of
|
14
|
+
random numbers.
|
15
|
+
- The gem is cryptographically signed.
|
16
|
+
|
17
|
+
### Changed
|
18
|
+
- Absolutely everything!
|
19
|
+
- Changed the default source for random numbers from the RANDOM.ORG web site
|
20
|
+
to the SecureRandom standard library.
|
21
|
+
- Diceware method selects words from 15 multi-lingual wordlists.
|
22
|
+
- Wordlists are stored in an SQLite 3 database file.
|
23
|
+
- Code organized to conform to current RubyGems guidelines.
|
24
|
+
- Re-designed the library to make it easier to use in client code.
|
25
|
+
|
26
|
+
### Removed
|
27
|
+
- Removed option `--mix|-m` for mixing in a capital letter, number, and a
|
28
|
+
special character.
|
29
|
+
- Removed option `--local|-l` to force the use of the SecureRandom standard
|
30
|
+
library to generate random numbers.
|
31
|
+
- Replaced the hash used to store the wordlists with an external SQLite 3
|
32
|
+
database file.
|
33
|
+
|
34
|
+
## Version 0.1.0 - 2011-04-25
|
35
|
+
No change log was maintained for this and prior versions.
|
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2015 Edmund Sumbar
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
11
|
+
copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
19
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,178 @@
|
|
1
|
+
# Passphrase
|
2
|
+
Use Passphrase to generate a passphrase for SSH or GPG keys. For example, on
|
3
|
+
the command-line, run
|
4
|
+
|
5
|
+
$ passphrase --num-words=4
|
6
|
+
dokusi uolgo allunga totalisa
|
7
|
+
|
8
|
+
or programmatically,
|
9
|
+
|
10
|
+
require "passphrase"
|
11
|
+
p = Passphrase::Passphrase.new(number_of_words: 4)
|
12
|
+
p.generate
|
13
|
+
passphrase = p.passphrase
|
14
|
+
|
15
|
+
Eliminate the spaces between words to use the result as a potential password.
|
16
|
+
|
17
|
+
## Installation
|
18
|
+
The Passphrase command-line tool and library can be installed with
|
19
|
+
|
20
|
+
$ gem install passphrase
|
21
|
+
|
22
|
+
However, because the gem is cryptographically signed to prevent tampering, the
|
23
|
+
preferred installation command should include the `--trust-policy` security
|
24
|
+
option, which causes the gem to be verified before being installed. To invoke
|
25
|
+
this option, you must first add my public key `esumbar.pem` to your list of
|
26
|
+
trusted certificates, as follows.
|
27
|
+
|
28
|
+
$ gem cert --add <(curl -Ls https://raw.githubusercontent.com/esumbar/passphrase/master/certs/esumbar.pem)
|
29
|
+
|
30
|
+
Finally, specify the appropriate security level when installing.
|
31
|
+
|
32
|
+
$ gem install passphrase --trust-policy MediumSecurity
|
33
|
+
|
34
|
+
Using `MediumSecurity` rather than `HighSecurity` omits dependent gems, in
|
35
|
+
this case `sqlite3`, which is not signed, from the verification process.
|
36
|
+
|
37
|
+
## Basic usage
|
38
|
+
### Command-line tool
|
39
|
+
|
40
|
+
$ passphrase --help
|
41
|
+
Usage: passphrase [options]
|
42
|
+
-n, --num-words=NUM Number of words in passphrase 3..10
|
43
|
+
(default: 5)
|
44
|
+
-r, --[no-]random-org Use random.org to generate random numbers
|
45
|
+
(default: --no-random-org)
|
46
|
+
-h, --help Show this message
|
47
|
+
-v, --version Show version
|
48
|
+
|
49
|
+
$ passphrase
|
50
|
+
sinmak termyne ismus affidavo recur
|
51
|
+
|
52
|
+
$ passphrase -n 4
|
53
|
+
apaisado vermouth seemag ebelik
|
54
|
+
|
55
|
+
### Ruby library
|
56
|
+
|
57
|
+
require "passphrase"
|
58
|
+
|
59
|
+
# generate a passphrase with default options
|
60
|
+
p = Passphrase::Passphrase.new
|
61
|
+
p.generate
|
62
|
+
puts "passphrase: #{p}"
|
63
|
+
puts "passphrase internals: #{p.inspect}"
|
64
|
+
|
65
|
+
# generate three four-word passphrases using RANDOM.ORG
|
66
|
+
options = { number_of_words: 4, use_random_org: true }
|
67
|
+
p = Passphrase::Passphrase.new(options)
|
68
|
+
passphrase1 = p.generate.passphrase
|
69
|
+
passphrase2 = p.generate.passphrase
|
70
|
+
passphrase3 = p.generate.passphrase
|
71
|
+
|
72
|
+
# generate an array of six-word passphrases
|
73
|
+
passphrase_array = Array.new(100)
|
74
|
+
Passphrase::Passphrase.new(number_of_words: 6) do |p|
|
75
|
+
passphrase_array.map! { |e| p.generate.passphrase }
|
76
|
+
end
|
77
|
+
|
78
|
+
## Background
|
79
|
+
Passphrase implements the [Diceware
|
80
|
+
Method](http://world.std.com/~reinhold/diceware.html) which constructs a
|
81
|
+
passphrase by randomly selecting words from a predefined list of more-or-less
|
82
|
+
meaningful words. Because the words are meaningful, the resulting passphrase
|
83
|
+
is easier to remember and type. And because the selection is random, it is
|
84
|
+
more secure. The more words in a passphrase, the better. However, four or five
|
85
|
+
is optimal.
|
86
|
+
|
87
|
+
The original Diceware wordlist from 1995 contained only English words. Since
|
88
|
+
then, [Diceware-compatible wordlists in other
|
89
|
+
languages](https://blog.agilebits.com/2013/04/16/1password-hashcat-strong-master-passwords/expanded-dicelists/) have been published and are incorporated
|
90
|
+
into Passphrase. Unfortunately, the enhanced security of the result comes at
|
91
|
+
the expense of having to deal with words from potentially unfamiliar languages.
|
92
|
+
_Note that these are not merely translations of the original Diceware
|
93
|
+
wordlist._
|
94
|
+
|
95
|
+
Passphrase includes wordlists in the following 15 languages (stored in an
|
96
|
+
SQLite 3 database file).
|
97
|
+
|
98
|
+
1. Afrikaans
|
99
|
+
2. Croatian
|
100
|
+
3. Czech
|
101
|
+
4. Diceware (the original Diceware wordlist)
|
102
|
+
5. English
|
103
|
+
6. Finnish
|
104
|
+
7. French
|
105
|
+
8. Italian
|
106
|
+
9. Japanese
|
107
|
+
10. Latin
|
108
|
+
11. Norwegian
|
109
|
+
12. Polish
|
110
|
+
13. Spanish
|
111
|
+
14. Swedish
|
112
|
+
15. Turkish
|
113
|
+
|
114
|
+
Except in the original Diceware list, all words are lower case, comprised of
|
115
|
+
characters from the ascii set `[a-z]`. The original Diceware list includes
|
116
|
+
some "words" with numerical and punctuation characters. No word appears more
|
117
|
+
than once in this set of wordlists.
|
118
|
+
|
119
|
+
The _dice_ in Diceware refers to the act of rolling a die to achieve
|
120
|
+
randomness. A sequence of five consecutive rolls has 7776 (6<sup>5</sup>)
|
121
|
+
possible outcomes. Each combination maps to one line in a given wordlist, and
|
122
|
+
each line, in turn, is composed of between 1 and 15 words (depending on the
|
123
|
+
language, the average being 5).
|
124
|
+
|
125
|
+
Therefore, to select a word, Passphrase
|
126
|
+
|
127
|
+
1. randomly selects one of 15 languages
|
128
|
+
2. randomly selects one of 7776 lines from the corresponding wordlist
|
129
|
+
3. randomly selects one word from the corresponding line
|
130
|
+
|
131
|
+
This leads to roughly 10<sup>28</sup> possible five-word passphrases, for
|
132
|
+
example.
|
133
|
+
|
134
|
+
Passphrase simulates the rolls of a die by using random numbers from one of
|
135
|
+
two sources. The default is to use the standard SecureRandom class. You also
|
136
|
+
have the option of requesting random numbers from
|
137
|
+
[RANDOM.ORG](http://www.random.org). However, because RANDOM.ORG depends on
|
138
|
+
network access, it is susceptible to network problems, and is also slower.
|
139
|
+
|
140
|
+
## Contributing to Passphrase
|
141
|
+
### Getting started
|
142
|
+
After forking the [repository on
|
143
|
+
GitHub](https://github.com/esumbar/passphrase), go to your local copy of the
|
144
|
+
repository and execute `bundle install` to ensure that all development gems
|
145
|
+
are installed.
|
146
|
+
|
147
|
+
Then, run `rake database` to create and populate the wordlist database. Note
|
148
|
+
that even though raw data files for Hungarian and Swahili exist, these
|
149
|
+
languages are excluded from the database because they do not have a full
|
150
|
+
compliment of 7776 entries.
|
151
|
+
|
152
|
+
To run the command-line tool within the repository directory, try `ruby -Ilib
|
153
|
+
bin/passphrase`. You can also experiment with the library in irb. For example,
|
154
|
+
|
155
|
+
$ irb -Ilib -rpassphrase
|
156
|
+
>> p = Passphrase::Passphrase.new(number_of_words: 3)
|
157
|
+
=> {:passphrase=>"", :number_of_words=>0, :word_origins=>{}}
|
158
|
+
>> p.generate
|
159
|
+
=> {:passphrase=>"bolt flanella ininaen", :number_of_words=>3,...}
|
160
|
+
>> p.passphrase
|
161
|
+
=> "bolt flanella ininaen"
|
162
|
+
|
163
|
+
Run the tests with `rake spec`.
|
164
|
+
|
165
|
+
### Future enhancements
|
166
|
+
In order to make passphrases more acceptable as passwords, a feature could be
|
167
|
+
added to the library to upcase a random letter, replace a random alphabetic
|
168
|
+
character with a numeric character (if one does not already exist), and mix in
|
169
|
+
a special character, like "`$`" or "`%`" etc. The command-line option could be
|
170
|
+
called `--passwordize|-p`.
|
171
|
+
|
172
|
+
## Changelog
|
173
|
+
See {file:CHANGELOG.md} for a list of changes.
|
174
|
+
|
175
|
+
## License
|
176
|
+
Passphrase © 2011-2015 by [Edmund Sumbar](mailto:esumbar@gmail.com).
|
177
|
+
Passphrase is licensed under the MIT license. Please see the {file:LICENSE}
|
178
|
+
document for more information.
|
data/bin/passphrase
CHANGED
@@ -1,12 +1,5 @@
|
|
1
|
-
|
1
|
+
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
require
|
3
|
+
require "passphrase"
|
4
4
|
|
5
|
-
|
6
|
-
p.run
|
7
|
-
if p.mix?
|
8
|
-
puts "unmixed phrase => #{p.unmixed_phrase}"
|
9
|
-
puts "passphrase => #{p.phrase}"
|
10
|
-
else
|
11
|
-
puts "passphrase => #{p.phrase}"
|
12
|
-
end
|
5
|
+
Passphrase::CLI.parse(ARGV)
|
data/lib/passphrase.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require "passphrase/default"
|
2
|
+
require "passphrase/diceware_method"
|
3
|
+
require "passphrase/diceware_random"
|
4
|
+
require "passphrase/language_query"
|
5
|
+
require "passphrase/passphrase"
|
6
|
+
require "passphrase/version"
|
7
|
+
require "passphrase/word_query"
|
8
|
+
|
9
|
+
module Passphrase
|
10
|
+
autoload(:CLI, "passphrase/CLI")
|
11
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require "optparse"
|
2
|
+
|
3
|
+
module Passphrase
|
4
|
+
# The CLI class encapsulates a simple command line interface to the
|
5
|
+
# {Passphrase} library. The class method {parse} merges any given command
|
6
|
+
# line arguments with a hash of default options. The resulting hash is then
|
7
|
+
# used to instantiate a Passphrase object and generate one passphrase, which
|
8
|
+
# is printed on standard output.
|
9
|
+
# @example
|
10
|
+
# require "passphrase"
|
11
|
+
# Passphrase::CLI.parse(ARGV)
|
12
|
+
class CLI
|
13
|
+
# @param args [Array<String>] array of command line elements, typically
|
14
|
+
# given by ARGV (may be empty)
|
15
|
+
# @return [void]
|
16
|
+
def self.parse(args)
|
17
|
+
options = Default.options
|
18
|
+
|
19
|
+
default_number_of_words = Default.options[:number_of_words]
|
20
|
+
default_random_org = Default.options[:use_random_org] ? "--random-org" : "--no-random-org"
|
21
|
+
|
22
|
+
parser = OptionParser.new do |opts|
|
23
|
+
opts.banner = "Usage: passphrase [options]"
|
24
|
+
opts.on(:REQUIRED, "-n NUM", "--num-words=NUM", Integer,
|
25
|
+
"Number of words in passphrase #{Default.number_range}",
|
26
|
+
"(default: #{default_number_of_words})") do |n|
|
27
|
+
options[:number_of_words] = n
|
28
|
+
end
|
29
|
+
opts.on(:NONE, "-r", "--[no-]random-org",
|
30
|
+
"Use random.org to generate random numbers",
|
31
|
+
"(default: #{default_random_org})") do |r|
|
32
|
+
options[:use_random_org] = r
|
33
|
+
end
|
34
|
+
opts.on_tail("-h", "--help", "Show this message") do
|
35
|
+
puts opts
|
36
|
+
exit(0)
|
37
|
+
end
|
38
|
+
opts.on_tail("-v", "--version", "Show version") do
|
39
|
+
puts VERSION
|
40
|
+
exit(0)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
begin
|
45
|
+
parser.parse!(args)
|
46
|
+
validate_number_of_words(options)
|
47
|
+
puts Passphrase.new(options).generate
|
48
|
+
rescue OptionParser::InvalidOption => e
|
49
|
+
handle_error(e)
|
50
|
+
rescue OptionParser::MissingArgument => e
|
51
|
+
handle_error(e)
|
52
|
+
# gracefully handle exit(0) from --help and --version options
|
53
|
+
rescue SystemExit => e
|
54
|
+
exit(e.status)
|
55
|
+
rescue Exception => e
|
56
|
+
handle_error(e)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.validate_number_of_words(options)
|
61
|
+
number_of_words = options[:number_of_words]
|
62
|
+
unless Default.number_range.include?(number_of_words)
|
63
|
+
raise "number of words out of range #{Default.number_range}"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.handle_error(error)
|
68
|
+
STDERR.puts "ERROR: #{error.message}"
|
69
|
+
exit(1)
|
70
|
+
end
|
71
|
+
|
72
|
+
class << self
|
73
|
+
private :validate_number_of_words, :handle_error
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Passphrase
|
2
|
+
class Default
|
3
|
+
class << self
|
4
|
+
# @return [Hash] the default options that the command line interface
|
5
|
+
# uses to instantiate a {Passphrase} object in {CLI.parse}
|
6
|
+
attr_reader :options
|
7
|
+
# @return [Range] an arbitrary range specifying the allowable number of
|
8
|
+
# words in a passphrase, referenced by the {CLI.parse} method
|
9
|
+
attr_reader :number_range
|
10
|
+
end
|
11
|
+
|
12
|
+
@options = { number_of_words: 5, use_random_org: nil }
|
13
|
+
@number_range = (3..10)
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require "sqlite3"
|
2
|
+
|
3
|
+
module Passphrase
|
4
|
+
# This class implements the Diceware Method for generating a passphrase. It
|
5
|
+
# selects words from a multi-language wordlist stored in an SQLite 3
|
6
|
+
# database. A special {DicewareRandom} class is provided to work with this
|
7
|
+
# class to simulate rolls of a die.
|
8
|
+
class DicewareMethod
|
9
|
+
# A convenience method for simultaneously creating a new DicewareMethod
|
10
|
+
# object and calling {#run}
|
11
|
+
# @return (see #run)
|
12
|
+
def self.run(options)
|
13
|
+
new(options).run
|
14
|
+
end
|
15
|
+
|
16
|
+
# @param options [Hash] the options passed from the {Passphrase} object
|
17
|
+
def initialize(options)
|
18
|
+
@number_of_words = options[:number_of_words]
|
19
|
+
@random = DicewareRandom.new(options[:use_random_org])
|
20
|
+
setup_database
|
21
|
+
setup_queries
|
22
|
+
end
|
23
|
+
|
24
|
+
# Runs the Diceware method and returns its result to the calling
|
25
|
+
# {Passphrase} object.
|
26
|
+
# @return [Array<Array>] a three element array of the (1) random languages,
|
27
|
+
# (2) random die rolls, and (3) corresponding random words
|
28
|
+
def run
|
29
|
+
get_random_languages
|
30
|
+
get_random_die_rolls
|
31
|
+
select_words_from_wordlist
|
32
|
+
[@random_languages, @random_die_rolls, @selected_words]
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def setup_database
|
38
|
+
wordlist_file = "wordlist/words.sqlite3"
|
39
|
+
wordlist_path = File.join(File.dirname(__FILE__), wordlist_file)
|
40
|
+
raise "Wordlist database not found" unless File.exist?(wordlist_path)
|
41
|
+
@db = SQLite3::Database.new(wordlist_path, readonly: true)
|
42
|
+
end
|
43
|
+
|
44
|
+
def setup_queries
|
45
|
+
@languages = LanguageQuery.new(@db)
|
46
|
+
@words = WordQuery.new(@db)
|
47
|
+
end
|
48
|
+
|
49
|
+
def get_random_languages
|
50
|
+
@random_languages = @random.indices(@number_of_words, @languages.count)
|
51
|
+
@random_languages.map! { |index| @languages[index] }
|
52
|
+
end
|
53
|
+
|
54
|
+
def get_random_die_rolls
|
55
|
+
@random_die_rolls = @random.die_rolls(@number_of_words)
|
56
|
+
end
|
57
|
+
|
58
|
+
def select_words_from_wordlist
|
59
|
+
@selected_words = []
|
60
|
+
@random_languages.each_with_index do |language, index|
|
61
|
+
die_rolls = @random_die_rolls[index]
|
62
|
+
selection = @words.where(language: language, die_rolls: die_rolls)
|
63
|
+
@selected_words << selection.split.sample
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
require "securerandom"
|
2
|
+
require "open-uri"
|
3
|
+
|
4
|
+
module Passphrase
|
5
|
+
# The DicewareRandom class supplies random numbers in two different formats
|
6
|
+
# through two instance methods {#indices} and {#die_rolls} for use by the
|
7
|
+
# {DicewareMethod} class. Depending on the value of the flag used to
|
8
|
+
# instantiate the DicewareRandom class, the unformatted raw random numbers
|
9
|
+
# are generated either by the standard SecureRandom class or retrieved from
|
10
|
+
# the RANDOM.ORG web site.
|
11
|
+
class DicewareRandom
|
12
|
+
class << self
|
13
|
+
# @return [Integer] the number of times the RANDOM.ORG site is accessed
|
14
|
+
# by all instances of the DicewareRandom class
|
15
|
+
attr_accessor :random_org_requests
|
16
|
+
end
|
17
|
+
|
18
|
+
@random_org_requests = 0
|
19
|
+
|
20
|
+
# @param use_random_org [Boolean] a flag that triggers the use of
|
21
|
+
# RANDOM.ORG for generating random numbers
|
22
|
+
def initialize(use_random_org=nil)
|
23
|
+
@random_org_uri = "https://www.random.org"
|
24
|
+
use_random_org ? setup_remote_generator : setup_local_generator
|
25
|
+
end
|
26
|
+
|
27
|
+
# Returns an array of random numbers that can index into the array of
|
28
|
+
# available languages. The number of elements in the array equals the
|
29
|
+
# number of words specified for the passphrase.
|
30
|
+
# @param number_of_words [Integer] the desired number of words in the
|
31
|
+
# passphrase
|
32
|
+
# @param number_of_languages [Integer] the number of available languages
|
33
|
+
# @return [Array<Integer>] an array of random number indices
|
34
|
+
def indices(number_of_words, number_of_languages)
|
35
|
+
generate_random_numbers(number_of_words, number_of_languages - 1)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Returns an array of strings where each string comprises five numeric
|
39
|
+
# characters, each one representing one roll of a die. The number of
|
40
|
+
# elements in the array equals the number of words specified for the
|
41
|
+
# passphrase.
|
42
|
+
# @param number_of_words [Integer] the desired number of words in the
|
43
|
+
# passphrase
|
44
|
+
# @return [Array<String>] an array of strings each one of which represents
|
45
|
+
# five rolls of a die
|
46
|
+
def die_rolls(number_of_words)
|
47
|
+
# The Diceware method specifies five rolls of the die for each word.
|
48
|
+
die_rolls_per_word = 5
|
49
|
+
total_die_rolls = number_of_words * die_rolls_per_word
|
50
|
+
die_roll_sequence = generate_random_numbers(total_die_rolls, 6, 1)
|
51
|
+
group_die_rolls(die_roll_sequence, number_of_words, die_rolls_per_word)
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def group_die_rolls(seq, number_of_words, die_rolls_per_word)
|
57
|
+
die_rolls = []
|
58
|
+
number_of_words.times do
|
59
|
+
rolls_per_word = seq.shift(die_rolls_per_word)
|
60
|
+
die_rolls << rolls_per_word.reduce("") { |roll, die| roll << die.to_s }
|
61
|
+
end
|
62
|
+
die_rolls
|
63
|
+
end
|
64
|
+
|
65
|
+
def setup_remote_generator
|
66
|
+
self.class.class_eval do
|
67
|
+
def generate_random_numbers(count, maximum, minimum=0)
|
68
|
+
# Check quota before proceeding and thereafter every 1000 uses to
|
69
|
+
# comply with random.org guidelines.
|
70
|
+
check_random_org_quota if self.class.random_org_requests % 1000 == 0
|
71
|
+
params = []
|
72
|
+
params << "num=#{count}"
|
73
|
+
params << "min=#{minimum}"
|
74
|
+
params << "max=#{maximum}"
|
75
|
+
params << "col=1"
|
76
|
+
params << "base=10"
|
77
|
+
params << "format=plain"
|
78
|
+
params << "rnd=new"
|
79
|
+
query = "/integers/?#{params.join('&')}"
|
80
|
+
array_of_random_numbers = []
|
81
|
+
open("#{@random_org_uri}#{query}") do |data|
|
82
|
+
data.each_line do |line|
|
83
|
+
array_of_random_numbers << line.to_i
|
84
|
+
end
|
85
|
+
end
|
86
|
+
self.class.random_org_requests += 1
|
87
|
+
array_of_random_numbers
|
88
|
+
end
|
89
|
+
|
90
|
+
def check_random_org_quota
|
91
|
+
query = "/quota/?format=plain"
|
92
|
+
open("#{@random_org_uri}#{query}") do |data|
|
93
|
+
quota = data.gets.chomp.to_i
|
94
|
+
over_quota_message = "RANDOM.ORG over quota, try again in 10 minutes"
|
95
|
+
raise "ERROR: #{over_quota_message}" if quota < 0
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
private :generate_random_numbers
|
100
|
+
private :check_random_org_quota
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def setup_local_generator
|
105
|
+
self.class.class_eval do
|
106
|
+
def generate_random_numbers(count, maximum, minimum=0)
|
107
|
+
max = maximum - minimum + 1
|
108
|
+
offset = minimum
|
109
|
+
# array_of_random_numbers
|
110
|
+
Array.new(count).map { |e| SecureRandom.random_number(max) + offset }
|
111
|
+
end
|
112
|
+
|
113
|
+
private :generate_random_numbers
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|