class Passmakr
Class to generate easy to remember passwords, random passwords and urandom passwords, it also has various utilities to crypt and hash passwords in ways compatable with various things like apache htpasswd, /etc/passwd, /etc/shadow and so forth
It include code from www.caliban.org/ruby/ruby-password.shtml
Attributes
Public Class Methods
Creates a password instance, possible modes are:
-
:phonemic - produces easily remembered passwords
-
:random - uses the ruby random function to generate a password
-
:urandom - uses linux /dev/urandom to generate a password
-
anything else will be used as the password instead of generating one
Each instance will have a unique hash of password information in the password attribute, the hash will have members:
-
:string - the actual password string
-
:crypt - a crypt encoded version with a salt, usable in unix password hashes
-
:md5 - a md5 hash usable in unix password hashes
-
:nato - a NATO alphabet readable version of the password
-
:rot13 - for kicks, a ro13 encoded version of the password
# File lib/passmakr.rb, line 103 def initialize(mode=:phonemic, length=8) pw = nil case mode when :urandom pw = urandom(length) when :random pw = random(length) when :phonemic pw = phonemic(length) else pw = mode end preppw(pw) end
Private Instance Methods
Encrypt a password using type encryption. salt, if supplied, will be used to perturb the encryption algorithm and should be chosen from the Passmakr::SALT_CHARS. If no salt is given, a randomly generated salt will be used.
# File lib/passmakr.rb, line 274 def crypt(pw, type=DES, salt='') unless ( salt.split( // ) - SALT_CHARS.split( // ) ).empty? raise CryptError, 'bad salt' end salt = 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 crypt = pw.crypt(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
Determine whether next character should be a vowel or consonant.
# File lib/passmakr.rb, line 122 def get_vowel_or_consonant rand( 2 ) == 1 ? VOWEL : CONSONANT end
Generate a memorable password of length characters, using phonemes that a human-being can easily remember. flags is one or more of Passmakr::ONE_DIGIT and Passmakr::ONE_CASE, logically OR’ed together. For example:
pw = Passmakr.phonemic( 8, Passmakr::ONE_DIGIT | Passmakr::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 Passmakr::PASSWD_CHARS.
# File lib/passmakr.rb, line 142 def phonemic(length=8, flags=Passmakr::ONE_CASE) 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 = 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 = 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 pw end
prepares @password using the password passed as a string
# File lib/passmakr.rb, line 297 def preppw(pw) rot13 = pw.tr("A-Za-z", "N-ZA-Mn-za-m") @password = {:string => pw, :nato => pw.to_nato, :crypt => crypt(pw), :md5 => crypt(pw, MD5), :rot13 => rot13 } end
Generate a random password of length characters. Unlike the Passmakr.phonemic
method, no attempt will be made to generate a memorable password. Generated passwords may contain any of the characters in Passmakr::PASSWD_CHARS.
# File lib/passmakr.rb, line 243 def random(length=8) pw = "" nr_chars = PASSWD_CHARS.size srand() length.times { pw << PASSWD_CHARS[ rand( nr_chars ) ] } pw end
An alternative to Passmakr.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 Passmakr::PASSWD_CHARS, plus the additional characters + and /.
# File lib/passmakr.rb, line 260 def 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 pw = [ rand_data ].pack( 'm' )[ 0 .. length - 1 ] end