spass 0.0.1 → 0.0.2
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.
- data/.rspec +2 -0
- data/History.md +19 -0
- data/README.md +1 -2
- data/bin/spass +61 -21
- data/lib/spass.rb +31 -25
- data/spass.gemspec +1 -1
- data/spec/spass_spec.rb +31 -28
- metadata +6 -4
data/.rspec
ADDED
data/History.md
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
History
|
2
|
+
=======
|
3
|
+
|
4
|
+
0.0.2
|
5
|
+
-----
|
6
|
+
|
7
|
+
- Added command-line options
|
8
|
+
- Multiple-phrase generation
|
9
|
+
- Optional inclusion of digits
|
10
|
+
- Restricted word length
|
11
|
+
|
12
|
+
|
13
|
+
0.0.1
|
14
|
+
-----
|
15
|
+
|
16
|
+
- Initial release
|
17
|
+
- Single phrase with random lowercase words
|
18
|
+
|
19
|
+
|
data/README.md
CHANGED
@@ -47,11 +47,10 @@ if you want to use words from a different dictionary, pass a second argument:
|
|
47
47
|
Future plans
|
48
48
|
------------
|
49
49
|
|
50
|
-
- Optional inclusion of uppercase
|
50
|
+
- Optional inclusion of uppercase and symbols
|
51
51
|
- Avoid word repetition
|
52
52
|
|
53
53
|
|
54
|
-
|
55
54
|
MIT License
|
56
55
|
-----------
|
57
56
|
|
data/bin/spass
CHANGED
@@ -1,13 +1,9 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
require 'spass'
|
3
|
+
require 'spass'
|
4
|
+
require 'optparse'
|
4
5
|
|
5
|
-
|
6
|
-
spass: Generate a passphrase with lowercase words
|
7
|
-
|
8
|
-
Usage:
|
9
|
-
|
10
|
-
$ spass <length> [path_to_dict]
|
6
|
+
HELP = <<EOF
|
11
7
|
|
12
8
|
This script generates a plain-English passphrase of at least the given length
|
13
9
|
(in characters), using random words from the given dictionary file
|
@@ -17,15 +13,12 @@ nonsensical).
|
|
17
13
|
|
18
14
|
Examples:
|
19
15
|
|
20
|
-
$ spass 12
|
16
|
+
$ spass -l 12
|
21
17
|
hive frighten
|
22
18
|
|
23
|
-
$ spass 24
|
19
|
+
$ spass -l 24
|
24
20
|
moppet castigator harvesters
|
25
21
|
|
26
|
-
$ spass 32
|
27
|
-
munificent icebound raymond clorets
|
28
|
-
|
29
22
|
A 32-character passphrase has about 120 bits of entropy, which is overkill for
|
30
23
|
most purposes. A 24-character passphrase clocks in at about 90 bits of entropy,
|
31
24
|
suitable for most high-security applications like root passwords or financial
|
@@ -35,18 +28,65 @@ your passphrase after generating one that you like:
|
|
35
28
|
http://rumkin.com/tools/password/passchk.php
|
36
29
|
|
37
30
|
Inspired by http://xkcd.com/936/
|
31
|
+
|
38
32
|
EOF
|
39
33
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
34
|
+
options = {
|
35
|
+
:length => 24,
|
36
|
+
:number => 10,
|
37
|
+
:dict => '/usr/share/dict/words',
|
38
|
+
:digits => false,
|
39
|
+
:chars => 10,
|
40
|
+
}
|
41
|
+
|
42
|
+
optparse = OptionParser.new do |opts|
|
43
|
+
opts.on('-l', '--length [NUM]', Integer,
|
44
|
+
"Ensure passphrases are at least NUM characters.",
|
45
|
+
"Default: #{options[:length]}") do |len|
|
46
|
+
options[:length] = len
|
47
|
+
end
|
48
|
+
|
49
|
+
opts.on('-n', '--number [NUM]', Integer,
|
50
|
+
"Generate NUM passphrases.",
|
51
|
+
"Default: #{options[:number]}") do |num|
|
52
|
+
options[:number] = num
|
53
|
+
end
|
54
|
+
|
55
|
+
opts.on('-f', '--file [FILE]', String,
|
56
|
+
"Read words from a FILE having one word per line.",
|
57
|
+
"Default: #{options[:dict]}") do |dict|
|
58
|
+
options[:dict] = dict
|
48
59
|
end
|
49
|
-
|
60
|
+
|
61
|
+
opts.on('-d', '--digits',
|
62
|
+
"Include random numbers from 1-99.",
|
63
|
+
"Default: False") do |digits|
|
64
|
+
options[:digits] = digits
|
65
|
+
end
|
66
|
+
|
67
|
+
opts.on('-c', '--chars NUM',
|
68
|
+
"Limit words to NUM characters in length.",
|
69
|
+
"Default: 10") do |chars|
|
70
|
+
options[:chars] = chars
|
71
|
+
end
|
72
|
+
|
73
|
+
opts.on_tail('-h', '--help', 'Display the help page') do
|
74
|
+
puts opts
|
75
|
+
puts HELP
|
76
|
+
exit 0
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
begin
|
81
|
+
optparse.parse!
|
82
|
+
rescue OptionParser::InvalidOption => e
|
83
|
+
puts optparse
|
84
|
+
puts "*** #{e}"
|
85
|
+
exit 1
|
50
86
|
end
|
51
87
|
|
88
|
+
sp = Generator.new(options[:dict], options)
|
89
|
+
options[:number].times do
|
90
|
+
puts sp.generate(options[:length], :digits=>options[:digits])
|
91
|
+
end
|
52
92
|
|
data/lib/spass.rb
CHANGED
@@ -1,41 +1,47 @@
|
|
1
1
|
|
2
|
-
class
|
3
|
-
|
4
|
-
|
5
|
-
def initialize(dict_path='/usr/share/dict/words')
|
6
|
-
@dict_path =
|
7
|
-
|
8
|
-
raise RuntimeError, "Cannot find dict file: #{@dict_path}"
|
9
|
-
end
|
10
|
-
@dict_lines = `wc -l #{@dict_path}`.split.first.to_i
|
2
|
+
class Generator
|
3
|
+
# Create a Generator using the given word dictionary, returning
|
4
|
+
# only words that match the given regular expression
|
5
|
+
def initialize(dict_path='/usr/share/dict/words', options={})
|
6
|
+
@dict_path = dict_path
|
7
|
+
@dict = read_dict(dict_path, options)
|
11
8
|
end
|
12
9
|
|
13
|
-
#
|
14
|
-
#
|
15
|
-
|
16
|
-
|
10
|
+
# Read a word dictionary from the given file, and return an array
|
11
|
+
# of elements that match the allowed regex. Raise an exception if
|
12
|
+
# the given dictionary file cannot be found.
|
13
|
+
def read_dict(path, options={})
|
14
|
+
allowed = options[:allowed] || /^[a-z]+$/
|
15
|
+
chars = (options[:chars] || 10).to_i
|
16
|
+
if !File.file?(path)
|
17
|
+
raise RuntimeError, "Cannot find dict file: #{path}"
|
18
|
+
end
|
19
|
+
dict = []
|
20
|
+
IO.readlines(path).each do |line|
|
21
|
+
line.strip!
|
22
|
+
if line.length <= chars && line =~ allowed
|
23
|
+
dict << line
|
24
|
+
end
|
25
|
+
end
|
26
|
+
return dict
|
17
27
|
end
|
18
28
|
|
19
|
-
# Return a random word from the dictionary
|
29
|
+
# Return a random word from the dictionary
|
20
30
|
def random_word
|
21
|
-
|
22
|
-
`#{cmd}`.chomp.downcase
|
31
|
+
@dict[rand(@dict.count)]
|
23
32
|
end
|
24
33
|
|
25
|
-
# Return a random
|
26
|
-
def
|
27
|
-
|
28
|
-
while word =~ /[^a-z]/
|
29
|
-
word = random_word
|
30
|
-
end
|
31
|
-
return word
|
34
|
+
# Return a random number from 1 to 99
|
35
|
+
def random_number
|
36
|
+
1 + rand(99)
|
32
37
|
end
|
33
38
|
|
34
39
|
# Generate a passphrase of at least the given length in characters
|
35
|
-
def generate(length)
|
40
|
+
def generate(length, options={})
|
36
41
|
phrase = ''
|
37
42
|
while phrase.length < length + 1 # to account for trailing space
|
38
|
-
phrase +=
|
43
|
+
phrase += "#{random_word} "
|
44
|
+
phrase += "#{random_number} " if options[:digits]
|
39
45
|
end
|
40
46
|
return phrase.chomp
|
41
47
|
end
|
data/spass.gemspec
CHANGED
data/spec/spass_spec.rb
CHANGED
@@ -2,39 +2,33 @@ require 'spass'
|
|
2
2
|
|
3
3
|
TEST_DICT = File.expand_path('../data/ten_words', __FILE__)
|
4
4
|
|
5
|
-
describe
|
6
|
-
before(:
|
7
|
-
@sp =
|
5
|
+
describe Generator do
|
6
|
+
before(:all) do
|
7
|
+
@sp = Generator.new(TEST_DICT)
|
8
8
|
end
|
9
9
|
|
10
10
|
|
11
|
-
describe "#
|
12
|
-
it "
|
13
|
-
|
14
|
-
|
15
|
-
@sp.dict_path.should == path
|
11
|
+
describe "#read_dict" do
|
12
|
+
it "returns an array of lines from the given file" do
|
13
|
+
@sp.read_dict(TEST_DICT).should == [
|
14
|
+
'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten']
|
16
15
|
end
|
17
16
|
|
18
|
-
it "
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
end.should raise_error(RuntimeError, /Cannot find dict file/)
|
17
|
+
it "only returns words matching a regex" do
|
18
|
+
@sp.read_dict(TEST_DICT, :allowed=>/^t/).should == ['two', 'three', 'ten']
|
19
|
+
@sp.read_dict(TEST_DICT, :allowed=>/^f/).should == ['four', 'five']
|
20
|
+
@sp.read_dict(TEST_DICT, :allowed=>/e$/).should == ['one', 'three', 'five', 'nine']
|
23
21
|
end
|
24
|
-
end
|
25
|
-
|
26
22
|
|
27
|
-
|
28
|
-
|
29
|
-
100.times do
|
30
|
-
@sp.random_line.should be >= 1
|
31
|
-
end
|
23
|
+
it "only returns words with N chars or fewer" do
|
24
|
+
@sp.read_dict(TEST_DICT, :chars=>3).should == ['one', 'two', 'six', 'ten']
|
32
25
|
end
|
33
26
|
|
34
|
-
it "
|
35
|
-
|
36
|
-
|
37
|
-
|
27
|
+
it "raises an exception when the file does not exist" do
|
28
|
+
path = File.expand_path('non/existent/path')
|
29
|
+
lambda do
|
30
|
+
@sp.read_dict(path)
|
31
|
+
end.should raise_error(RuntimeError, /Cannot find dict file/)
|
38
32
|
end
|
39
33
|
end
|
40
34
|
|
@@ -48,14 +42,17 @@ describe SPass do
|
|
48
42
|
end
|
49
43
|
|
50
44
|
|
51
|
-
describe "#
|
52
|
-
it "
|
53
|
-
|
54
|
-
@sp.
|
45
|
+
describe "#random_number" do
|
46
|
+
it "returns a number between 1 and 999" do
|
47
|
+
1000.times do
|
48
|
+
num = @sp.random_number
|
49
|
+
num.should be >= 1
|
50
|
+
num.should be <= 99
|
55
51
|
end
|
56
52
|
end
|
57
53
|
end
|
58
54
|
|
55
|
+
|
59
56
|
describe "#generate" do
|
60
57
|
it "is at least the given length" do
|
61
58
|
[10, 15, 20, 25, 30, 35, 40].each do |len|
|
@@ -64,6 +61,12 @@ describe SPass do
|
|
64
61
|
end
|
65
62
|
end
|
66
63
|
end
|
64
|
+
|
65
|
+
it "includes digits when :digits is true" do
|
66
|
+
10.times do
|
67
|
+
@sp.generate(24, :digits=>true).should =~ /^([a-z]+ [0-9]+ ?)+$/
|
68
|
+
end
|
69
|
+
end
|
67
70
|
end
|
68
71
|
end
|
69
72
|
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: spass
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 27
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version: 0.0.
|
9
|
+
- 2
|
10
|
+
version: 0.0.2
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Eric Pierce
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-
|
18
|
+
date: 2011-11-01 00:00:00 -06:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -58,8 +58,10 @@ extra_rdoc_files: []
|
|
58
58
|
|
59
59
|
files:
|
60
60
|
- .gitignore
|
61
|
+
- .rspec
|
61
62
|
- Gemfile
|
62
63
|
- Gemfile.lock
|
64
|
+
- History.md
|
63
65
|
- README.md
|
64
66
|
- bin/spass
|
65
67
|
- lib/spass.rb
|