altpass 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 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