#!/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 = '''

Number of passwords to generate (upto 20): 

Security level:
  Hack Me (non-altered words from the dictionary)
  Easy (randomly generated, hopefully pronounceable words)
  Medium (mutated dictionary words)
  Hard (mutated generated words)
  Insane (random sequence of letters, numerals and other characters)

Strength:
  Long
  Short

\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 = '''

Number of passwords to generate (upto 20): 

Security level:
  Hack Me (non-altered words from the dictionary)
  Easy (randomly generated, hopefully pronounceable words)
  Medium (mutated dictionary words)
  Hard (mutated generated words)
  Insane (random sequence of letters, numerals and other characters)

Strength:
  Long
  Short

''' % 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)