altpass 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ *.swp
3
+ /.rvmrc
4
+ /Gemfile.lock
5
+
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in altpass.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Alex Batko
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,177 @@
1
+ Altpass
2
+ =======
3
+
4
+ A Ruby gem and command-line utility for *generating passwords* derived from *hand-alternating*, *visually unambiguous*, *alphanumeric characters*.
5
+
6
+ Example
7
+ -------
8
+
9
+ `nXH3ix9w`
10
+
11
+ General Characteristics
12
+ -----------------------
13
+
14
+ * alternates hands (currently only supports `QWERTY` [keyboard layout](http://en.wikipedia.org/wiki/Keyboard_layout))
15
+ * always starts with a lower-case character (left-hand first for odd-length passwords; right if even)
16
+ * always ends with a left-hand character (so that the right-hand can finalize the hand alternation by hitting `Enter`/`Return`)
17
+ * all characters are alphanumeric; there are no symbols (so the passwords highlight when double-clicked)
18
+ * all characters are visually unambigous (some fonts make it difficult/impossible to distinguish *zero* and *capital 'o'*, or *one* and *lowercase 'L'*, and *uppercase i*, etc. so such characters are purposely not used)
19
+
20
+ The "memorizable" pattern
21
+ -------------------------
22
+
23
+ * the "memorizable" pattern is: lower, UPPER, UPPER, *number*, lower, lower, *number*, lower
24
+ * this pattern repeats for longer passwords
25
+
26
+ At a glance, a password such as `nXH3ix9w` looks incredibly difficult to memorize, but practicing it 5-10 times usually does the trick, particularly after first taking note of the pattern.
27
+
28
+ Options
29
+ -------
30
+
31
+ * make passwords of any *length* (`:length => [0-9]+`)
32
+ * use the *memorizable* pattern or not (`:memorizable => true|false`)
33
+ * show the sample sets and number of *permutations* (`:permutations => [vvv]`) (more `v`'s increase verbosity)
34
+
35
+ Defaults
36
+ --------
37
+
38
+ * `:length => 8`
39
+ * `:memorizable => true`
40
+
41
+ Examples of Gem Usage
42
+ ---------------------
43
+
44
+ Return a *default* password:
45
+
46
+ Altpass.generate
47
+ => "uGL4ox7f"
48
+
49
+ Return an *odd-length* password, not using the memorizable pattern:
50
+
51
+ Altpass.generate(:length => 15, :memorizable => false)
52
+ => "cH5L3jwyw9XPw8V"
53
+
54
+ Return an *even-length* password, not using the memorizable pattern:
55
+
56
+ Altpass.generate(:length => 16, :memorizable => false)
57
+ => "uV7AhspqySkBUwhs"
58
+
59
+ Return the *number of permutations*:
60
+
61
+ Altpass.permutations(:length => 16, :memorizable => false)
62
+ => 2869959681148181529600
63
+
64
+ Print the *sample sets* and *permutation calculation* of a default password, and return the *number of permutations*:
65
+
66
+ Altpass.permutations(:length => 8, :memorizable => true, :permutations => 'vvv')
67
+ sample sets:
68
+ ["y", "u", "i", "o", "p", "h", "j", "k", "n", "m"]
69
+ ["Q", "W", "E", "R", "T", "A", "S", "D", "F", "G", "Z", "X", "C", "V", "B"]
70
+ ["Y", "U", "P", "H", "J", "K", "L", "N", "M"]
71
+ ["2", "3", "4", "5"]
72
+ ["y", "u", "i", "o", "p", "h", "j", "k", "n", "m"]
73
+ ["q", "w", "e", "r", "t", "a", "s", "d", "f", "g", "z", "x", "c", "v"]
74
+ ["7", "8", "9"]
75
+ ["q", "w", "e", "r", "t", "a", "s", "d", "f", "g", "z", "x", "c", "v"]
76
+ 10 * 15 * 9 * 4 * 10 * 14 * 3 * 14 permutations
77
+ => 31752000
78
+
79
+ Installation
80
+ ------------
81
+
82
+ Add this line to your application's Gemfile:
83
+
84
+ gem 'altpass'
85
+
86
+ And then execute:
87
+
88
+ $ bundle
89
+
90
+ **Or** install it yourself:
91
+
92
+ $ gem install altpass
93
+
94
+ The Command-Line Utility
95
+ ========================
96
+
97
+ The command-line utility, `altpass.rb`, is built into the gem.
98
+
99
+ Example
100
+ --------
101
+
102
+ $ altpass.rb
103
+ nF7zHTNrjtYRmxod
104
+
105
+ Options
106
+ -------
107
+
108
+ Usage: altpass.rb [options]
109
+ -l, --length [INTEGER>0] Password length
110
+ -m, --memorizable [true|false] Use the memorizable pattern or not
111
+ -p, --permutations [vvv] Show permutation size; verbosity increases with v's
112
+ -s, --switches Show password switches
113
+ -h, --help Show this message
114
+
115
+ Configuration
116
+ -------------
117
+
118
+ ### Program defaults
119
+
120
+ The program itself comes with the following defaults:
121
+
122
+ options[:length] = 8
123
+ options[:memorizable] = true
124
+ options[:permutations] = false
125
+ options[:switches] = false
126
+
127
+ ### File override
128
+
129
+ Overrides program defaults.
130
+
131
+ cat ~/.altpass.yaml
132
+ :length: 16
133
+ :memorizable: false
134
+
135
+ ### Command-line override
136
+
137
+ Command-line options override everything mentioned above. See *Command-line Options* above.
138
+
139
+ Examples of Command-Line Usage
140
+ ------------------------------
141
+
142
+ These examples assume the above mentioned *Configuration* section is in effect.
143
+
144
+ $ altpass.rb
145
+ nF7zHTNrjtYRmxod
146
+
147
+ $ altpass.rb -p
148
+ oDKfhfYznFN3j4Nv
149
+ 2,869,959,681,148,181,529,600 permutations
150
+
151
+ $ altpass.rb --length 8 --memorizable true
152
+ kSU5yx9e
153
+
154
+ $ altpass.rb -l8 -mt -pvvv -s
155
+ jXJ3oc7z
156
+ sample sets:
157
+ ["y", "u", "i", "o", "p", "h", "j", "k", "n", "m"]
158
+ ["Q", "W", "E", "R", "T", "A", "S", "D", "F", "G", "Z", "X", "C", "V", "B"]
159
+ ["Y", "U", "P", "H", "J", "K", "L", "N", "M"]
160
+ ["2", "3", "4", "5"]
161
+ ["y", "u", "i", "o", "p", "h", "j", "k", "n", "m"]
162
+ ["q", "w", "e", "r", "t", "a", "s", "d", "f", "g", "z", "x", "c", "v"]
163
+ ["7", "8", "9"]
164
+ ["q", "w", "e", "r", "t", "a", "s", "d", "f", "g", "z", "x", "c", "v"]
165
+ 10 * 15 * 9 * 4 * 10 * 14 * 3 * 14 permutations
166
+ 31,752,000 permutations
167
+ --length 8 --memorizable true
168
+
169
+ Contributing
170
+ ============
171
+
172
+ 1. Fork it
173
+ 2. Create your feature branch (`git checkout -b my_new_feature`)
174
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
175
+ 4. Push to the branch (`git push origin my_new_feature`)
176
+ 5. Create new Pull Request
177
+
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require 'bundler/gem_tasks'
2
+
3
+ require 'rake/testtask'
4
+
5
+ Rake::TestTask.new do |t|
6
+ t.libs << 'test'
7
+ end
8
+
9
+ desc 'Run tests'
10
+ task default: :test
data/altpass.gemspec ADDED
@@ -0,0 +1,24 @@
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'altpass/version'
4
+
5
+ Gem::Specification.new do |gem|
6
+ gem.name = 'altpass'
7
+ gem.version = Altpass::VERSION
8
+
9
+ gem.authors = ['Alex Batko']
10
+ gem.email = ['alexbatko@gmail.com']
11
+
12
+ gem.summary = %q{Generate passwords derived from hand-alternating, visually unambiguous, alphanumeric characters}
13
+ gem.description = "#{gem.summary}. This is a Ruby gem and command-line utility. Formerly known as 'alexpass'."
14
+
15
+ gem.homepage = 'https://github.com/abatko/altpass'
16
+
17
+ gem.files = `git ls-files`.split($/)
18
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
19
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
20
+ gem.require_paths = ['lib']
21
+
22
+ gem.license = 'MIT'
23
+ end
24
+
data/bin/altpass.rb ADDED
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'altpass' # for actually generating the Altpass passwords
4
+ require 'optparse' # for command-line options parsing
5
+ require 'yaml' # for loading the YAML configuration file, if present
6
+
7
+ # defaults
8
+ options = {}
9
+ options[:length] = 8
10
+ options[:memorizable] = true
11
+ options[:permutations] = false
12
+ options[:switches] = false
13
+
14
+ # YAML configuration file overrides the defaults below
15
+ CONFIGURATION_FILE = "#{ENV['HOME']}/.altpass.yaml"
16
+ yaml_defaults = YAML.load_file(CONFIGURATION_FILE) if File.exist?(CONFIGURATION_FILE) && File.readable?(CONFIGURATION_FILE)
17
+ options = options.merge(yaml_defaults) unless yaml_defaults.nil?
18
+
19
+ # command-line options override yaml_defaults
20
+ OptionParser.new do |opts|
21
+ opts.banner = 'Usage: altpass.rb [options]'
22
+
23
+ opts.on('-l', '--length [INTEGER>0]', OptionParser::DecimalInteger, 'Password length') do |l|
24
+ unless l > 0
25
+ puts 'argument to -l must be > 0'
26
+ puts opts ; exit
27
+ end
28
+ options[:length] = l
29
+ end
30
+
31
+ opts.on('-m [t|f]', '--memorizable [true|false]', 'Use the memorizable pattern or not') do |m|
32
+ case m
33
+ when /^(t|true)\Z/
34
+ options[:memorizable] = true
35
+ when /^(f|false)\Z/
36
+ options[:memorizable] = false
37
+ else
38
+ puts 'argument to -m must be [true|t|false|f]'
39
+ puts opts ; exit
40
+ end
41
+ end
42
+
43
+ opts.on('-p', '--permutations [vvv]', 'Show permutation size; verbosity increases with v\'s') do |p|
44
+ if !p.nil? && p !~ /^v+\Z/
45
+ puts 'argument to -p must be v\'s'
46
+ puts opts ; exit
47
+ end
48
+ options[:permutations] = p.nil? ? 'v' : p+'v'
49
+ end
50
+
51
+ opts.on('-s', '--switches', 'Show password switches') do |s|
52
+ options[:switches] = s
53
+ end
54
+
55
+ opts.on_tail('-h', '--help', 'Show this message') do
56
+ puts opts ; exit
57
+ end
58
+ end.parse!
59
+
60
+ def show_permutations(options)
61
+ permutations = Altpass.permutations(options)
62
+ # print the number of permutations after adding commas to it
63
+ puts permutations.to_s.reverse.scan(/(?:\d*\.)?\d{1,3}-?/).join(',').reverse + ' permutations'
64
+ end
65
+
66
+ puts Altpass.generate(:length => options[:length], :memorizable => options[:memorizable])
67
+
68
+ show_permutations(options) if options[:permutations]
69
+
70
+ puts "--length #{options[:length]} --memorizable #{options[:memorizable]}" if options[:switches]
71
+
@@ -0,0 +1,4 @@
1
+ module Altpass
2
+ VERSION = '0.1.0'
3
+ end
4
+
data/lib/altpass.rb ADDED
@@ -0,0 +1,92 @@
1
+ require 'altpass/version'
2
+
3
+ module Altpass
4
+
5
+ DEFAULT_LENGTH = 8
6
+ DEFAULT_OPTIONS = {:length => DEFAULT_LENGTH, :memorizable => true}
7
+
8
+ # character sets divided by touch typing hands
9
+ LN = '12345'.split('') # Left-hand Numbers
10
+ RN = '67890' .split('') # Right ''
11
+ LL = ('qwert'+'asdfg'+'zxcvb').split('') # Left-hand Lowercase letters
12
+ RL = ('yuiop'+'hjkl'+'nm').split('') # Right '' ''
13
+ LU = LL.collect {|c| c.capitalize} # Left-hand Uppercase letters
14
+ RU = RL.collect {|c| c.capitalize} # Right '' ''
15
+
16
+ AMBIGUOUS =
17
+ '1lI0O' + # visually ambiguous
18
+ '6b' # left/right ambiguous
19
+
20
+ # patterns for even and odd length passwords;
21
+ # alternating hands, with the last always being on the left;
22
+ # only the first in each subset takes part in the "memorizable" pattern: LUUNLLNL
23
+ @pattern_even = [[RL], [LU, LN, LL], [RU, RN, RL], [LN, LL, LU], [RL, RU, RN], [LL, LU, LN], [RN, RL, RU], [LL]]
24
+
25
+ @pattern_odd = [[LL], [RU, RN, RL], [LU, LN, LL], [RN, RL, RU], [LL, LU, LN], [RL, RU, RN], [LN, LL, LU], [RL]]
26
+
27
+ # return a password
28
+ def self.generate(options={})
29
+ options = self._verify_options(options)
30
+ password = ''
31
+ iterations = options[:length]/DEFAULT_LENGTH + 1
32
+ iterations.times { password += self._generate(options) }
33
+ password.slice(0...options[:length])
34
+ end
35
+
36
+ # return the number of permutations,
37
+ # and additionally print out details when options[:permutations] contains v's
38
+ def self.permutations(options={})
39
+ options = self._verify_options(options)
40
+ p = 1
41
+ pattern = options[:length].even? ? @pattern_even : @pattern_odd
42
+ plen = pattern.length
43
+ samples = []
44
+ (0...options[:length]).each { |i|
45
+ samples[i] = (options[:memorizable] ? pattern[i%plen][0] : pattern[i%plen].flatten).reject{|c| AMBIGUOUS.include?(c)}
46
+ p *= samples[i].length # take the product of all sample sizes to get the total number of permutations
47
+ }
48
+ if options[:permutations] =~ /^v{3,}\Z/
49
+ puts 'sample sets:'
50
+ samples.each {|s|
51
+ puts s.inspect
52
+ }
53
+ end
54
+ puts "#{samples.collect {|s| s.length}.join(' * ')} permutations" if options[:permutations] =~ /^v{2,}\Z/
55
+ p
56
+ end
57
+
58
+ private
59
+
60
+ # verify and return an options hash, after merging the default options with the incoming ones
61
+ def self._verify_options(options=nil)
62
+ raise ArgumentError, "expected a Hash, but got: #{options.inspect}" unless options.kind_of?(Hash)
63
+ options = DEFAULT_OPTIONS.merge(options)
64
+ raise ArgumentError, "expected :memorizable to be boolean, but got: #{options[:memorizable].inspect}" unless options[:memorizable].kind_of?(TrueClass) || options[:memorizable].kind_of?(FalseClass)
65
+ raise ArgumentError, "expected :length to be a Fixnum, but got: #{options[:length].inspect}" unless options[:length].kind_of?(Fixnum)
66
+ raise ArgumentError, "expected :length > 0, but got: #{options[:length]}" unless options[:length] > 0
67
+ options
68
+ end
69
+
70
+ # generate and return a string, having a parity-appropriate pattern, where each character is sampled from the appropriate character set (depending on whether :memorizable is true or false)
71
+ def self._generate(options={})
72
+ options = self._verify_options(options)
73
+ pattern = options[:length].even? ? @pattern_even : @pattern_odd
74
+ return pattern.collect { |i|
75
+ s =''
76
+ loop do
77
+ s = _sample(i[options[:memorizable] ? 0 : rand(i.length)])
78
+ break unless AMBIGUOUS.include?(s)
79
+ end
80
+ s
81
+ }.join.slice(0..-1)
82
+ end
83
+
84
+ # pick a random element from the given array
85
+ def self._sample(a=[])
86
+ raise ArgumentError, "expected an Array, but got: #{a.class}" unless a.kind_of?(Array)
87
+ raise ArgumentError, 'expected a non-empty Array, but got an empty one' unless a.length > 0
88
+ a[rand(a.length)]
89
+ end
90
+
91
+ end
92
+
@@ -0,0 +1,66 @@
1
+ # encoding: utf-8
2
+ require 'test_helper'
3
+
4
+ class AltpassTest < Test::Unit::TestCase
5
+
6
+ def test__generate__default_length
7
+ assert_equal Altpass::DEFAULT_LENGTH, Altpass.generate.length
8
+ end
9
+
10
+ def test__generate__random_length
11
+ random_length = rand(20)+1
12
+ assert_equal random_length, Altpass.generate(:length=>random_length).length
13
+ end
14
+
15
+ def test__generate__raise_exception_for_non_hash_argument
16
+ non_hash_argument = 10
17
+ exception = assert_raise(ArgumentError) { Altpass.generate(non_hash_argument) }
18
+ assert_equal "expected a Hash, but got: #{non_hash_argument}", exception.message
19
+ end
20
+
21
+ def test__generate__raise_exception_for_bad_memorizable_hash_value_type
22
+ non_boolean = {:memorizable => 10}
23
+ exception = assert_raise(ArgumentError) { Altpass.generate(non_boolean) }
24
+ assert_equal "expected :memorizable to be boolean, but got: #{non_boolean[:memorizable].inspect}", exception.message
25
+ end
26
+
27
+ def test__generate__raise_exception_for_bad_length_hash_value_type
28
+ non_fixnum = {:length => 'A'}
29
+ exception = assert_raise(ArgumentError) { Altpass.generate(non_fixnum) }
30
+ assert_equal "expected :length to be a Fixnum, but got: #{non_fixnum[:length].inspect}", exception.message
31
+ end
32
+
33
+ def test_raise_exception_for_fixnum_argument_less_than_one_to_generate_to_length
34
+ fixnum_argument = {:length => 0}
35
+ exception = assert_raise(ArgumentError) { Altpass.generate(fixnum_argument) }
36
+ assert_equal "expected :length > 0, but got: #{fixnum_argument[:length].inspect}", exception.message
37
+ end
38
+
39
+ def test__sample
40
+ assert_equal 'a', Altpass._sample(['a'])
41
+ assert_equal 'a', Altpass._sample(['a', 'a', 'a'])
42
+ random_element = Altpass._sample(['a', 'b', 'c'])
43
+ assert_equal String, random_element.class
44
+ assert_equal 1, random_element.length
45
+ end
46
+
47
+ def test__sample_raise_exception_for_non_array_argument
48
+ exception = assert_raise(ArgumentError) { Altpass._sample({}) }
49
+ assert_equal "expected an Array, but got: Hash", exception.message
50
+ end
51
+
52
+ def test__sample_raise_exception_for_empty_array_argument
53
+ exception = assert_raise(ArgumentError) { Altpass._sample([]) }
54
+ assert_equal "expected a non-empty Array, but got an empty one", exception.message
55
+ end
56
+
57
+ def test__permutations
58
+ # these hardcoded values will need to be updated if/when the character set lengths change
59
+ assert_equal 31752000, Altpass.permutations
60
+ assert_equal 53572004640, Altpass.permutations(:memorizable => false)
61
+ assert_equal 60011280000, Altpass.permutations(:memorizable => true, :length => 11)
62
+ assert_equal 544505855160960, Altpass.permutations(:memorizable => false, :length => 11)
63
+ end
64
+
65
+ end
66
+
@@ -0,0 +1,4 @@
1
+ require 'test/unit'
2
+ #require 'altpass'
3
+ require './lib/altpass.rb'
4
+
metadata ADDED
@@ -0,0 +1,63 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: altpass
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Alex Batko
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-12-14 00:00:00.000000000Z
13
+ dependencies: []
14
+ description: Generate passwords derived from hand-alternating, visually unambiguous,
15
+ alphanumeric characters. This is a Ruby gem and command-line utility. Formerly known
16
+ as 'alexpass'.
17
+ email:
18
+ - alexbatko@gmail.com
19
+ executables:
20
+ - altpass.rb
21
+ extensions: []
22
+ extra_rdoc_files: []
23
+ files:
24
+ - .gitignore
25
+ - Gemfile
26
+ - LICENSE.txt
27
+ - README.md
28
+ - Rakefile
29
+ - altpass.gemspec
30
+ - bin/altpass.rb
31
+ - lib/altpass.rb
32
+ - lib/altpass/version.rb
33
+ - test/test_altpass.rb
34
+ - test/test_helper.rb
35
+ homepage: https://github.com/abatko/altpass
36
+ licenses:
37
+ - MIT
38
+ post_install_message:
39
+ rdoc_options: []
40
+ require_paths:
41
+ - lib
42
+ required_ruby_version: !ruby/object:Gem::Requirement
43
+ none: false
44
+ requirements:
45
+ - - ! '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ required_rubygems_version: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ requirements: []
55
+ rubyforge_project:
56
+ rubygems_version: 1.8.10
57
+ signing_key:
58
+ specification_version: 3
59
+ summary: Generate passwords derived from hand-alternating, visually unambiguous, alphanumeric
60
+ characters
61
+ test_files:
62
+ - test/test_altpass.rb
63
+ - test/test_helper.rb