Class: PawGen

Inherits:
Object
  • Object
show all
Defined in:
lib/pawgen.rb

Constant Summary collapse

DIGITS =
'0123456789'.freeze
UPPERCASE =
'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.freeze
LOWERCASE =
'abcdefghijklmnopqrstuvwxyz'.freeze
SYMBOLS =
'!"#$%&\'()*+,-./:<=>?@[\\]^_`{|}~'.freeze
AMBIGUOUS =
'B8G6I1l0OQDS5Z2'.freeze
VOWELS =
'01aeiouyAEIOUY'.freeze
PHF_CONSONANT =

Phoneme flags

0x01
PHF_VOWEL =
0x02
PHF_DIPTHONG =
0x04
PHF_NOT_FIRST =
0x08
ANGLOPHONEMES =
[
  ['a',   PHF_VOWEL],
  ['ae',  PHF_VOWEL | PHF_DIPTHONG],
  ['ah',  PHF_VOWEL | PHF_DIPTHONG],
  ['ai',  PHF_VOWEL | PHF_DIPTHONG],
  ['b',   PHF_CONSONANT],
  ['c',   PHF_CONSONANT],
  ['ch',  PHF_CONSONANT | PHF_DIPTHONG],
  ['d',   PHF_CONSONANT],
  ['e',   PHF_VOWEL],
  ['ee',  PHF_VOWEL | PHF_DIPTHONG],
  ['ei',  PHF_VOWEL | PHF_DIPTHONG],
  ['f',   PHF_CONSONANT],
  ['g',   PHF_CONSONANT],
  ['gh',  PHF_CONSONANT | PHF_DIPTHONG | PHF_NOT_FIRST],
  ['h',   PHF_CONSONANT],
  ['i',   PHF_VOWEL],
  ['ie',  PHF_VOWEL | PHF_DIPTHONG],
  ['j',   PHF_CONSONANT],
  ['k',   PHF_CONSONANT],
  ['l',   PHF_CONSONANT],
  ['m',   PHF_CONSONANT],
  ['n',   PHF_CONSONANT],
  ['ng',  PHF_CONSONANT | PHF_DIPTHONG | PHF_NOT_FIRST],
  ['o',   PHF_VOWEL],
  ['oh',  PHF_VOWEL | PHF_DIPTHONG],
  ['oo',  PHF_VOWEL | PHF_DIPTHONG],
  ['p',   PHF_CONSONANT],
  ['ph',  PHF_CONSONANT | PHF_DIPTHONG],
  ['qu',  PHF_CONSONANT | PHF_DIPTHONG],
  ['r',   PHF_CONSONANT],
  ['s',   PHF_CONSONANT],
  ['sh',  PHF_CONSONANT | PHF_DIPTHONG],
  ['t',   PHF_CONSONANT],
  ['th',  PHF_CONSONANT | PHF_DIPTHONG],
  ['u',   PHF_VOWEL],
  ['v',   PHF_CONSONANT],
  ['w',   PHF_CONSONANT],
  ['x',   PHF_CONSONANT],
  ['y',   PHF_CONSONANT],
  ['z',   PHF_CONSONANT],
]
ANGLOVOWELS =
ANGLOPHONEMES.
select{|entry| (entry[1] & PHF_VOWEL) != 0}.
freeze
ANGLOCONSONANTS =
ANGLOPHONEMES.
select{|entry| (entry[1] & PHF_CONSONANT) != 0}.
freeze
REQ_UPPERCASE =

Requirement flags

0x01
REQ_DIGITS =
0x02
REQ_SYMBOLS =
0x04
KANA_MORAE =

Real kana includes signs to prolongate a preceding vowel or a following consonant. We’re not including these, based on the wild-assed guess that the advantage in memorability is outweighed by the disadvantage in reduced password space.

Similarly, we’re not indicating palatalisation: we’ll use ‘ti’ rather than ‘chi’, ‘tu’ rather than ‘tsu’, and so on.

([''] + %w{k g s z t d n h b p m y r w}).
map{|c| %w{a i u e o}.map{|v| c + v}}.
flatten + %w{n} - %w{yi ye wu}

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializePawGen

Returns a new instance of PawGen.



11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# File 'lib/pawgen.rb', line 11

def initialize
  super()
  # The original defaulted the length to 8, but it's a bit too
  # short to be the default length these days -- especially
  # considering that phonemic password construction shrinks
  # the password space.
  @length = 12
  @include_uppercase = true
  @include_digits = true
  @include_symbols = false
  @exclude_ambiguous = false
  @exclude_vowels = false
  @mode = method :anglophonemic
  return
end

Instance Attribute Details

#lengthObject

Returns the value of attribute length.



102
103
104
# File 'lib/pawgen.rb', line 102

def length
  @length
end

Class Method Details

.random_bool(probability_of_true) ⇒ Object



135
136
137
# File 'lib/pawgen.rb', line 135

def self::random_bool probability_of_true
  return SecureRandom.random_number < probability_of_true
end

.random_item(array) ⇒ Object



139
140
141
# File 'lib/pawgen.rb', line 139

def self::random_item array
  return array[SecureRandom.random_number array.length]
end

Instance Method Details

#anglophonemicObject



223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
# File 'lib/pawgen.rb', line 223

def anglophonemic
  raise 'unable to generate such a ' +
      'short anglophonemic password' \
      unless length >= 5
  raise 'unable to generate an ' +
      'anglophonemic password with no vowels' \
      if exclude_vowels?

  reqs = 0
  reqs |= REQ_UPPERCASE if include_uppercase?
  reqs |= REQ_DIGITS if include_digits?
  reqs |= REQ_SYMBOLS if include_symbols?
  result, unmet_reqs = [] # declare local variables
  begin
    unmet_reqs = reqs
    first = true
        # also re-set after a digit (but not after a symbol)
    result = ''
    prev_phoneme_flags = 0
    should_be = nil
    while result.length < length do
      should_be ||=
          PawGen.random_item [ANGLOVOWELS, ANGLOCONSONANTS]
      phoneme, phoneme_flags = PawGen.random_item should_be
      next if first and (phoneme_flags & PHF_NOT_FIRST) != 0
      # block a vowel followed by a vowel-dipthong
      next if (prev_phoneme_flags & PHF_VOWEL) != 0 and
          (phoneme_flags & PHF_VOWEL) != 0 and
          (phoneme_flags & PHF_DIPTHONG) != 0
      next if result.length + phoneme.length > length

      if include_uppercase? and
          (first or (phoneme_flags & PHF_CONSONANT) != 0) and
          PawGen.random_bool 0.2 then
        phoneme = phoneme.capitalize
        unmet_reqs &= ~REQ_UPPERCASE
      end

      # Note that the unambiguous lowercase 'o' can become the
      # ambiguous 'O' through capitalisation.  Contrariwise,
      # the ambiguous lowercase 'l' can become the unambiguous
      # uppercase 'L'.
      next if exclude_ambiguous? and
          phoneme.split('').any?{|c| AMBIGUOUS.include? c}

      # All the checks for the phoneme passed.  Let's add it.
      result << phoneme

      break if result.length == length

      # Checking against the [[first]] flag here means
      # requiring at least _two_ phonemes before the digit can
      # appear, because if we just applied the first phoneme,
      # [[first]] is still true.
      if include_digits? and
          !first and
          PawGen.random_bool 0.3 then
        choice = DIGITS.split ''
        choice -= AMBIGUOUS.split '' if exclude_ambiguous?
        result << PawGen.random_item(choice)
        unmet_reqs &= ~REQ_DIGITS
        first = true
        prev_phoneme_flags = 0
        should_be = nil # pick next [[should_be]] at random
        next
      end

      if include_symbols? and
          !first and
          PawGen.random_bool 0.2 then
        choice = SYMBOLS.split ''
        # Our [[AMBIGUOUS]] does not actually contain any
        # symbols in the current version.  This may be a
        # problem, considering [['`]] and [[!/1l|]] and [[&8]]
        # and [[-_]] and [[^~]] and maybe even [[.,]] and
        # [[:;]] and [[()l|]] so we're excluding [[AMBIGUOUS]]
        # anyway in anticipation of a future version
        # mentioning some of those.
        choice -= AMBIGUOUS.split '' if exclude_ambiguous?
        result << PawGen.random_item(choice)
        unmet_reqs &= ~REQ_SYMBOLS
        # not resetting [[first]]; not [[next]]ing
      end

      if should_be.object_id == ANGLOCONSONANTS.object_id then
        should_be = ANGLOVOWELS
      else
        if (prev_phoneme_flags & PHF_VOWEL) != 0 or
            (phoneme_flags & PHF_DIPTHONG) != 0 or
            PawGen.random_bool(0.6) then
          should_be = ANGLOCONSONANTS
        else
          should_be = ANGLOVOWELS
        end
      end
      prev_phoneme_flags = phoneme_flags
      first = false
    end
  end until unmet_reqs.zero?
  return result
end

#do_not_exclude_ambiguous!Object Also known as: dont_exclude_ambiguous!



78
79
80
81
# File 'lib/pawgen.rb', line 78

def do_not_exclude_ambiguous!
  @exclude_ambiguous = false
  return self
end

#do_not_exclude_vowels!Object Also known as: dont_exclude_vowels!



95
96
97
98
# File 'lib/pawgen.rb', line 95

def do_not_exclude_vowels!
  @exclude_vowels = false
  return self
end

#exclude_ambiguous!Object



73
74
75
76
# File 'lib/pawgen.rb', line 73

def exclude_ambiguous!
  @exclude_ambiguous = true
  return self
end

#exclude_ambiguous?Boolean

Returns:

  • (Boolean)


69
70
71
# File 'lib/pawgen.rb', line 69

def exclude_ambiguous?
  return @exclude_ambiguous
end

#exclude_vowels!Object



90
91
92
93
# File 'lib/pawgen.rb', line 90

def exclude_vowels!
  @exclude_vowels = true
  return self
end

#exclude_vowels?Boolean

Returns:

  • (Boolean)


86
87
88
# File 'lib/pawgen.rb', line 86

def exclude_vowels?
  return @exclude_vowels
end

#generateObject



131
132
133
# File 'lib/pawgen.rb', line 131

def generate
  return @mode.call
end

#include_digits!Object



45
46
47
48
# File 'lib/pawgen.rb', line 45

def include_digits!
  @include_digits = true
  return self
end

#include_digits?Boolean

Returns:

  • (Boolean)


41
42
43
# File 'lib/pawgen.rb', line 41

def include_digits?
  return @include_digits
end

#include_symbols!Object



59
60
61
62
# File 'lib/pawgen.rb', line 59

def include_symbols!
  @include_symbols = true
  return self
end

#include_symbols?Boolean

Returns:

  • (Boolean)


55
56
57
# File 'lib/pawgen.rb', line 55

def include_symbols?
  return @include_symbols
end

#include_uppercase!Object



31
32
33
34
# File 'lib/pawgen.rb', line 31

def include_uppercase!
  @include_uppercase = true
  return self
end

#include_uppercase?Boolean

Returns:

  • (Boolean)


27
28
29
# File 'lib/pawgen.rb', line 27

def include_uppercase?
  return @include_uppercase
end

#kanaphonemicObject



338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
# File 'lib/pawgen.rb', line 338

def kanaphonemic
  raise 'unable to generate such a ' +
      'short kanaphonemic password' \
      unless length >= 5
  raise 'unable to generate an ' +
      'kanaphonemic password with no vowels' \
      if exclude_vowels?

  reqs = 0
  reqs |= REQ_UPPERCASE if include_uppercase?
  reqs |= REQ_DIGITS if include_digits?
  reqs |= REQ_SYMBOLS if include_symbols?
  result, unmet_reqs = [] # declare local variables
  begin
    unmet_reqs = reqs
    first = true
        # also re-set after a digit (but not after a symbol)
    result = ''
    while result.length < length do
      phoneme = PawGen.random_item KANA_MORAE
      next if result.length + phoneme.length > length

      if include_uppercase? and
          PawGen.random_bool 0.2 then
        phoneme = phoneme.capitalize
        unmet_reqs &= ~REQ_UPPERCASE
      end

      # Note that the unambiguous lowercase 'o' can become the
      # ambiguous 'O' through capitalisation.
      next if exclude_ambiguous? and
          phoneme.split('').any?{|c| AMBIGUOUS.include? c}

      # All the checks for the phoneme passed.  Let's add it.
      result << phoneme

      break if result.length == length

      # Checking against the [[first]] flag here means
      # requiring at least _two_ phonemes before the digit can
      # appear, because if we just applied the first phoneme,
      # [[first]] is still true.
      if include_digits? and
          !first and
          PawGen.random_bool 0.3 then
        choice = DIGITS.split ''
        choice -= AMBIGUOUS.split '' if exclude_ambiguous?
        result << PawGen.random_item(choice)
        unmet_reqs &= ~REQ_DIGITS
        first = true
        next
      end

      if include_symbols? and
          !first and
          PawGen.random_bool 0.2 then
        choice = SYMBOLS
        # Our [[AMBIGUOUS]] does not actually contain any
        # symbols in the current version.  This may be a
        # problem, considering [['`]] and [[!/1l|]] and [[&8]]
        # and [[-_]] and [[^~]] and maybe even [[.,]] and
        # [[:;]] and [[()l|]] so we're excluding [[AMBIGUOUS]]
        # anyway in anticipation of a future version
        # mentioning some of those.
        choice -= AMBIGUOUS.split '' if exclude_ambiguous?
        result << PawGen.random_item(choice)
        unmet_reqs &= ~REQ_SYMBOLS
        # not resetting [[first]]; not [[next]]ing
      end

      first = false
    end
  end until unmet_reqs.zero?
  return result
end

#no_digits!Object



50
51
52
53
# File 'lib/pawgen.rb', line 50

def no_digits!
  @include_digits = false
  return self
end

#no_symbols!Object



64
65
66
67
# File 'lib/pawgen.rb', line 64

def no_symbols!
  @include_symbols = false
  return self
end

#no_uppercase!Object



36
37
38
39
# File 'lib/pawgen.rb', line 36

def no_uppercase!
  @include_uppercase = false
  return self
end

#set_length!(new_length) ⇒ Object



109
110
111
112
113
114
# File 'lib/pawgen.rb', line 109

def set_length! new_length
  raise 'invalid length' \
      unless new_length.is_a? Integer and new_length >= 1
  @length = new_length
  return self
end

#structurelessObject



143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/pawgen.rb', line 143

def structureless
  charset = LOWERCASE
  charset += UPPERCASE if include_uppercase?
  charset += DIGITS if include_digits?
  charset += SYMBOLS if include_symbols?
  charset = charset.split('')
  charset -= AMBIGUOUS.split('') if exclude_ambiguous?
  charset -= VOWELS.split('') if exclude_vowels?
  return (0 ... @length).
      map{PawGen.random_item charset}.
      join ''
end

#use_anglophonemic_generator!Object



121
122
123
124
# File 'lib/pawgen.rb', line 121

def use_anglophonemic_generator!
  @mode = method :anglophonemic
  return
end

#use_kanaphonemic_generator!Object



126
127
128
129
# File 'lib/pawgen.rb', line 126

def use_kanaphonemic_generator!
  @mode = method :kanaphonemic
  return
end

#use_structureless_generator!Object



116
117
118
119
# File 'lib/pawgen.rb', line 116

def use_structureless_generator!
  @mode = method :structureless
  return
end