Class: Milia::Password

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

Overview

Provides support generating memorable passwords

Class Method Summary collapse

Class Method Details

.generate(length = 8, flags = nil) ⇒ Object

Generate a memorable password of length characters, using phonemes that a human-being can easily remember. flags is one or more of Password::ONE_DIGIT and Password::ONE_CASE, logically OR’ed together. For example:

password = Password.generate(8, Password::ONE_DIGIT | Password::ONE_CASE)

This would generate an eight character password, containing a digit and an upper-case letter, such as Ug2shoth.

This method was inspired by the pwgen tool, written by Theodore Ts’o.

Generated passwords may contain any of the characters in Password::PASSWD_CHARS.



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
# File 'lib/milia/password_generator.rb', line 83

def generate(length = 8, flags = nil)
  password = nil
  ph_flags = flags
  
  loop do
    password = ''
    
    # Separate the flags integer into an array of individual flags
    feature_flags = [flags & ONE_DIGIT, flags & ONE_CASE]
    
    prev = []
    first = true
    desired = Password.get_vowel_or_consonant
    
    # Get an Array of all of the phonemes
    phonemes = PHONEMES.keys.map {|ph| ph.to_s}
    nr_phonemes = phonemes.size
    
    while password.length < length do
      # Get a random phoneme and its length
      phoneme = phonemes[rand(nr_phonemes)]
      ph_len = phoneme.length
      
      # Get its flags as an Array
      ph_flags = PHONEMES[phoneme.to_sym]
      ph_flags = [ph_flags & CONSONANT, ph_flags & VOWEL, ph_flags & DIPHTHONG, ph_flags & NOT_FIRST]
      
      # Filter on the basic type of the next phoneme
      next if ph_flags.include?(desired)
      
      # Handle the NOT_FIRST flag
      next if first && ph_flags.include?(NOT_FIRST)
      
      # Don't allow a VOWEL followed a vowel/diphthong pair
      next if prev.include?(VOWEL) && ph_flags.include?(VOWEL) && ph_flags.include?(DIPHTHONG)
      
      # Don't allow us to go longer than the desired length
      next if ph_len > length - password.length
      
      # We've found a phoneme that meets our criteria
      password << phoneme
      
      # Handle ONE_CASE
      if feature_flags.include?(ONE_CASE)
        if (first || ph_flags.include?(CONSONANT)) && rand(10) < 3
          password[-ph_len, 1] = password[-ph_len, 1].upcase
          feature_flags.delete(ONE_CASE)
        end
      end
      
      # Is password already long enough?
      break if password.length >= length
      
      # Handle ONE_DIGIT
      if feature_flags.include?(ONE_DIGIT)
        if !first && rand(10) < 3
          password << (rand(10) + '0'.ord).chr
          feature_flags.delete(ONE_DIGIT)
          
          first = true
          prev = []
          desired = Password.get_vowel_or_consonant
          next
        end
      end
      
      if desired == CONSONANT
        desired = VOWEL
      elsif prev.include?(VOWEL) || ph_flags.include?(DIPHTHONG) || rand(10) > 3
        desired = CONSONANT
      else
        desired = VOWEL
      end
      
      prev = ph_flags
      first = false
    end
    
    # Try again
    break unless feature_flags.include?(ONE_CASE) || feature_flags.include?(ONE_DIGIT)
    
  end
  
  password
end

.get_vowel_or_consonantObject

Determine whether the next character should be a vowel or consonant.



64
65
66
# File 'lib/milia/password_generator.rb', line 64

def get_vowel_or_consonant
  rand( 2 ) == 1 ? VOWEL : CONSONANT
end