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

password[R]

Public Class Methods

new(mode=:phonemic, length=8) click to toggle source

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

crypt(pw, type=DES, salt='') click to toggle source

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
get_vowel_or_consonant() click to toggle source

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
phonemic(length=8, flags=Passmakr::ONE_CASE) click to toggle source

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
preppw(pw) click to toggle source

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
random(length=8) click to toggle source

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
urandom(length=8) click to toggle source

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