keepass-password-generator 0.1.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.
- data/.gitignore +6 -0
- data/.rspec +2 -0
- data/.yardopts +3 -0
- data/CHANGELOG.md +8 -0
- data/Gemfile +3 -0
- data/MIT-LICENSE.txt +19 -0
- data/README.md +58 -0
- data/Rakefile +12 -0
- data/keepass-password-generator.gemspec +28 -0
- data/lib/keepass-password-generator.rb +1 -0
- data/lib/keepass/password.rb +58 -0
- data/lib/keepass/password/char_set.rb +91 -0
- data/lib/keepass/password/generator.rb +111 -0
- data/lib/keepass/password/version.rb +5 -0
- data/lib/keepass/random.rb +38 -0
- data/spec/char_set_spec.rb +38 -0
- data/spec/generator_spec.rb +117 -0
- data/spec/random_spec.rb +49 -0
- data/spec/spec_helper.rb +32 -0
- metadata +146 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/.yardopts
ADDED
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/MIT-LICENSE.txt
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2011 John Nishinaga and Pat Deegan, PhD & Associates, LLC.
|
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
|
11
|
+
all 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
|
19
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
# keepass-password-generator
|
2
|
+
|
3
|
+
Generate passwords using KeePass password generator patterns.
|
4
|
+
|
5
|
+
## RubyGems installation
|
6
|
+
|
7
|
+
gem install keepass-password-generator
|
8
|
+
|
9
|
+
## Bundler installation
|
10
|
+
|
11
|
+
In your `Gemfile`:
|
12
|
+
|
13
|
+
gem 'keepass-password-generator'
|
14
|
+
|
15
|
+
Install bundled gems:
|
16
|
+
|
17
|
+
bundle
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
require 'keepass/password'
|
22
|
+
|
23
|
+
KeePass::Password.generate('A{6}s')
|
24
|
+
#=> "Un2hd#t"
|
25
|
+
|
26
|
+
See <http://keepass.info/help/base/pwgenerator.html> for information about KeePass patterns.
|
27
|
+
|
28
|
+
## Examples
|
29
|
+
|
30
|
+
A 40-bit WEP key:
|
31
|
+
|
32
|
+
KeePass::Password.generate('h{10}')
|
33
|
+
#=> "ae6929dc0e"
|
34
|
+
|
35
|
+
A random MAC address:
|
36
|
+
|
37
|
+
KeePass::Password.generate('HH\-HH\-HH\-HH\-HH\-HH', :permute => false)
|
38
|
+
#=> "0D-4D-32-64-EB-7D"
|
39
|
+
|
40
|
+
A password with 10 alphanumeric characters, where at least 2 are upper case and at least are 2 lower case characters:
|
41
|
+
|
42
|
+
KeePass::Password.generate('uullA{6}')
|
43
|
+
#=> "us2j1nTIQT"
|
44
|
+
|
45
|
+
A password with 20 alphanumeric and symbol characters, without any lookalike characters (e.g., I and |):
|
46
|
+
|
47
|
+
KeePass::Password.generate('[As]{20}', :remove_lookalikes => true)
|
48
|
+
#=> "-2~[+Rze{hZezk(\\nZ-W"
|
49
|
+
|
50
|
+
Invalid patterns raise an exception:
|
51
|
+
|
52
|
+
KeePass::Password.generate('[\I\|]{3}', :remove_lookalikes => true)
|
53
|
+
#=> KeePass::Password::InvalidPatternError: empty character set for token 1 for "[\\I\\|]{3}"
|
54
|
+
|
55
|
+
## Related gems
|
56
|
+
|
57
|
+
* <https://github.com/dmke/simple-password-gen>
|
58
|
+
* <http://rubygems.org/gems/ruby-password>
|
data/Rakefile
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "keepass/password/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "keepass-password-generator"
|
7
|
+
s.version = KeePass::Password::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["John Nishinaga"]
|
10
|
+
s.email = ["jingoro@casa-z.org"]
|
11
|
+
s.homepage = "https://github.com/patdeegan/keepass-password-generator"
|
12
|
+
s.summary = "keepass-password-generator-#{KeePass::Password::VERSION}"
|
13
|
+
s.description = "Generate passwords using KeePass password generator patterns"
|
14
|
+
|
15
|
+
s.rubyforge_project = "keepass-password-generator"
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
|
+
s.require_paths = ["lib"]
|
21
|
+
|
22
|
+
s.add_dependency 'activesupport', '>= 2.2.0'
|
23
|
+
|
24
|
+
s.add_development_dependency 'yard'
|
25
|
+
s.add_development_dependency 'bluecloth'
|
26
|
+
s.add_development_dependency 'rspec'
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'keepass/password'
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'keepass/password/char_set'
|
2
|
+
require 'keepass/password/generator'
|
3
|
+
require 'keepass/password/version'
|
4
|
+
|
5
|
+
module KeePass
|
6
|
+
|
7
|
+
module Password
|
8
|
+
|
9
|
+
# Returns a generated password.
|
10
|
+
#
|
11
|
+
# @param [String] pattern the pattern
|
12
|
+
# @param [Hash] options the options
|
13
|
+
# @option options [Boolean] :permute (true) whether or not to randomly permute generated passwords
|
14
|
+
# @option options [Boolean] :remove_lookalikes (false) whether or not to remove lookalike characters
|
15
|
+
# @option options [Hash] :charset_mapping (CharSet::DEFAULT_MAPPING) the KeePass character set ID mapping
|
16
|
+
# @return [String] the new password
|
17
|
+
# @raise [InvalidPatternError] if `pattern` is invalid
|
18
|
+
def self.generate(pattern, options = {})
|
19
|
+
Generator.new(pattern, options).generate
|
20
|
+
end
|
21
|
+
|
22
|
+
# Returns whether or not the pattern is valid.
|
23
|
+
#
|
24
|
+
# @param [String] pattern the pattern
|
25
|
+
# @param [Hash] options the options
|
26
|
+
# @option options [Boolean] :permute (true) whether or not to randomly permute generated passwords
|
27
|
+
# @option options [Boolean] :remove_lookalikes (false) whether or not to remove lookalike characters
|
28
|
+
# @option options [Hash] :charset_mapping (CharSet::DEFAULT_MAPPING) the KeePass character set ID mapping
|
29
|
+
# @return [Boolean] whether or not the pattern is valid
|
30
|
+
def self.validate_pattern(pattern, options = {})
|
31
|
+
begin
|
32
|
+
generate(pattern, options)
|
33
|
+
true
|
34
|
+
rescue InvalidPatternError
|
35
|
+
false
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Returns an entropy estimate of a password.
|
40
|
+
#
|
41
|
+
# @param [String] test the password to test
|
42
|
+
# @see http://en.wikipedia.org/wiki/Password_strength
|
43
|
+
def self.estimate_entropy(test)
|
44
|
+
chars = 0
|
45
|
+
chars += 26 if test =~ LOWERCASE_TEST_RE
|
46
|
+
chars += 26 if test =~ UPPERCASE_TEST_RE
|
47
|
+
chars += 10 if test =~ DIGITS_TEST_RE
|
48
|
+
chars += CharSet::PRINTABLE_ASCII_SPECIAL.size if test =~ SPECIAL_TEST_RE
|
49
|
+
if chars == 0
|
50
|
+
0
|
51
|
+
else
|
52
|
+
(test.size * Math.log(chars) / Math.log(2)).to_i
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module KeePass
|
4
|
+
|
5
|
+
module Password
|
6
|
+
|
7
|
+
class InvalidCharSetIDError < RuntimeError; end
|
8
|
+
|
9
|
+
# Character sets for the KeePass password generator.
|
10
|
+
#
|
11
|
+
# @see http://keepass.info/help/base/pwgenerator.html#pattern
|
12
|
+
class CharSet < Set
|
13
|
+
|
14
|
+
UPPERCASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
15
|
+
LOWERCASE = "abcdefghijklmnopqrstuvwxyz"
|
16
|
+
DIGITS = "0123456789"
|
17
|
+
UPPER_CONSONANTS = "BCDFGHJKLMNPQRSTVWXYZ"
|
18
|
+
LOWER_CONSONANTS = "bcdfghjklmnpqrstvwxyz"
|
19
|
+
UPPER_VOWELS = "AEIOU"
|
20
|
+
LOWER_VOWELS = "aeiou"
|
21
|
+
PUNCTUATION = ",.;:"
|
22
|
+
BRACKETS = "[]{}()<>"
|
23
|
+
PRINTABLE_ASCII_SPECIAL = "!\"#\$%&'()*+,-./:;<=>?[\\]^_{|}~"
|
24
|
+
UPPER_HEX = "0123456789ABCDEF"
|
25
|
+
LOWER_HEX = "0123456789abcdef"
|
26
|
+
HIGH_ANSI = (0x7f..0xfe).map { |i| i.chr }.join
|
27
|
+
|
28
|
+
DEFAULT_MAPPING = {
|
29
|
+
'a' => [LOWERCASE, DIGITS],
|
30
|
+
'A' => [LOWERCASE, UPPERCASE, DIGITS],
|
31
|
+
'U' => [UPPERCASE, DIGITS],
|
32
|
+
'c' => [LOWER_CONSONANTS],
|
33
|
+
'C' => [LOWER_CONSONANTS, UPPER_CONSONANTS],
|
34
|
+
'z' => [UPPER_CONSONANTS],
|
35
|
+
'd' => [DIGITS],
|
36
|
+
'h' => [LOWER_HEX],
|
37
|
+
'H' => [UPPER_HEX],
|
38
|
+
'l' => [LOWERCASE],
|
39
|
+
'L' => [LOWERCASE, UPPERCASE],
|
40
|
+
'u' => [UPPERCASE],
|
41
|
+
'p' => [PUNCTUATION],
|
42
|
+
'b' => [BRACKETS],
|
43
|
+
's' => [PRINTABLE_ASCII_SPECIAL],
|
44
|
+
'S' => [UPPERCASE, LOWERCASE, DIGITS, PRINTABLE_ASCII_SPECIAL],
|
45
|
+
'v' => [LOWER_VOWELS],
|
46
|
+
'V' => [LOWER_VOWELS, UPPER_VOWELS],
|
47
|
+
'Z' => [UPPER_VOWELS],
|
48
|
+
'x' => [HIGH_ANSI],
|
49
|
+
}
|
50
|
+
|
51
|
+
ASCII_MAPPING = DEFAULT_MAPPING.reject { |k, v| k == 'x' }
|
52
|
+
|
53
|
+
# @return [Hash] the KeePass character set ID mapping
|
54
|
+
attr_accessor :mapping
|
55
|
+
|
56
|
+
# Instantiates a new CharSet object.
|
57
|
+
#
|
58
|
+
# @see Set#new
|
59
|
+
def initialize(*args)
|
60
|
+
@mapping = DEFAULT_MAPPING
|
61
|
+
super
|
62
|
+
end
|
63
|
+
|
64
|
+
# Adds several characters according to the KeePass character class.
|
65
|
+
#
|
66
|
+
# @see http://keepass.info/help/base/pwgenerator.html#pattern
|
67
|
+
# @param [String] char_set_id the KeePass character set ID
|
68
|
+
# @raise [InvalidCharSetIDError] if mapping does not contain `char_set_id`
|
69
|
+
# @return [CharSet] self
|
70
|
+
def add_from_char_set_id(char_set_id)
|
71
|
+
if strings = mapping[char_set_id]
|
72
|
+
add_from_strings *strings
|
73
|
+
else
|
74
|
+
raise InvalidCharSetIDError, "no such char set ID #{char_set_id.inspect}"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Adds each character from one or more strings.
|
79
|
+
#
|
80
|
+
# @param [Array] *strings one or more strings to add
|
81
|
+
# @return [CharSet] self
|
82
|
+
def add_from_strings(*strings)
|
83
|
+
strings.each { |s| merge Set.new(s.split('')) }
|
84
|
+
self
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'keepass/password/char_set'
|
2
|
+
require 'keepass/random'
|
3
|
+
|
4
|
+
module KeePass
|
5
|
+
|
6
|
+
module Password
|
7
|
+
|
8
|
+
class InvalidPatternError < RuntimeError; end
|
9
|
+
|
10
|
+
# Generate passwords using KeePass password generator patterns.
|
11
|
+
#
|
12
|
+
# @see http://keepass.info/help/base/pwgenerator.html
|
13
|
+
class Generator
|
14
|
+
|
15
|
+
# Available character sets
|
16
|
+
CHARSET_IDS = CharSet::DEFAULT_MAPPING.keys.join
|
17
|
+
|
18
|
+
# ASCII printables regular expression
|
19
|
+
LITERALS_RE = /[\x20-\x7e]/
|
20
|
+
CHAR_TOKEN_RE = Regexp.new("([#{CHARSET_IDS}])|\\\\(#{LITERALS_RE.source})")
|
21
|
+
GROUP_TOKEN_RE = Regexp.new("(#{CHAR_TOKEN_RE.source}|" +
|
22
|
+
"\\[((#{CHAR_TOKEN_RE.source})*?)\\])" +
|
23
|
+
"(\\{(\\d+)\\})?")
|
24
|
+
VALIDATOR_RE = Regexp.new("\\A(#{GROUP_TOKEN_RE.source})+\\Z")
|
25
|
+
|
26
|
+
LOOKALIKE = "O0l1I|"
|
27
|
+
LOOKALIKE_CHARSET = CharSet.new.add_from_strings LOOKALIKE
|
28
|
+
|
29
|
+
# @return [String] the pattern
|
30
|
+
attr_reader :pattern
|
31
|
+
|
32
|
+
# @return [Array<CharSet>] the character sets from the pattern
|
33
|
+
attr_reader :char_sets
|
34
|
+
|
35
|
+
# @return [Boolean] whether or not to permute the password
|
36
|
+
attr_accessor :permute
|
37
|
+
|
38
|
+
# Instantiates a new PasswordGenerator object.
|
39
|
+
#
|
40
|
+
# @param [String] pattern the pattern
|
41
|
+
# @param [Hash] options the options
|
42
|
+
# @option options [Boolean] :permute (true) whether or not to randomly permute generated passwords
|
43
|
+
# @option options [Boolean] :remove_lookalikes (false) whether or not to remove lookalike characters
|
44
|
+
# @option options [Hash] :charset_mapping (CharSet::DEFAULT_MAPPING) the KeePass character set ID mapping
|
45
|
+
# @return [PasswordGenerator] self
|
46
|
+
# @raise [InvalidPatternError] if `pattern` is invalid
|
47
|
+
def initialize(pattern, options = {})
|
48
|
+
@permute = options.has_key?(:permute) ? options[:permute] : true
|
49
|
+
@pattern = pattern
|
50
|
+
@char_sets = pattern_to_char_sets(pattern, options)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Returns a new password.
|
54
|
+
#
|
55
|
+
# @return [String] a new password
|
56
|
+
def generate
|
57
|
+
result = char_sets.map { |c| Random.sample_array(c.to_a) }
|
58
|
+
result = Random.shuffle_array(result) if permute
|
59
|
+
result.join
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def pattern_to_char_sets(pattern, options) #:nodoc:
|
65
|
+
remove_lookalikes = options[:remove_lookalikes] || false
|
66
|
+
mapping = options[:charset_mapping] || CharSet::DEFAULT_MAPPING
|
67
|
+
char_sets = []
|
68
|
+
i = 1
|
69
|
+
pattern.scan(GROUP_TOKEN_RE) do |x1, char, bs_char, char_group, x5, x6, x7, x8, repeat|
|
70
|
+
char_set = CharSet.new
|
71
|
+
char_set.mapping = mapping
|
72
|
+
begin
|
73
|
+
if char
|
74
|
+
char_set.add_from_char_set_id(char)
|
75
|
+
elsif bs_char
|
76
|
+
char_set.add(bs_char)
|
77
|
+
else
|
78
|
+
char_group.scan(CHAR_TOKEN_RE) do |c, e|
|
79
|
+
if c
|
80
|
+
char_set.add_from_char_set_id(c)
|
81
|
+
else
|
82
|
+
char_set.add(e)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
rescue InvalidCharSetIDError => e
|
87
|
+
raise InvalidPatternError, e.message
|
88
|
+
end
|
89
|
+
char_set -= LOOKALIKE_CHARSET if remove_lookalikes
|
90
|
+
if char_set.empty?
|
91
|
+
raise InvalidPatternError, "empty character set for token #{i} for #{pattern.inspect}"
|
92
|
+
end
|
93
|
+
(repeat ? repeat.to_i : 1).times { char_sets << char_set }
|
94
|
+
i += 1
|
95
|
+
end
|
96
|
+
|
97
|
+
if char_sets.any?
|
98
|
+
char_sets
|
99
|
+
else
|
100
|
+
raise InvalidPatternError, "no char sets from #{pattern.inspect}"
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
|
105
|
+
# private
|
106
|
+
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'active_support/secure_random'
|
2
|
+
|
3
|
+
module KeePass
|
4
|
+
|
5
|
+
module Random
|
6
|
+
|
7
|
+
# If `n` is a positive integer, then returns a random
|
8
|
+
# integer `r` such that 0 <= `r` < `n`.
|
9
|
+
#
|
10
|
+
# If `n` is 0 or unspecified, then returns a random
|
11
|
+
# float `r` such that 0 <= `r` < 1.
|
12
|
+
#
|
13
|
+
# @param [Integer] n the upper bound
|
14
|
+
# @return [Integer|Float] the random number
|
15
|
+
# @see ActiveSupport::SecureRandom#random_number
|
16
|
+
def self.random_number(n = 0)
|
17
|
+
ActiveSupport::SecureRandom.random_number(n)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Returns a randomly sampled item from the array.
|
21
|
+
#
|
22
|
+
# @param [Array] array the array to sample from
|
23
|
+
# @return [Object] random item or nil if no items exist
|
24
|
+
def self.sample_array(array)
|
25
|
+
array[random_number(array.size)]
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns the array shuffled randomly.
|
29
|
+
#
|
30
|
+
# @param [Array] array the array to shuffle
|
31
|
+
# @return [Array] the shuffled array
|
32
|
+
def self.shuffle_array(array)
|
33
|
+
array.sort_by { random_number }
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe KeePass::Password::CharSet do
|
4
|
+
|
5
|
+
describe "#add_from_strings" do
|
6
|
+
|
7
|
+
it "should add from multiple arguments" do
|
8
|
+
subject.add_from_strings('abc', 'cde', 'QQ')
|
9
|
+
subject.should == Set.new(%w(a b c d e Q))
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "#add_from_char_set_id" do
|
15
|
+
|
16
|
+
it "should add the digits" do
|
17
|
+
subject.add_from_char_set_id('d')
|
18
|
+
subject.should == Set.new('0'..'9')
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should support chaining" do
|
22
|
+
subject.add_from_char_set_id('l').add_from_char_set_id('u')
|
23
|
+
subject.should == (Set.new('a'..'z') + Set.new('A'..'Z'))
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should allow x with default mapping" do
|
27
|
+
subject.add_from_char_set_id('x')
|
28
|
+
subject.should include(0x7f.chr)
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should raise an error with ASCII mapping" do
|
32
|
+
subject.mapping = KeePass::Password::CharSet::ASCII_MAPPING
|
33
|
+
expect { subject.add_from_char_set_id('x') }.to raise_error(KeePass::Password::InvalidCharSetIDError)
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe KeePass::Password::Generator do
|
4
|
+
|
5
|
+
def char_set(*ids)
|
6
|
+
char_set = KeePass::Password::CharSet.new
|
7
|
+
ids.each { |id| char_set.add_from_char_set_id(id) }
|
8
|
+
char_set
|
9
|
+
end
|
10
|
+
|
11
|
+
subject { described_class.new(pattern, options) }
|
12
|
+
let(:options) { { } }
|
13
|
+
let(:random_class) { KeePass::Random }
|
14
|
+
|
15
|
+
describe "pattern 'h{10}' (40-bit WEP key)" do
|
16
|
+
|
17
|
+
let(:pattern) { 'h{10}' }
|
18
|
+
|
19
|
+
its(:pattern) { should == 'h{10}' }
|
20
|
+
its(:permute) { should be_true }
|
21
|
+
its(:char_sets) { should have(10).items }
|
22
|
+
|
23
|
+
it "should generate 10 hex digits " do
|
24
|
+
random_class.should_receive(:sample_array) do |array|
|
25
|
+
array.sort.should == char_set('h').to_a.sort
|
26
|
+
'0'
|
27
|
+
end.exactly(10).times
|
28
|
+
random_class.should_receive(:shuffle_array) do |array|
|
29
|
+
array.sort
|
30
|
+
end.once
|
31
|
+
subject.generate.should == '0000000000'
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "pattern 'HH\-HH\-HH\-HH\-HH\-HH', :permute => false" do
|
37
|
+
|
38
|
+
let(:pattern) { 'HH\-HH\-HH\-HH\-HH\-HH' }
|
39
|
+
let(:options) { { :permute => false } }
|
40
|
+
|
41
|
+
its(:pattern) { should == 'HH\-HH\-HH\-HH\-HH\-HH' }
|
42
|
+
its(:permute) { should be_false }
|
43
|
+
its(:char_sets) { should have(17).items }
|
44
|
+
|
45
|
+
it "should generate a MAC address" do
|
46
|
+
random_class.should_receive(:sample_array) do |array|
|
47
|
+
if array == ['-']
|
48
|
+
'-'
|
49
|
+
else
|
50
|
+
array.sort.should == char_set('H').to_a.sort
|
51
|
+
'0'
|
52
|
+
end
|
53
|
+
end.exactly(17).times
|
54
|
+
random_class.should_not_receive(:shuffle_array)
|
55
|
+
subject.generate.should == '00-00-00-00-00-00'
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
describe "pattern 'uullA{6}'" do
|
61
|
+
|
62
|
+
let(:pattern) { 'uullA{6}' }
|
63
|
+
|
64
|
+
its(:pattern) { should == 'uullA{6}' }
|
65
|
+
its(:permute) { should be_true }
|
66
|
+
its(:char_sets) { should have(10).items }
|
67
|
+
|
68
|
+
it "should generate a 10-character alphanumeric password" do
|
69
|
+
random_class.should_receive(:sample_array) do |array|
|
70
|
+
array.sort.first
|
71
|
+
end.exactly(10).times
|
72
|
+
random_class.should_receive(:shuffle_array) do |array|
|
73
|
+
array.sort
|
74
|
+
end.once
|
75
|
+
subject.generate.should == '000000AAaa'
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
describe "pattern '[As]{20}', :remove_lookalikes => true" do
|
81
|
+
|
82
|
+
let(:pattern) { '[As]{20}' }
|
83
|
+
let(:options) { { :remove_lookalikes => true } }
|
84
|
+
|
85
|
+
its(:pattern) { should == '[As]{20}' }
|
86
|
+
its(:permute) { should be_true }
|
87
|
+
its(:char_sets) { should have(20).items }
|
88
|
+
|
89
|
+
it "should generate a 20-character password" do
|
90
|
+
test_set = (char_set('A', 's') - Set.new(%w(O 0 l 1 I |))).to_a.sort
|
91
|
+
i = 0
|
92
|
+
random_class.should_receive(:sample_array) do |array|
|
93
|
+
array.sort.should == test_set
|
94
|
+
result = array.sort[i]
|
95
|
+
i += 1
|
96
|
+
result
|
97
|
+
end.exactly(20).times
|
98
|
+
random_class.should_receive(:shuffle_array) do |array|
|
99
|
+
array.sort
|
100
|
+
end.once
|
101
|
+
subject.generate.should == '!"#$%&\'()*+,-./23456'
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
|
106
|
+
describe "pattern '[\\I\\|]{3}', :remove_lookalikes => true" do
|
107
|
+
|
108
|
+
let(:pattern) { '[\I\|]{3}' }
|
109
|
+
let(:options) { { :remove_lookalikes => true } }
|
110
|
+
|
111
|
+
it "should raise an error" do
|
112
|
+
expect { subject }.to raise_error(KeePass::Password::InvalidPatternError)
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
data/spec/random_spec.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe KeePass::Random do
|
4
|
+
|
5
|
+
describe "#random_number" do
|
6
|
+
|
7
|
+
it "should use ActiveSupport::SecureRandom" do
|
8
|
+
ActiveSupport::SecureRandom.should_receive(:random_number).once.with(12)
|
9
|
+
described_class.random_number(12)
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should accept default argument" do
|
13
|
+
ActiveSupport::SecureRandom.should_receive(:random_number).with(0)
|
14
|
+
described_class.random_number
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "#sample_array" do
|
20
|
+
|
21
|
+
it "should call random_number with the array size" do
|
22
|
+
described_class.should_receive(:random_number).with(6).and_return(3)
|
23
|
+
described_class.sample_array(%w(a b c d e f)).should == 'd'
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should return expected values for deterministic random number" do
|
27
|
+
described_class.stub(:random_number) { |arg| 0 }
|
28
|
+
described_class.sample_array(%w(a b c)).should == 'a'
|
29
|
+
described_class.sample_array(%w(b a c)).should == 'b'
|
30
|
+
described_class.sample_array(%w(c b a)).should == 'c'
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "#shuffle_array" do
|
36
|
+
|
37
|
+
it "should call random_number with no parameters" do
|
38
|
+
described_class.should_receive(:random_number).with().at_least(5).times.and_return(0.5)
|
39
|
+
described_class.shuffle_array(%w(a b c d e))
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should return the same elements" do
|
43
|
+
described_class.stub(:random_number) { 0.5 }
|
44
|
+
described_class.shuffle_array(%w(a b c d e)).sort.should == %w(a b c d e)
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
$:.unshift File.expand_path('../../lib', __FILE__)
|
2
|
+
|
3
|
+
require 'rspec'
|
4
|
+
require 'keepass/password/generator'
|
5
|
+
|
6
|
+
# module DeterministicRandomness
|
7
|
+
#
|
8
|
+
# def deterministic_random_number(&block)
|
9
|
+
# ActiveSupport::SecureRandom.stub(:random_number) do |arg|
|
10
|
+
# if block
|
11
|
+
# block.call(arg)
|
12
|
+
# else
|
13
|
+
# 0
|
14
|
+
# end
|
15
|
+
# end
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# # def deterministic_shuffle
|
19
|
+
# # Array.any_instance.stub(:shuffle!)
|
20
|
+
# # end
|
21
|
+
#
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# RSpec.configure do |config|
|
25
|
+
# config.include DeterministicRandomness
|
26
|
+
# end
|
27
|
+
|
28
|
+
# RSpec::Matchers.define :have_char_set_length_of do |expected|
|
29
|
+
# match do |actual|
|
30
|
+
# actual.char_sets.size == expected
|
31
|
+
# end
|
32
|
+
# end
|
metadata
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: keepass-password-generator
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- John Nishinaga
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-03-22 00:00:00 -04:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
prerelease: false
|
23
|
+
type: :runtime
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 7
|
30
|
+
segments:
|
31
|
+
- 2
|
32
|
+
- 2
|
33
|
+
- 0
|
34
|
+
version: 2.2.0
|
35
|
+
name: activesupport
|
36
|
+
version_requirements: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
prerelease: false
|
39
|
+
type: :development
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
hash: 3
|
46
|
+
segments:
|
47
|
+
- 0
|
48
|
+
version: "0"
|
49
|
+
name: yard
|
50
|
+
version_requirements: *id002
|
51
|
+
- !ruby/object:Gem::Dependency
|
52
|
+
prerelease: false
|
53
|
+
type: :development
|
54
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
55
|
+
none: false
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
hash: 3
|
60
|
+
segments:
|
61
|
+
- 0
|
62
|
+
version: "0"
|
63
|
+
name: bluecloth
|
64
|
+
version_requirements: *id003
|
65
|
+
- !ruby/object:Gem::Dependency
|
66
|
+
prerelease: false
|
67
|
+
type: :development
|
68
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
69
|
+
none: false
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
hash: 3
|
74
|
+
segments:
|
75
|
+
- 0
|
76
|
+
version: "0"
|
77
|
+
name: rspec
|
78
|
+
version_requirements: *id004
|
79
|
+
description: Generate passwords using KeePass password generator patterns
|
80
|
+
email:
|
81
|
+
- jingoro@casa-z.org
|
82
|
+
executables: []
|
83
|
+
|
84
|
+
extensions: []
|
85
|
+
|
86
|
+
extra_rdoc_files: []
|
87
|
+
|
88
|
+
files:
|
89
|
+
- .gitignore
|
90
|
+
- .rspec
|
91
|
+
- .yardopts
|
92
|
+
- CHANGELOG.md
|
93
|
+
- Gemfile
|
94
|
+
- MIT-LICENSE.txt
|
95
|
+
- README.md
|
96
|
+
- Rakefile
|
97
|
+
- keepass-password-generator.gemspec
|
98
|
+
- lib/keepass-password-generator.rb
|
99
|
+
- lib/keepass/password.rb
|
100
|
+
- lib/keepass/password/char_set.rb
|
101
|
+
- lib/keepass/password/generator.rb
|
102
|
+
- lib/keepass/password/version.rb
|
103
|
+
- lib/keepass/random.rb
|
104
|
+
- spec/char_set_spec.rb
|
105
|
+
- spec/generator_spec.rb
|
106
|
+
- spec/random_spec.rb
|
107
|
+
- spec/spec_helper.rb
|
108
|
+
has_rdoc: true
|
109
|
+
homepage: https://github.com/patdeegan/keepass-password-generator
|
110
|
+
licenses: []
|
111
|
+
|
112
|
+
post_install_message:
|
113
|
+
rdoc_options: []
|
114
|
+
|
115
|
+
require_paths:
|
116
|
+
- lib
|
117
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
118
|
+
none: false
|
119
|
+
requirements:
|
120
|
+
- - ">="
|
121
|
+
- !ruby/object:Gem::Version
|
122
|
+
hash: 3
|
123
|
+
segments:
|
124
|
+
- 0
|
125
|
+
version: "0"
|
126
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
127
|
+
none: false
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
hash: 3
|
132
|
+
segments:
|
133
|
+
- 0
|
134
|
+
version: "0"
|
135
|
+
requirements: []
|
136
|
+
|
137
|
+
rubyforge_project: keepass-password-generator
|
138
|
+
rubygems_version: 1.5.2
|
139
|
+
signing_key:
|
140
|
+
specification_version: 3
|
141
|
+
summary: keepass-password-generator-0.1.0
|
142
|
+
test_files:
|
143
|
+
- spec/char_set_spec.rb
|
144
|
+
- spec/generator_spec.rb
|
145
|
+
- spec/random_spec.rb
|
146
|
+
- spec/spec_helper.rb
|