#!/usr/bin/python
#
# PyKey (http://www.sajjadzaidi.com/pykey/)
# Password generator
#
# Written by A. Sajjad Zaidi (http://www.sajjadzaidi.com/)
#
# Copyright 2004
#
from random import choice
import sys
import os
import string
#######################################
# Basic Control Functions #
#######################################
def cgiProcess():
'''Gets input from a html form and processes the options'''
global DICTOPEN
import cgi
cgiLocation = '/pykey/pykey.py'
form = cgi.FieldStorage()
if form.has_key('generate'):
pCount = int(form['count'].value)
noOfPasswds = pCount
pLevel = form['level'].value
tooEasyLevel = ''
easyLevel = ''
midLevel = ''
hardLevel = ''
hardestLevel = ''
pStrength = form['strength'].value
longStrength = ''
shortStrength = ''
if pStrength == 'long':
longStrength = 'checked'
elif pStrength == 'short':
shortStrength = 'checked'
else:
longStrength = 'checked'
pStrength = 'long'
if noOfPasswds < 21:
if DICTOPEN == 'no' and pLevel == 'medium':
pLevel = 'hard'
if DICTOPEN == 'no' and pLevel == 'veasy':
pLevel = 'easy'
if pLevel == 'hardest':
passList = '
'
while noOfPasswds > 0:
newPasswd = makeHardest(pStrength)
# Generate new passwords until a good one is found
while (checkPasswd(newPasswd) == 'bad'):
newPasswd = makeHardest(pStrength)
passList = '%s\n%s
' % (passList, newPasswd)
noOfPasswds = noOfPasswds - 1
passList = '%s
' % passList
hardestLevel = 'checked '
elif pLevel == 'hard':
passList = ''
while noOfPasswds > 0:
newPasswd = makeHard(pStrength)
# Generate new passwords until a good one is found
while (checkPasswd(newPasswd) == 'bad'):
newPasswd = makeHard(pStrength)
passList = '%s\n%s
' % (passList, newPasswd)
noOfPasswds = noOfPasswds - 1
passList = '%s
' % passList
hardLevel = 'checked '
elif pLevel == 'medium':
passList = ''
while noOfPasswds > 0:
newPasswd = makeMedium(pStrength)
# Generate new passwords until a good one is found
while (checkPasswd(newPasswd) == 'bad'):
newPasswd = makeMedium(pStrength)
passList = '%s\n%s
' % (passList, newPasswd)
noOfPasswds = noOfPasswds - 1
passList = '%s
' % passList
midLevel = 'checked '
elif pLevel == 'easy':
passList = ''
while noOfPasswds > 0:
newPasswd = makeEasy(pStrength)
passList = '%s\n%s
' % (passList, newPasswd)
noOfPasswds = noOfPasswds - 1
passList = '%s
' % passList
easyLevel = 'checked '
else:
passList = ''
while noOfPasswds > 0:
newPasswd = makeEasiest(pStrength)
passList = '%s\n%s
' % (passList, newPasswd)
noOfPasswds = noOfPasswds - 1
passList = '%s
' % passList
tooEasyLevel = 'checked '
else:
passList = 'Nice try sonny
'
midLevel = 'checked '
outputText = '''
\n
\n(Please note that unless run on the same system or
over SSL, these passwords are being transmitted in plain text and
could be intercepted)\n
\n%s''' % (cgiLocation, pCount, tooEasyLevel, easyLevel, midLevel, hardLevel, hardestLevel, longStrength, shortStrength, passList)
else:
outputText = '''
''' % cgiLocation
printWebPage(outputText)
sys.exit(0)
return()
def cliProcess(pLevel, pStrength, noOfPasswords):
global DICTOPEN
if DICTOPEN == 'no' and pLevel == 'medium':
pLevel = 'hard'
if DICTOPEN == 'no' and pLevel == 'veasy':
pLevel = 'easy'
if pLevel == 'hardest':
while noOfPasswords > 0:
newPasswd = makeHardest(pStrength)
# Generate new passwords until a good one is found
while (checkPasswd(newPasswd) == 'bad'):
newPasswd = makeHardest(pStrength)
print newPasswd
noOfPasswords = noOfPasswords - 1
elif pLevel == 'hard':
while noOfPasswords > 0:
newPasswd = makeHard(pStrength)
# Generate new passwords until a good one is found
while (checkPasswd(newPasswd) == 'bad'):
newPasswd = makeHard(pStrength)
print newPasswd
noOfPasswords = noOfPasswords - 1
elif pLevel == 'medium':
while noOfPasswords > 0:
newPasswd = makeMedium(pStrength)
# Generate new passwords until a good one is found
while (checkPasswd(newPasswd) == 'bad'):
newPasswd = makeMedium(pStrength)
print newPasswd
noOfPasswords = noOfPasswords - 1
elif pLevel == 'easy':
while noOfPasswords > 0:
newPasswd = makeEasy(pStrength)
print newPasswd
noOfPasswords = noOfPasswords - 1
else:
while noOfPasswords > 0:
newPasswd = makeEasiest(pStrength)
print newPasswd
noOfPasswords = noOfPasswords - 1
return()
#######################################
# Difficulty level functions #
#######################################
def makeHardest(pStrength):
'''Pick a random length and random characters to generate a password'''
characters = ('a','b','c','d','e','f','g','h','i','j','k','l','m',
'n','o','p','q','r','s','t','u','v','w','x','y','z',
'A','B','C','D','E','F','G','H','I','J','K','L','M',
'N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
'0','1','2','3','4','5','6','7','8','9',
'!','@','#','$','%','^','&','*','=','?','+','-','_')
if pStrength == 'long':
pLength = choice(range(8,14))
else:
pLength = choice(range(4,6))
randomPass = ''
a = 0
while a < pLength:
randomPass = '%s%s' %(randomPass, choice(characters))
a = a + 1
return(randomPass)
def makeHard(pStrength):
'''Generate new words, modify them and then mash together'''
if pStrength == 'long':
lenRange = range(4,7)
else:
lenRange = range(2,3)
wDivider = (' ', '_', '-','.')
noOfWords = choice((1,2,3))
if noOfWords == 1:
resultPasswd = mutateWord(makeWord(choice(lenRange)))
elif noOfWords == 2:
randWord = mutateWord(makeWord(choice(lenRange)))
randWord2 = mutateWord(makeWord(choice(lenRange)))
resultPasswd = '%s%s%s' % (randWord, choice(wDivider), randWord2)
else:
randWord = mutateWord(makeWord(choice(lenRange)))
randWord2 = mutateWord(makeWord(choice(lenRange)))
randWord3 = mutateWord(makeWord(choice(lenRange)))
resultPasswd = '%s%s%s%s%s' % (randWord, choice(wDivider), randWord2, choice(wDivider), randWord3)
return(resultPasswd)
def makeMedium(pStrength):
'''Get dictionary words, modify them and then mash together'''
wDivider = (' ', '_', '-','.')
noOfWords = choice((1,2,3))
if noOfWords == 1:
resultPasswd = mutateWord(getWord())
elif noOfWords == 2:
randWord = mutateWord(getWord())
randWord2 = mutateWord(getWord())
resultPasswd = '%s%s%s' % (randWord, choice(wDivider), randWord2)
else:
randWord = mutateWord(getWord())
randWord2 = mutateWord(getWord())
randWord3 = mutateWord(getWord())
resultPasswd = '%s%s%s%s%s' % (randWord, choice(wDivider), randWord2, choice(wDivider), randWord3)
return(resultPasswd)
def makeEasy(pStrength):
'''Create new words, then mash them together'''
if pStrength == 'long':
lenRange = range(4,7)
else:
lenRange = range(2,3)
wDivider = (' ', '_', '-','.')
noOfWords = choice((1,2,3))
if noOfWords == 1:
resultPasswd = makeWord(choice(lenRange))
elif noOfWords == 2:
randWord = makeWord(choice(lenRange))
randWord2 = makeWord(choice(lenRange))
resultPasswd = '%s%s%s' % (randWord, choice(wDivider), randWord2)
else:
randWord = makeWord(choice(lenRange))
randWord2 = makeWord(choice(lenRange))
randWord3 = makeWord(choice(lenRange))
resultPasswd = '%s%s%s%s%s' % (randWord, choice(wDivider), randWord2, choice(wDivider), randWord3)
return(resultPasswd)
def makeEasiest(pStrength):
'''Get words and just join them together with other words without
modifying them'''
wDivider = (' ', '_', '.')
noOfWords = choice((1,2))
if noOfWords == 1:
resultPasswd = getWord()
else:
randWord = getWord()
randWord2 = getWord()
resultPasswd = '%s%s%s' % (randWord, choice(wDivider), randWord2)
return(resultPasswd)
#######################################
# Other useful functions #
#######################################
def parseCLI():
"""If command line arguments are given, parse them and return the
results. This seems to work, but all the required options must
be given"""
from getopt import getopt
import re
allCmdArgs = sys.argv
cmdUsage = 'Usage: %s [OPTIONS]\n\
Command line blog entries.\n\
-h, --help display this help and exit\n\
-l, --level=[veasy|easy|medium|hard|hardest] Set the difficulty level of the passwords\n\
-s, --strength=[long|short] Set the range of password length\n\
-n, --number=NUMBER Choose the number of passwords to generate' % sys.argv[0]
allCmdArgs.pop(0)
shortCmdOpts = "hl:s:n:"
longCmdOpts = ['help','level=','strength=','number=']
try:
pLevel = 'medium'
pStrength = 'long'
pCount = 1
opts, args = getopt(allCmdArgs,shortCmdOpts,longCmdOpts)
for opt, val in opts:
if opt == "-h" or opt == "--help":
sys.exit(1)
if opt == "-l" or opt == "--level":
pLevel = val
if opt == "-s" or opt == "--strength":
pStrength = val
if opt == "-n" or opt == "--number":
pCount = int(val)
except:
print cmdUsage
sys.exit(1)
return(pLevel, pStrength, pCount)
def printWebPage(outputData):
'''Outputs the results'''
print 'Content-type: text/html\n\n'
print 'PyKey Passphrase Generator'
print outputData
print ''
return()
def getWord():
'''Choose a random word from the dictionary that is at least 6 characters
and no more than 10'''
global DICTWORDS
# # Dictionary file
# dictFile = '/usr/share/dict/words'
#
# # Read it into memory. I know, waste of resources, but I thought
# # it was better than opening and closing the file for every single
# # word
# try:
# d = open(dictFile)
# DICTWORDS = d.readlines()
# d.close()
# except:
# print 'Error opening dictionary file. Exiting.'
# sys.exit(1)
goodWord = choice(DICTWORDS)
# A check to keep the word from containing problematic characters, being
# too short or too long.
searchResult = string.find(goodWord,'\'') + string.find(goodWord,'\"')
if (searchResult != -2) | (len(goodWord) < 6) | (len(goodWord) > 10):
goodWord = getWord()
goodWord = string.strip(goodWord)
return(goodWord)
def makeWord(pLength):
'''Try to generate a pronounceable word'''
conSounds = ('b','c','d','f','g','h','j','k','l','m',
'n','p','q','r','s','t','v','w','x','y','z',
'bl','br','cl','cr','ch','dr','fl','fr','gh',
'gl','gr','gw','kl','kr','ph','pl','pr','sc',
'sh','sl','sn','sp','st','th','tr','wh','wr',
'B','C','D','F','G','H','J','K','L','M',
'N','P','Q','R','S','T','V','W','X','Y','Z',
'Bl','Br','Cl','Cr','Ch','Dr','Fl','Fr','Gh',
'Gl','Gr','Gw','Kl','Kr','Ph','Pl','Pr','Sc',
'Sh','Sl','Sn','Sp','St','Th','Tr','Wh','Wr')
vowelies = ('a','e','i','o','u','y')
resultPasswd = ''
a = 0
while a < pLength:
resultPasswd = '%s%s%s' %(resultPasswd, choice(conSounds),choice(vowelies))
a = a + 1
return(resultPasswd)
def mutateWord(sheepWord):
'''Use different ways to modify random characters of the word'''
countDigits = range(21)
#countDigits = (0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20)
#tortureMethods = ('changeCase','insertDigits','replaceChars')
#Inserting digits makes the passwords hard to memorize. Must fix.
tortureMethods = ('changeCase','replaceChars')
finalWord = sheepWord
a = choice(countDigits)
while a >= 0:
currentMethod = choice(tortureMethods)
if currentMethod == 'changeCase':
finalWord = changeCase(finalWord)
elif currentMethod == 'insertDigits':
finalWord = insertDigits(finalWord)
else:
finalWord = replaceChars(finalWord)
a = a-1
return(finalWord)
def replaceChars(rawWord):
'''Substitutes various characters with similar characters'''
# List of possible replacements for characters
someChanges = (
('a','@'), ('A','@'), ('a','6'), ('A','6'),
('@','a'), ('@','A'), ('6','a'), ('6','A'),
('B','8'), ('8','B'),
('c','('), ('C','('), ('(','c'), ('(','C'),
('e','3'), ('E','3'), ('3','e'), ('3','E'),
('h','#'), ('H','#'), ('#','h'), ('#','H'),
('i','1'), ('I','1'), ('i','!'), ('I','!'),
('1','i'), ('1','I'), ('!','i'), ('!','I'),
('l','1'), ('L','1'), ('l','!'), ('L','!'), ('L','7'),
('1','l'), ('1','L'), ('!','l'), ('!','L'), ('7','L'),
('o','0'), ('O','0'), ('0','o'), ('0','O'),
('s','$'), ('S','$'), ('s','5'), ('S','5'),
('$','s'), ('$','S'), ('$','5'), ('$','5'),
('t','+'), ('T','+'), ('+','t'), ('+','T'),
('x','%'), ('X','%'), ('%','x'), ('%','X'),
('z','2'), ('Z','2'), ('2','z'), ('2','Z')
)
randChange = choice(someChanges)
cookedWord = string.replace(rawWord,randChange[0],randChange[1],1)
return(cookedWord)
def changeCase(sheepWord):
'''Swap the case for a random letter in a word'''
charList = []
a = 0
for c in sheepWord:
charList.append(a)
a = a+1
itemNo = choice(charList)
changedWord = '%s%s%s' % (sheepWord[:itemNo], string.swapcase(sheepWord[itemNo]), sheepWord[itemNo+1:])
return(changedWord)
def insertDigits(sheepWord):
'''Insert random digits before or after the password'''
digitsToInsert = (range(0,10))
noOfDigits = (0,1,2,0)
digitPosition = ('b','a')
chosenDigits = ''
a = choice(noOfDigits)
while a > 0:
chosenDigits = '%s%s' %(chosenDigits, choice(digitsToInsert))
a = a-1
pos = choice(digitPosition)
if pos == 'a':
changedWord = '%s%s' % (sheepWord, chosenDigits)
else:
changedWord = '%s%s' % (chosenDigits, sheepWord)
return(changedWord)
def checkPasswd(freshPasswd):
'''Check to see if the password has at least one lowercase, one uppercase
one digit and one meta character'''
# Using regex like this was the best way I could come up with though
# I'm sure it could be done in a better way
import re
mPat = re.compile(r'[!@#$%^&*()+=]')
nPat = re.compile(r'[0-9]')
lPat = re.compile(r'[a-z]')
uPat = re.compile(r'[A-Z]')
mMatch = mPat.search(freshPasswd)
nMatch = nPat.search(freshPasswd)
lMatch = lPat.search(freshPasswd)
uMatch = uPat.search(freshPasswd)
if mMatch and nMatch and lMatch and uMatch:
result = 'good'
else:
result = 'bad'
return(result)
###########################################
## Start ##
###########################################
# Dictionary file
dictFile = '/usr/share/dict/words'
# Read it into memory. I know, waste of resources, but I thought
# it was better than opening and closing the file for every single
# word
try:
d = open(dictFile)
DICTWORDS = d.readlines()
d.close()
DICTOPEN = 'yes'
except:
DICTOPEN = 'no'
pLevel = 'medium'
# Decide on the method
if os.environ.has_key("GATEWAY_INTERFACE"):
cgiProcess()
elif len(sys.argv) > 1:
#noOfPasswords = int(sys.argv[1])
pLevel, pStrength, noOfPasswords = parseCLI()
cliProcess(pLevel, pStrength, noOfPasswords)
else:
cliProcess('medium', 'short', 1)
sys.exit(0)