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 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 &copy; 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
- #! /usr/bin/env ruby
1
+ #!/usr/bin/env ruby
2
2
 
3
- require 'passphrase/generator'
3
+ require "passphrase"
4
4
 
5
- p = Passphrase::Generator.new(ARGV)
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