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