pawgen 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.
Files changed (9) hide show
  1. checksums.yaml +7 -0
  2. data/GPL-2 +339 -0
  3. data/GPL-3 +674 -0
  4. data/Manifest.txt +7 -0
  5. data/README +149 -0
  6. data/bin/pawgen +208 -0
  7. data/lib/pawgen.rb +412 -0
  8. data/pawgen.gemspec +18 -0
  9. metadata +54 -0
data/lib/pawgen.rb ADDED
@@ -0,0 +1,412 @@
1
+ require 'securerandom'
2
+
3
+ class PawGen
4
+ DIGITS = '0123456789'.freeze
5
+ UPPERCASE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.freeze
6
+ LOWERCASE = 'abcdefghijklmnopqrstuvwxyz'.freeze
7
+ SYMBOLS = '!"#$%&\'()*+,-./:<=>?@[\\]^_`{|}~'.freeze
8
+ AMBIGUOUS = 'B8G6I1l0OQDS5Z2'.freeze
9
+ VOWELS = '01aeiouyAEIOUY'.freeze
10
+
11
+ def initialize
12
+ super()
13
+ # The original defaulted the length to 8, but it's a bit too
14
+ # short to be the default length these days -- especially
15
+ # considering that phonemic password construction shrinks
16
+ # the password space.
17
+ @length = 12
18
+ @include_uppercase = true
19
+ @include_digits = true
20
+ @include_symbols = false
21
+ @exclude_ambiguous = false
22
+ @exclude_vowels = false
23
+ @mode = method :anglophonemic
24
+ return
25
+ end
26
+
27
+ def include_uppercase?
28
+ return @include_uppercase
29
+ end
30
+
31
+ def include_uppercase!
32
+ @include_uppercase = true
33
+ return self
34
+ end
35
+
36
+ def no_uppercase!
37
+ @include_uppercase = false
38
+ return self
39
+ end
40
+
41
+ def include_digits?
42
+ return @include_digits
43
+ end
44
+
45
+ def include_digits!
46
+ @include_digits = true
47
+ return self
48
+ end
49
+
50
+ def no_digits!
51
+ @include_digits = false
52
+ return self
53
+ end
54
+
55
+ def include_symbols?
56
+ return @include_symbols
57
+ end
58
+
59
+ def include_symbols!
60
+ @include_symbols = true
61
+ return self
62
+ end
63
+
64
+ def no_symbols!
65
+ @include_symbols = false
66
+ return self
67
+ end
68
+
69
+ def exclude_ambiguous?
70
+ return @exclude_ambiguous
71
+ end
72
+
73
+ def exclude_ambiguous!
74
+ @exclude_ambiguous = true
75
+ return self
76
+ end
77
+
78
+ def do_not_exclude_ambiguous!
79
+ @exclude_ambiguous = false
80
+ return self
81
+ end
82
+
83
+ alias_method :dont_exclude_ambiguous!,
84
+ :do_not_exclude_ambiguous!
85
+
86
+ def exclude_vowels?
87
+ return @exclude_vowels
88
+ end
89
+
90
+ def exclude_vowels!
91
+ @exclude_vowels = true
92
+ return self
93
+ end
94
+
95
+ def do_not_exclude_vowels!
96
+ @exclude_vowels = false
97
+ return self
98
+ end
99
+
100
+ alias_method :dont_exclude_vowels!, :do_not_exclude_vowels!
101
+
102
+ attr_reader :length
103
+
104
+ def length= new_length
105
+ set_length! new_length
106
+ return new_length
107
+ end
108
+
109
+ def set_length! new_length
110
+ raise 'invalid length' \
111
+ unless new_length.is_a? Integer and new_length >= 1
112
+ @length = new_length
113
+ return self
114
+ end
115
+
116
+ def use_structureless_generator!
117
+ @mode = method :structureless
118
+ return
119
+ end
120
+
121
+ def use_anglophonemic_generator!
122
+ @mode = method :anglophonemic
123
+ return
124
+ end
125
+
126
+ def use_kanaphonemic_generator!
127
+ @mode = method :kanaphonemic
128
+ return
129
+ end
130
+
131
+ def generate
132
+ return @mode.call
133
+ end
134
+
135
+ def self::random_bool probability_of_true
136
+ return SecureRandom.random_number < probability_of_true
137
+ end
138
+
139
+ def self::random_item array
140
+ return array[SecureRandom.random_number array.length]
141
+ end
142
+
143
+ def structureless
144
+ charset = LOWERCASE
145
+ charset += DIGITS if include_digits?
146
+ charset += SYMBOLS if include_symbols?
147
+ charset = charset.split('')
148
+ charset -= AMBIGUOUS.split('') if exclude_ambiguous?
149
+ charset -= VOWELS.split('') if exclude_vowels?
150
+ return (0 ... @length).
151
+ map{PawGen.random_item charset}.
152
+ join ''
153
+ end
154
+
155
+ # Phoneme flags
156
+ PHF_CONSONANT = 0x01
157
+ PHF_VOWEL = 0x02
158
+ PHF_DIPTHONG = 0x04
159
+ PHF_NOT_FIRST = 0x08
160
+
161
+ ANGLOPHONEMES = [
162
+ ['a', PHF_VOWEL],
163
+ ['ae', PHF_VOWEL | PHF_DIPTHONG],
164
+ ['ah', PHF_VOWEL | PHF_DIPTHONG],
165
+ ['ai', PHF_VOWEL | PHF_DIPTHONG],
166
+ ['b', PHF_CONSONANT],
167
+ ['c', PHF_CONSONANT],
168
+ ['ch', PHF_CONSONANT | PHF_DIPTHONG],
169
+ ['d', PHF_CONSONANT],
170
+ ['e', PHF_VOWEL],
171
+ ['ee', PHF_VOWEL | PHF_DIPTHONG],
172
+ ['ei', PHF_VOWEL | PHF_DIPTHONG],
173
+ ['f', PHF_CONSONANT],
174
+ ['g', PHF_CONSONANT],
175
+ ['gh', PHF_CONSONANT | PHF_DIPTHONG | PHF_NOT_FIRST],
176
+ ['h', PHF_CONSONANT],
177
+ ['i', PHF_VOWEL],
178
+ ['ie', PHF_VOWEL | PHF_DIPTHONG],
179
+ ['j', PHF_CONSONANT],
180
+ ['k', PHF_CONSONANT],
181
+ ['l', PHF_CONSONANT],
182
+ ['m', PHF_CONSONANT],
183
+ ['n', PHF_CONSONANT],
184
+ ['ng', PHF_CONSONANT | PHF_DIPTHONG | PHF_NOT_FIRST],
185
+ ['o', PHF_VOWEL],
186
+ ['oh', PHF_VOWEL | PHF_DIPTHONG],
187
+ ['oo', PHF_VOWEL | PHF_DIPTHONG],
188
+ ['p', PHF_CONSONANT],
189
+ ['ph', PHF_CONSONANT | PHF_DIPTHONG],
190
+ ['qu', PHF_CONSONANT | PHF_DIPTHONG],
191
+ ['r', PHF_CONSONANT],
192
+ ['s', PHF_CONSONANT],
193
+ ['sh', PHF_CONSONANT | PHF_DIPTHONG],
194
+ ['t', PHF_CONSONANT],
195
+ ['th', PHF_CONSONANT | PHF_DIPTHONG],
196
+ ['u', PHF_VOWEL],
197
+ ['v', PHF_CONSONANT],
198
+ ['w', PHF_CONSONANT],
199
+ ['x', PHF_CONSONANT],
200
+ ['y', PHF_CONSONANT],
201
+ ['z', PHF_CONSONANT],
202
+ ]
203
+
204
+ ANGLOPHONEMES.each do |entry|
205
+ entry.first.freeze
206
+ entry.freeze
207
+ end
208
+ ANGLOPHONEMES.freeze
209
+
210
+ ANGLOVOWELS = ANGLOPHONEMES.
211
+ select{|entry| (entry[1] & PHF_VOWEL) != 0}.
212
+ freeze
213
+ ANGLOCONSONANTS = ANGLOPHONEMES.
214
+ select{|entry| (entry[1] & PHF_CONSONANT) != 0}.
215
+ freeze
216
+
217
+ # Requirement flags
218
+ REQ_UPPERCASE = 0x01
219
+ REQ_DIGITS = 0x02
220
+ REQ_SYMBOLS = 0x04
221
+
222
+ def anglophonemic
223
+ raise 'unable to generate such a ' +
224
+ 'short anglophonemic password' \
225
+ unless length >= 5
226
+ raise 'unable to generate an ' +
227
+ 'anglophonemic password with no vowels' \
228
+ if exclude_vowels?
229
+
230
+ reqs = 0
231
+ reqs |= REQ_UPPERCASE if include_uppercase?
232
+ reqs |= REQ_DIGITS if include_digits?
233
+ reqs |= REQ_SYMBOLS if include_symbols?
234
+ result, unmet_reqs = [] # declare local variables
235
+ begin
236
+ unmet_reqs = reqs
237
+ first = true
238
+ # also re-set after a digit (but not after a symbol)
239
+ result = ''
240
+ prev_phoneme_flags = 0
241
+ should_be = nil
242
+ while result.length < length do
243
+ should_be ||=
244
+ PawGen.random_item [ANGLOVOWELS, ANGLOCONSONANTS]
245
+ phoneme, phoneme_flags = PawGen.random_item should_be
246
+ next if first and (phoneme_flags & PHF_NOT_FIRST) != 0
247
+ # block a vowel followed by a vowel-dipthong
248
+ next if (prev_phoneme_flags & PHF_VOWEL) != 0 and
249
+ (phoneme_flags & PHF_VOWEL) != 0 and
250
+ (phoneme_flags & PHF_DIPTHONG) != 0
251
+ next if result.length + phoneme.length > length
252
+
253
+ if include_uppercase? and
254
+ (first or (phoneme_flags & PHF_CONSONANT) != 0) and
255
+ PawGen.random_bool 0.2 then
256
+ phoneme = phoneme.capitalize
257
+ unmet_reqs &= ~REQ_UPPERCASE
258
+ end
259
+
260
+ # Note that the unambiguous lowercase 'o' can become the
261
+ # ambiguous 'O' through capitalisation. Contrariwise,
262
+ # the ambiguous lowercase 'l' can become the unambiguous
263
+ # uppercase 'L'.
264
+ next if exclude_ambiguous? and
265
+ phoneme.split('').any?{|c| AMBIGUOUS.include? c}
266
+
267
+ # All the checks for the phoneme passed. Let's add it.
268
+ result << phoneme
269
+
270
+ break if result.length == length
271
+
272
+ # Checking against the [[first]] flag here means
273
+ # requiring at least _two_ phonemes before the digit can
274
+ # appear, because if we just applied the first phoneme,
275
+ # [[first]] is still true.
276
+ if include_digits? and
277
+ !first and
278
+ PawGen.random_bool 0.3 then
279
+ choice = DIGITS.split ''
280
+ choice -= AMBIGUOUS.split '' if exclude_ambiguous?
281
+ result << PawGen.random_item(choice)
282
+ unmet_reqs &= ~REQ_DIGITS
283
+ first = true
284
+ prev_phoneme_flags = 0
285
+ should_be = nil # pick next [[should_be]] at random
286
+ next
287
+ end
288
+
289
+ if include_symbols? and
290
+ !first and
291
+ PawGen.random_bool 0.2 then
292
+ choice = SYMBOLS.split ''
293
+ # Our [[AMBIGUOUS]] does not actually contain any
294
+ # symbols in the current version. This may be a
295
+ # problem, considering [['`]] and [[!/1l|]] and [[&8]]
296
+ # and [[-_]] and [[^~]] and maybe even [[.,]] and
297
+ # [[:;]] and [[()l|]] so we're excluding [[AMBIGUOUS]]
298
+ # anyway in anticipation of a future version
299
+ # mentioning some of those.
300
+ choice -= AMBIGUOUS.split '' if exclude_ambiguous?
301
+ result << PawGen.random_item(choice)
302
+ unmet_reqs &= ~REQ_SYMBOLS
303
+ # not resetting [[first]]; not [[next]]ing
304
+ end
305
+
306
+ if should_be.object_id == ANGLOCONSONANTS.object_id then
307
+ should_be = ANGLOVOWELS
308
+ else
309
+ if (prev_phoneme_flags & PHF_VOWEL) != 0 or
310
+ (phoneme_flags & PHF_DIPTHONG) != 0 or
311
+ PawGen.random_bool(0.6) then
312
+ should_be = ANGLOCONSONANTS
313
+ else
314
+ should_be = ANGLOVOWELS
315
+ end
316
+ end
317
+ prev_phoneme_flags = phoneme_flags
318
+ first = false
319
+ end
320
+ end until unmet_reqs.zero?
321
+ return result
322
+ end
323
+
324
+ # Real kana includes signs to prolongate a preceding vowel or
325
+ # a following consonant. We're not including these, based on
326
+ # the wild-assed guess that the advantage in memorability is
327
+ # outweighed by the disadvantage in reduced password space.
328
+ #
329
+ # Similarly, we're not indicating palatalisation: we'll use
330
+ # 'ti' rather than 'chi', 'tu' rather than 'tsu', and so on.
331
+ KANA_MORAE = ([''] + %w{k g s z t d n h b p m y r w}).
332
+ map{|c| %w{a i u e o}.map{|v| c + v}}.
333
+ flatten + %w{n} - %w{yi ye wu}
334
+ KANA_MORAE.each &:freeze
335
+ KANA_MORAE.freeze
336
+
337
+ def kanaphonemic
338
+ raise 'unable to generate such a ' +
339
+ 'short kanaphonemic password' \
340
+ unless length >= 5
341
+ raise 'unable to generate an ' +
342
+ 'kanaphonemic password with no vowels' \
343
+ if exclude_vowels?
344
+
345
+ reqs = 0
346
+ reqs |= REQ_UPPERCASE if include_uppercase?
347
+ reqs |= REQ_DIGITS if include_digits?
348
+ reqs |= REQ_SYMBOLS if include_symbols?
349
+ result, unmet_reqs = [] # declare local variables
350
+ begin
351
+ unmet_reqs = reqs
352
+ first = true
353
+ # also re-set after a digit (but not after a symbol)
354
+ result = ''
355
+ while result.length < length do
356
+ phoneme = PawGen.random_item KANA_MORAE
357
+ next if result.length + phoneme.length > length
358
+
359
+ if include_uppercase? and
360
+ PawGen.random_bool 0.2 then
361
+ phoneme = phoneme.capitalize
362
+ unmet_reqs &= ~REQ_UPPERCASE
363
+ end
364
+
365
+ # Note that the unambiguous lowercase 'o' can become the
366
+ # ambiguous 'O' through capitalisation.
367
+ next if exclude_ambiguous? and
368
+ phoneme.split('').any?{|c| AMBIGUOUS.include? c}
369
+
370
+ # All the checks for the phoneme passed. Let's add it.
371
+ result << phoneme
372
+
373
+ break if result.length == length
374
+
375
+ # Checking against the [[first]] flag here means
376
+ # requiring at least _two_ phonemes before the digit can
377
+ # appear, because if we just applied the first phoneme,
378
+ # [[first]] is still true.
379
+ if include_digits? and
380
+ !first and
381
+ PawGen.random_bool 0.3 then
382
+ choice = DIGITS.split ''
383
+ choice -= AMBIGUOUS.split '' if exclude_ambiguous?
384
+ result << PawGen.random_item(choice)
385
+ unmet_reqs &= ~REQ_DIGITS
386
+ first = true
387
+ next
388
+ end
389
+
390
+ if include_symbols? and
391
+ !first and
392
+ PawGen.random_bool 0.2 then
393
+ choice = SYMBOLS
394
+ # Our [[AMBIGUOUS]] does not actually contain any
395
+ # symbols in the current version. This may be a
396
+ # problem, considering [['`]] and [[!/1l|]] and [[&8]]
397
+ # and [[-_]] and [[^~]] and maybe even [[.,]] and
398
+ # [[:;]] and [[()l|]] so we're excluding [[AMBIGUOUS]]
399
+ # anyway in anticipation of a future version
400
+ # mentioning some of those.
401
+ choice -= AMBIGUOUS.split '' if exclude_ambiguous?
402
+ result << PawGen.random_item(choice)
403
+ unmet_reqs &= ~REQ_SYMBOLS
404
+ # not resetting [[first]]; not [[next]]ing
405
+ end
406
+
407
+ first = false
408
+ end
409
+ end until unmet_reqs.zero?
410
+ return result
411
+ end
412
+ end
data/pawgen.gemspec ADDED
@@ -0,0 +1,18 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'pawgen'
3
+ s.version = '1.0.0'
4
+ s.date = '2014-10-20'
5
+ s.homepage = 'https://github.com/digwuren/pawgen'
6
+ s.summary = 'A password generator'
7
+ s.author = 'Andres Soolo'
8
+ s.email = 'dig@mirky.net'
9
+ s.files = File.read('Manifest.txt').split(/\n/)
10
+ s.executables << 'pawgen'
11
+ s.license = 'GPL-2'
12
+ s.description = <<EOD
13
+ Pawgen is a password generation tool inspired by Theodore Ts'o's
14
+ pwgen. Generates anglophonemic, kanaphonemic, or syntaxless
15
+ passwords. Implemented in pure Ruby.
16
+ EOD
17
+ s.has_rdoc = false
18
+ end
metadata ADDED
@@ -0,0 +1,54 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pawgen
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Andres Soolo
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-10-20 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: |
14
+ Pawgen is a password generation tool inspired by Theodore Ts'o's
15
+ pwgen. Generates anglophonemic, kanaphonemic, or syntaxless
16
+ passwords. Implemented in pure Ruby.
17
+ email: dig@mirky.net
18
+ executables:
19
+ - pawgen
20
+ extensions: []
21
+ extra_rdoc_files: []
22
+ files:
23
+ - GPL-2
24
+ - GPL-3
25
+ - Manifest.txt
26
+ - README
27
+ - bin/pawgen
28
+ - lib/pawgen.rb
29
+ - pawgen.gemspec
30
+ homepage: https://github.com/digwuren/pawgen
31
+ licenses:
32
+ - GPL-2
33
+ metadata: {}
34
+ post_install_message:
35
+ rdoc_options: []
36
+ require_paths:
37
+ - lib
38
+ required_ruby_version: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - '>='
41
+ - !ruby/object:Gem::Version
42
+ version: '0'
43
+ required_rubygems_version: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ requirements: []
49
+ rubyforge_project:
50
+ rubygems_version: 2.0.14
51
+ signing_key:
52
+ specification_version: 4
53
+ summary: A password generator
54
+ test_files: []