# HG changeset patch # User Bernhard Reiter # Date 1455892126 -3600 # Node ID 7558ecd1cbf1f947930917e7dccc9f89186fd955 Initial version. diff -r 000000000000 -r 7558ecd1cbf1 ppgen.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ppgen.py Fri Feb 19 15:28:46 2016 +0100 @@ -0,0 +1,138 @@ +#!/usr/bin/env python3 +"""Create a passphrase from a few random words. DRAFT + +Relies on the entropy of python's + random.SystemRandom class + which (according to the documentation) calls os.urandom() + which (according to the documentation) calls the operating system + specific randomness source which "should be unpredictable + enough for cryptographic applications" + +Requires: + * Python v>=3.2 + * a dictionary, Ding's trans-de-en by default. + E.g. on a Debian/Ubuntu system in package "trans-de-en". + or from http://ftp.tu-chemnitz.de/pub/Local/urz/ding/de-en/ + +Uses a hardcodes filepath and selected language. +Search for **customize** below to change it. + +Copyright 2016 by Intevation GmbH. +Author: 2016-01-21 Bernhard E. Reiter + +This file is Free Software under the Apache 2.0 license and thus +comes without any warranty (to extend permissible under applicable law). +""" + +import math +import re +import sys + +from random import SystemRandom +_srandom = SystemRandom() + +tainted = False # to be set if we find a hint that the passphrase may be weak + +def buildDictionary(): + """Build up a dictionary of unique words, calculate stats.""" + global tainted + d = [] + + # dictionary for testing + #d = ["abc", "aBc", "cde", "efg", "hij", "blubber", "jikf", "zug", "lmf", "opq"] + + # Using the dictionary from Ding **customize** + d = readDingDict(filename="/usr/share/trans/de-en", useLeft=True) + + ## for debugging purpuses, dump dictionary + #dumpfilename = "ddump.txt" + #print("Writing out {}.".format(dumpfilename)) + #with open(dumpfilename, "w") as f: + # for i in d: + # f.write("{}\n".format(i)) + + # Print some stats on the dictionary to be used + dl = len(d) + print("Found {:d} dictionary entries".format(dl)) + if dl < 2000: + print("!Your dictionary is below 2k entries, that is quite small!") + tainted = True + + print("|= Number of words |= possibilities |") + for i in range(1,5): + print("| {:2d} | 2^{:4.1f} |".format( + i, math.log(dl**i,2))) + return d + + +def readDingDict(filename = "/usr/share/trans/de-en", useLeft=False): + """Read dictionary with unique words from file in Ding format. + + useLeft: Boolean to control which language to use + + TODO: add option to use both languages for people that speak them both? + """ + + dset = set() #using the datatype 'set' to aviod duplicates + + splitter = re.compile(r"""\ \|\ # first pattern ' | ' + |;\ # second pattern '; ' + |(?<=\S)/(?=\S) # 3.: '\' surrounded by chars + |\s+ # by whitespace + """,re.VERBOSE) + + print("Reading entries from {}.".format(filename), end='') + counter = 0 # for progress or stopping early + with open(filename, "r") as f: + for line in f: + if line[0] == '#': continue + + # languages are separated by " :: " + p = line.partition(" :: ") + languageEntry = p[0] if useLeft else p[2] + + for word in splitter.split(languageEntry): + word = word.strip('(",.)\'!:;').rstrip('/') + if len(word) > 2 and not word[0] in '[{/': + dset.add(word) + + #TODO: check for very common words and remove them? + + counter += 1 + ## stop early when debugging + #if counter > 10: break + if not counter % 10000: + print('.', end='') + sys.stdout.flush() + print() + + return list(dset) + +def main(): + global tainted + dictionary = buildDictionary() + + howMany = 4 + + # use a dictionary with lower case words for a simple check if + # our random source is okay + print("\nGenerated passphrase with {} randomly selected words:\n".format( + howMany)) + print(" ", end='') + words = {} + for x in range(howMany): + word = _srandom.choice(dictionary) + words[word.lower]= True + print(word, end='\n ') + print("\n") + + if len(words) < howMany: + print("! Your random generator is weak") + print("! or you are being very lucky.") + tainted = True + + if tainted: + print("!!! Don't use the resulting passphrase !!!") + +if __name__ == "__main__": + main()