Class: Password
- Inherits:
-
String
- Object
- String
- Password
- Defined in:
- lib/password.rb
Overview
Ruby/Password is a collection of password handling routines for Ruby, including an interface to CrackLib for the purposes of testing password strength.
require 'password'
# Define and check a password in code
pw = Password.new( "bigblackcat" )
pw.check
# Get and check a password from the keyboard
begin
password = Password.get( "New password: " )
password.check
rescue Password::WeakPassword => reason
puts reason
retry
end
# Automatically generate and encrypt a password
password = Password.phonemic( 12, Password:ONE_CASE | Password::ONE_DIGIT )
crypted = password.crypt
Defined Under Namespace
Classes: CryptError, DictionaryError, WeakPassword
Constant Summary collapse
- VERSION =
'0.5.3'
- DES =
DES algorithm
true
- MD5 =
MD5 algorithm (see crypt(3) for more information)
false
- ONE_DIGIT =
This flag is used in conjunction with Password.phonemic and states that a password must include a digit.
1
- ONE_CASE =
This flag is used in conjunction with Password.phonemic and states that a password must include a capital letter.
1 << 1
- PASSWD_CHARS =
Characters that may appear in generated passwords. Password.urandom may also use the characters + and /.
'0123456789' + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + 'abcdefghijklmnopqrstuvwxyz'
- SALT_CHARS =
Valid salt characters for use by Password#crypt.
'0123456789' + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + 'abcdefghijklmnopqrstuvwxyz' + './'
- CONSONANT =
phoneme flags
1
- VOWEL =
1 << 1
- DIPHTHONG =
1 << 2
- NOT_FIRST =
indicates that a given phoneme may not occur first
1 << 3
- PHONEMES =
{ :a => VOWEL, :ae => VOWEL | DIPHTHONG, :ah => VOWEL | DIPHTHONG, :ai => VOWEL | DIPHTHONG, :b => CONSONANT, :c => CONSONANT, :ch => CONSONANT | DIPHTHONG, :d => CONSONANT, :e => VOWEL, :ee => VOWEL | DIPHTHONG, :ei => VOWEL | DIPHTHONG, :f => CONSONANT, :g => CONSONANT, :gh => CONSONANT | DIPHTHONG | NOT_FIRST, :h => CONSONANT, :i => VOWEL, :ie => VOWEL | DIPHTHONG, :j => CONSONANT, :k => CONSONANT, :l => CONSONANT, :m => CONSONANT, :n => CONSONANT, :ng => CONSONANT | DIPHTHONG | NOT_FIRST, :o => VOWEL, :oh => VOWEL | DIPHTHONG, :oo => VOWEL | DIPHTHONG, :p => CONSONANT, :ph => CONSONANT | DIPHTHONG, :qu => CONSONANT | DIPHTHONG, :r => CONSONANT, :s => CONSONANT, :sh => CONSONANT | DIPHTHONG, :t => CONSONANT, :th => CONSONANT | DIPHTHONG, :u => VOWEL, :v => CONSONANT, :w => CONSONANT, :x => CONSONANT, :y => CONSONANT, :z => CONSONANT }
Class Method Summary collapse
-
.echo(on = true, masked = false) ⇒ Object
Turn local terminal echo on or off.
-
.get(message = "Password: ") ⇒ Object
Get a password from STDIN, using buffered line input and displaying message as the prompt.
-
.get_vowel_or_consonant ⇒ Object
Determine whether next character should be a vowel or consonant.
-
.getc(message = "Password: ", mask = '*') ⇒ Object
Get a password from STDIN in unbuffered mode, i.e.
-
.phonemic(length = 8, flags = nil) ⇒ Object
Generate a memorable password of length characters, using phonemes that a human-being can easily remember.
-
.random(length = 8) ⇒ Object
Generate a random password of length characters.
-
.urandom(length = 8) ⇒ Object
An alternative to Password.random.
Instance Method Summary collapse
-
#crypt(type = DES, salt = '') ⇒ Object
Encrypt a password using type encryption.
Class Method Details
.echo(on = true, masked = false) ⇒ Object
Turn local terminal echo on or off. This method is used for securing the display, so that a soon to be entered password will not be echoed to the screen. It is also used for restoring the display afterwards.
If masked is true
, the keyboard is put into unbuffered mode, allowing the retrieval of characters one at a time. masked has no effect when on is false
. You are unlikely to need this method in the course of normal operations.
166 167 168 169 170 171 172 173 174 175 176 177 |
# File 'lib/password.rb', line 166 def Password.echo(on=true, masked=false) term = Termios::getattr( $stdin ) if on term.c_lflag |= ( Termios::ECHO | Termios::ICANON ) else # off term.c_lflag &= ~Termios::ECHO term.c_lflag &= ~Termios::ICANON if masked end Termios::setattr( $stdin, Termios::TCSANOW, term ) end |
.get(message = "Password: ") ⇒ Object
Get a password from STDIN, using buffered line input and displaying message as the prompt. No output will appear while the password is being typed. Hitting [Enter] completes password entry. If STDIN is not connected to a tty, no prompt will be displayed.
185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 |
# File 'lib/password.rb', line 185 def Password.get(="Password: ") begin if $stdin.tty? Password.echo false print if end pw = Password.new( $stdin.gets || "" ) pw.chomp! ensure if $stdin.tty? Password.echo true print "\n" end end end |
.get_vowel_or_consonant ⇒ Object
Determine whether next character should be a vowel or consonant.
241 242 243 |
# File 'lib/password.rb', line 241 def Password.get_vowel_or_consonant rand( 2 ) == 1 ? VOWEL : CONSONANT end |
.getc(message = "Password: ", mask = '*') ⇒ Object
Get a password from STDIN in unbuffered mode, i.e. one key at a time. message will be displayed as the prompt and each key press with echo mask to the terminal. There is no need to hit [Enter] at the end.
208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 |
# File 'lib/password.rb', line 208 def Password.getc(="Password: ", mask='*') # Save current buffering mode buffering = $stdout.sync # Turn off buffering $stdout.sync = true begin Password.echo(false, true) print if pw = "" while ( char = $stdin.getc ) != 10 # break after [Enter] putc mask pw << char end ensure Password.echo true print "\n" end # Restore original buffering mode $stdout.sync = buffering Password.new( pw ) end |
.phonemic(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:
pw = Password.phonemic( 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.
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 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 |
# File 'lib/password.rb', line 265 def Password.phonemic(length=8, flags=nil) pw = nil ph_flags = flags loop do pw = "" # 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 pw.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 and ph_flags.include? NOT_FIRST # Don't allow a VOWEL followed a vowel/diphthong pair next if prev.include? VOWEL and ph_flags.include? VOWEL and ph_flags.include? DIPHTHONG # Don't allow us to go longer than the desired length next if ph_len > length - pw.length # We've found a phoneme that meets our criteria pw << phoneme # Handle ONE_CASE if feature_flags.include? ONE_CASE if (first or ph_flags.include? CONSONANT) and rand( 10 ) < 3 pw[-ph_len, 1] = pw[-ph_len, 1].upcase feature_flags.delete ONE_CASE end end # Is password already long enough? break if pw.length >= length # Handle ONE_DIGIT if feature_flags.include? ONE_DIGIT if ! first and rand( 10 ) < 3 pw << ( rand( 10 ) + ?0 ).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 or ph_flags.include? DIPHTHONG or rand(10) > 3 desired = CONSONANT else desired = VOWEL end prev = ph_flags first = false end # Try again break unless feature_flags.include? ONE_CASE or feature_flags.include? ONE_DIGIT end Password.new( pw ) end |
.random(length = 8) ⇒ Object
Generate a random password of length characters. Unlike the Password.phonemic method, no attempt will be made to generate a memorable password. Generated passwords may contain any of the characters in Password::PASSWD_CHARS.
370 371 372 373 374 375 376 377 |
# File 'lib/password.rb', line 370 def Password.random(length=8) pw = "" nr_chars = PASSWD_CHARS.size length.times { pw << PASSWD_CHARS[ rand( nr_chars ) ] } Password.new( pw ) end |
.urandom(length = 8) ⇒ Object
An alternative to Password.random. It uses the /dev/urandom
device to generate passwords, returning nil
on systems that do not implement the device. The passwords it generates may contain any of the characters in Password::PASSWD_CHARS, plus the additional characters + and /.
386 387 388 389 390 391 392 393 394 |
# File 'lib/password.rb', line 386 def Password.urandom(length=8) return nil unless File.chardev? '/dev/urandom' rand_data = nil File.open( "/dev/urandom" ) { |f| rand_data = f.read( length ) } # Base64 encode it Password.new( [ rand_data ].pack( 'm' )[ 0 .. length - 1 ] ) end |
Instance Method Details
#crypt(type = DES, salt = '') ⇒ Object
Encrypt a password using type encryption. salt, if supplied, will be used to perturb the encryption algorithm and should be chosen from the Password::SALT_CHARS. If no salt is given, a randomly generated salt will be used.
402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 |
# File 'lib/password.rb', line 402 def crypt(type=DES, salt='') unless ( salt.split( // ) - SALT_CHARS.split( // ) ).empty? raise CryptError, 'bad salt' end salt = Password.random( type ? 2 : 8 ) if salt.empty? # (Linux glibc2 interprets a salt prefix of '$1$' as a call to use MD5 # instead of DES when calling crypt(3)) salt = '$1$' + salt if type == MD5 # Pass to crypt in class String (our parent class) crypt = super( salt ) # Raise an exception if MD5 was wanted, but result is not recognisable if type == MD5 && crypt !~ /^\$1\$/ raise CryptError, 'MD5 not implemented' end crypt end |