Welcome, guest | Sign In | My Account | Store | Cart

This recipe generates a password of any length using the standard library modules of Python. The password generated is a function of an identifier and a master password.

I liked the idea behind the PasswordMaker browser plugin (http://passwordmaker.org/). But I wanted to have a portable command line version which I wanted to carry around without having the need to install an application or needing a web browser. Additionally I wanted to use only the pre-installed modules. I haven't looked into the PasswordMaker's algorithms but I have borrowed the main idea that you have a single master password. To that you add a site address or your account name and take a cryptographic hash (MD5, SHA-1, etc.) of this string. Your password will be a function of this generated hash. I have broken each of the above steps into separate functions so that one can replace the actual implementation with their own algorithms. Note on Security: I am not a security expert; so I am not qualified to comment on how secure this approach is. I welcome others' comments on the vulnerabilities.

Python, 116 lines
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 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
# Author: Hemanth Sethuram
# Email: hemanthps@gmail.com
# Date: 16 Sep 2005
# Modifications: 
# 27 Jul 2006
#    1. Changed ALPHABETS set. This follows the keyboard layout and their shifted versions.
#    2. Added CONST_STRING
#    3. Added a new password generation function for GeneratePassword() and
#    deprecated the random number based password generation.
#    4. Passphrase is now Identifier + Master Password + CONST_STRING
# 12 Nov 2006
#    1. Added a new Password generation method using base64 encoding. Just use the string
#       representation of the hash and encode it as base64. This becomes the password with
#      alphanumeric characters.
#    2. Renamed the old password generation function to GeneratePasswordWithCharSet()

import getpass, random, sha, string

#The character set used in the password
#!!!CAUTION!!!:
#Do not change this string. Else, you may not get back the same password again

# In every row from top to bottom, move from left to right
# Then in the same order all shifted characters are taken
ALPHABETS = r'''`1234567890-=qwertyuiop[]\asdfghjkl;'zxcvbnm,./~!@#$%^&*()_+QWERTYUIOP{}|ASDFGHJKL:"ZXCVBNM<>?'''

# if you want passwords containing only alphanumeric strings
ALPHABETS_ALPHANUM = r'''1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM'''

ALPHABETS_LIMITED = r'''1234567890qwertyuiopasdfghjklzxcvbnm!@#$%^&<>?QWERTYUIOPASDFGHJKLZXCVBNM'''

CONST_STRING = r"""This is an arbitrary string present just to add some bytes to
the string that will be used to generate the hash. This constant string is added
to the identifier and the master password and fed to the hash function."""

def GetMasterPassword(prompt="Enter Master Password:"):
    """This is the only password that you need to remember. All other passwords
    are generated for you using your master password + the unique identifier for
    an account. This id can be public and need not be a secret."""
    s = getpass.getpass(prompt)
    return s
    
def GetIdentifier(prompt="Enter the Identifier:"):
    """This function gets the unique identifier you want to use for the account.
    e.g. myname@yahoo.com or login.yahoo.com, etc. This id can be public and need 
    not be a secret. You can typically use the site's webpage address or your
    account name as your Id."""
    s = raw_input(prompt)
    return s
    
def GetPasswordLength(prompt="Enter number of letters in the password:"):
    while 1:
        try:
            s = raw_input(prompt)
            n = string.atoi(s)
            if (n > 40):
                print "Only a maximum of 40 characters is allowed"
                continue
            else:
                return n
        except:
            print "Only digits allowed"
            continue

# as mentioned by Walker Hale, future implementation of Random may not generate
# the same number with the same seed. Not safe to use this method.
def GeneratePassword_Deprecated(charset, passString, passLength):
    """This function creates a pseudo random number generator object, seeded with
    the cryptographic hash of the passString. The contents of the character set
    is then shuffled and a selection of passLength words is made from this list.
    This selection is returned as the generated password."""
    l = list(charset)
    s = sha.new(passString)
    r = random.Random(long(s.hexdigest(),16))  
    #r.shuffle(l)
    return "".join(r.sample(l,passLength))

def GeneratePasswordWithCharSet(charset, passString, passLength):
    """This function creates a SHA-1 hash from the passString. The 40 nibbles of
    this hash are used as indexes into the charset from where the characters are
    picked. This is again shuffled by repeating the above process on this subset.
    Finally the required number of characters are returned as the generated
    password"""
    assert passLength <= 40   # because we want to use sha-1 (160 bits)
    charlen = len(charset)
    c1 = []
    n = 0
    s = sha.sha(passString).hexdigest() # this gives a 40 nibble string (160 bits)
    for nibble in s:
        n = (n + string.atoi(nibble,16)) % charlen
        c1.append(charset[n])  # this will finally generate a 40 character list
        
    # Repeat the above loop to scramble this set again
    n = 0
    c2 = []
    for nibble in s:
        n = (n + string.atoi(nibble,16)) % 40   # for 40 nibbles
        c2.append(c1[n])
    
    # Now truncate this character list to the required length and return
    return "".join(c2[-passLength:])
    
def GeneratePasswordBase64(passString, passLength):
    """This function creates a SHA-1 hash from the passString. The 40 nibbles of
    this hash are expressed in base64 format and the first passLength characters
    are returned as the generated password"""
    assert passLength <= 160/6  #because each base64 character is derived from 6 bits
    import base64
    return base64.b64encode(sha.sha(passString).hexdigest())[:passLength]

    
if __name__ == "__main__":
    i = GetIdentifier()
    m = GetMasterPassword()
    n = GetPasswordLength()
    print "Your password is:", GeneratePasswordBase64(i+m+CONST_STRING,n)

Added a function GeneratePasswordBase64()to use the standard library module base64 for generating printable characters for the password.

12 comments

Josiah Carlson 16 years, 2 months ago  # | flag

A few comments:

  1. Shuffling the set of vaild characters before using random.sample() is unnecessary.

  2. While hashing is generally useful for reasonably sized data sets, hashing a short string as initialization to MT would generally be quite ineffectual at actually seeding the random number generator.

  3. Most people would use as an identifier either their login name or the domain of the url, etc. Sometimes sites change urls, sometimes users can't use the same login everyhwere, ...

  4. Your module is stateless. That is, the only thing you need to carry around is the module itself; no database of passwords necessary. However, it does not solve the general problem of "I've already got YY usernames and passwords that need to be stored, and it would be nice to know where I have logins". For that, there are many programs which also offer random password generation (see KeePass Password Safe for an example which does password generation right).

hemanth sethuram (author) 16 years, 2 months ago  # | flag

On the need to store the logins in a database. The problem with programs like Keepass, PINs, etc., is that strangely they are always written for a single platform (either Windows or Linux) and not portable. For me the easiest way to store account information is in a plain text file with columns indicating Account Name, URL The problem with programs like Keepass, PINs, etc., is that strangely they are always written for a single platform (either Windows or Linux) and not portable. For me the easiest way to store account information is in a plain text file with columns indicating Account Name, URL

Josiah Carlson 16 years, 1 month ago  # | flag

Regardless of platform, your recipe still has the aforementioned problems. Find a decent encryption algorithm that is doable in Python, and write a simple interface to an encrypted password store. Then you can move around your (encrypted) list of sites with logins along with your simple Python interface, and solve all four problems.

Walker Hale 15 years, 4 months ago  # | flag

Warning: depends on specific implementation of random module. Upgrading your version of Python in the future may cause you to loose all your passwords until you go back to the previous version.

This code assumes that you will get the same sample for any given seed. While this is true for any particular pseudo-random implementation, you are likely to get a different sequence if the implementation ever changes. The implementation of the random module has changed in the past. It may very well do so again. When this happens, the only way to recover your passwords is to go back to the previous version of the Python library.

hemanth sethuram (author) 15 years, 4 months ago  # | flag

Removed dependency on Random() implementation. Thanks to Walker Hale for pointing out the dependence on Random. I have now written another routine to generate password by using the bits in the SHA-1 as index into the alphabet set.

hemanth sethuram (author) 15 years, 4 months ago  # | flag

New hashlib from 2.5. From Python2.5 a new hashlib is added to the standard library. Versions are also available for Python 2.4 and 2.3. More hash algorithms are available in this now.

import hashlib
s = hashlib.sha254()
s.update("String to hash")
print s.hexdigest()
hemanth sethuram (author) 15 years, 4 months ago  # | flag

New hashlib from 2.5. From Python2.5 a new hashlib is added to the standard library. Versions are also available for Python 2.4 and 2.3. More hash algorithms are available in this now.

import hashlib
s = hashlib.sha256()
s.update("String to hash")
print s.hexdigest()
hemanth sethuram (author) 15 years, 4 months ago  # | flag

New hashlib from 2.5. From Python2.5 a new hashlib is added to the standard library. Versions are also available for Python 2.4 and 2.3. More hash algorithms are available in this now.

import hashlib
s = hashlib.sha256()
s.update("String to hash")
print s.hexdigest()
hemanth sethuram (author) 15 years, 4 months ago  # | flag

New hashlib from 2.5. From Python2.5 a new hashlib is added to the standard library. Versions are also available for Python 2.4 and 2.3. More hash algorithms are available in this now.

import hashlib
s = hashlib.sha256()
s.update("String to hash")
print s.hexdigest()
hemanth sethuram (author) 15 years, 4 months ago  # | flag

New hashlib from 2.5. From Python2.5 a new hashlib is added to the standard library. Versions are also available for Python 2.4 and 2.3. More hash algorithms are available in this now.

import hashlib
s = hashlib.sha256()
s.update("String to hash")
print s.hexdigest()
hemanth sethuram (author) 15 years, 4 months ago  # | flag

New hashlib from 2.5. From Python2.5 a new hashlib is added to the standard library. Versions are also available for Python 2.4 and 2.3. More hash algorithms are available in this now.

import hashlib
s = hashlib.sha256()
s.update("String to hash")
print s.hexdigest()
a 11 years, 7 months ago  # | flag

string.atoi() is deprecated. you should use int() instead