Mercurial > ppgen
comparison ppgen.py @ 4:85c65a597420
Improves: command line options and code style.
Adds a command line option for dumping the dictionary into a file.
Improves source code style to better conform to pep8.
Calls it BETA now.
author | Bernhard Reiter <bernhard@intevation.de> |
---|---|
date | Thu, 06 Oct 2016 17:28:46 +0200 |
parents | 757625ec8364 |
children | f8e24b2b6b6a |
comparison
equal
deleted
inserted
replaced
3:757625ec8364 | 4:85c65a597420 |
---|---|
1 #!/usr/bin/env python3 | 1 #!/usr/bin/env python3 |
2 """Create a passphrase from a few random words. DRAFT | 2 """Create a random passphrase from a dictionary of words. BETA |
3 | 3 |
4 Relies on the entropy of python's | 4 Relies on the entropy of python's |
5 random.SystemRandom class | 5 random.SystemRandom class |
6 which (according to the documentation) calls os.urandom() | 6 which (according to the documentation) calls os.urandom() |
7 which (according to the documentation) calls the operating system | 7 which (according to the documentation) calls the operating system |
8 specific randomness source which "should be unpredictable | 8 specific randomness source which "should be unpredictable |
9 enough for cryptographic applications" | 9 enough for cryptographic applications" |
10 | 10 |
11 Requires: | 11 Requires: |
12 * Python v>=3.2 | 12 * Python v>=3.2 |
13 * a dictionary, Ding's trans-de-en by default. | 13 * a dictionary, Ding's trans-de-en by default. |
26 | 26 |
27 This file is Free Software under the Apache 2.0 license and thus | 27 This file is Free Software under the Apache 2.0 license and thus |
28 comes without any warranty (to extend permissible under applicable law). | 28 comes without any warranty (to extend permissible under applicable law). |
29 """ | 29 """ |
30 | 30 |
31 import argparse | |
31 import math | 32 import math |
32 import re | 33 import re |
33 import sys | 34 import sys |
34 | 35 |
35 from random import SystemRandom | 36 from random import SystemRandom |
36 _srandom = SystemRandom() | 37 _srandom = SystemRandom() |
37 | 38 |
38 tainted = False # to be set if we find a hint that the passphrase may be weak | 39 tainted = False # to be set if we find a hint that the passphrase may be weak |
39 | 40 |
40 def buildDictionary(): | 41 |
42 def buildDictionary(options): | |
41 """Build up a dictionary of unique words, calculate stats.""" | 43 """Build up a dictionary of unique words, calculate stats.""" |
42 global tainted | 44 global tainted |
43 d = [] | 45 d = [] |
44 | 46 |
45 # dictionary for testing | 47 # dictionary for testing |
46 #d = ["abc", "aBc", "cde", "efg", "hij", "blubber", "jikf", "zug", "lmf", "opq"] | 48 #d = ["abc", "aBc", "cde", "efg", "hij", "blubber", |
49 # "jikf", "zug", "lmf", "opq"] | |
47 # second test dictionary to show that different string functions are used. | 50 # second test dictionary to show that different string functions are used. |
48 #d = [''.join('A' * 1000) for _ in range(1000)] | 51 #d = [''.join('A' * 1000) for _ in range(1000)] |
49 | 52 |
50 # Using the dictionary from Ding **customize** | 53 # Using the dictionary from Ding **customize** |
51 d = readDingDict(filename="/usr/share/trans/de-en", useLeft=True) | 54 d = readDingDict(filename="/usr/share/trans/de-en", useLeft=True) |
52 | 55 |
53 ## for debugging purpuses, dump dictionary | 56 ## for debugging purpuses, dump dictionary |
54 #dumpfilename = "ddump.txt" | 57 if options.ddump_filename: |
55 #print("Writing out {}.".format(dumpfilename)) | 58 print("Writing out dictionary in '{}'.".format(options.ddump_filename)) |
56 #with open(dumpfilename, "w") as f: | 59 with open(options.ddump_filename, "w") as f: |
57 # for i in d: | 60 for i in d: |
58 # f.write("{}\n".format(i)) | 61 f.write("{}\n".format(i)) |
59 | 62 |
60 # Print some stats on the dictionary to be used | 63 # Print some stats on the dictionary to be used |
61 dl = len(d) | 64 dl = len(d) |
62 print("Found {:d} dictionary entries.".format(dl)) | 65 print("Found {:d} dictionary entries.".format(dl)) |
63 if dl < 8000: | 66 if dl < 8000: |
64 print("!Your dictionary is below 8k entries, that is quite small!") | 67 print("!Your dictionary is below 8k entries, that is quite small!") |
65 tainted = True | 68 tainted = True |
66 | 69 |
67 print("|= Number of words |= possibilities |") | 70 print("|= Number of words |= possibilities |") |
68 for i in range(1,5): | 71 for i in range(1, 5): |
69 print("| {:2d} | 2^{:4.1f} |".format( | 72 print("| {:2d} | 2^{:4.1f} |".format( |
70 i, math.log(dl**i,2))) | 73 i, math.log(dl**i, 2))) |
71 return d | 74 return d |
72 | 75 |
73 | 76 |
74 def readDingDict(filename = "/usr/share/trans/de-en", useLeft=False): | 77 def readDingDict(filename="/usr/share/trans/de-en", useLeft=False): |
75 """Read dictionary with unique words from file in Ding format. | 78 """Read dictionary with unique words from file in Ding format. |
76 | 79 |
77 useLeft: Boolean to control which language to use | 80 useLeft: Boolean to control which language to use |
78 | 81 |
79 TODO: add option to use both languages for people that speak them both? | 82 TODO: add option to use both languages for people that speak them both? |
80 """ | 83 """ |
81 | 84 |
82 dset = set() #using the datatype 'set' to aviod duplicates | 85 dset = set() # using the datatype 'set' to aviod duplicates |
83 | 86 |
84 splitter = re.compile(r"""\ \|\ # first pattern ' | ' | 87 splitter = re.compile(r"""\ \|\ # first pattern ' | ' |
85 |;\ # second pattern '; ' | 88 |;\ # second pattern '; ' |
86 |(?<=\S)/(?=\S) # 3.: '\' surrounded by chars | 89 |(?<=\S)/(?=\S) # 3.: '\' surrounded by chars |
87 |\s+ # by whitespace | 90 |\s+ # by whitespace |
88 """,re.VERBOSE) | 91 """, re.VERBOSE) |
89 | 92 |
90 print("Reading entries from {}.".format(filename), end='') | 93 print("Reading entries from {}.".format(filename), end='') |
91 counter = 0 # for progress or stopping early | 94 counter = 0 # for progress or stopping early |
92 with open(filename, "r") as f: | 95 with open(filename, "r") as f: |
93 for line in f: | 96 for line in f: |
94 if line[0] == '#': continue | 97 if line[0] == '#': |
98 continue | |
95 | 99 |
96 # languages are separated by " :: " | 100 # languages are separated by " :: " |
97 p = line.partition(" :: ") | 101 p = line.partition(" :: ") |
98 languageEntry = p[0] if useLeft else p[2] | 102 languageEntry = p[0] if useLeft else p[2] |
99 | 103 |
112 sys.stdout.flush() | 116 sys.stdout.flush() |
113 print() | 117 print() |
114 | 118 |
115 return list(dset) | 119 return list(dset) |
116 | 120 |
121 | |
117 def main(): | 122 def main(): |
118 global tainted | 123 global tainted |
119 dictionary = buildDictionary() | 124 |
125 parser = argparse.ArgumentParser(description=__doc__.splitlines()[0]) | |
126 parser.add_argument('--ddump-filename', | |
127 help='filename to dump the dictionary to') | |
128 options = parser.parse_args() | |
129 | |
130 dictionary = buildDictionary(options) | |
120 | 131 |
121 howMany = 4 | 132 howMany = 4 |
122 | 133 |
123 # use a dictionary with lower case words for a simple check if | 134 # use a dictionary with lower case words for a simple check if |
124 # our random source is okay | 135 # our random source is okay |
126 howMany)) | 137 howMany)) |
127 print(" ", end='') | 138 print(" ", end='') |
128 words = {} | 139 words = {} |
129 for x in range(howMany): | 140 for x in range(howMany): |
130 word = _srandom.choice(dictionary) | 141 word = _srandom.choice(dictionary) |
131 words[word.lower()]= True | 142 words[word.lower()] = True |
132 print(word, end='\n ') | 143 print(word, end='\n ') |
133 print("\n") | 144 print("\n") |
134 | 145 |
135 if len(words) < howMany: | 146 if len(words) < howMany: |
136 print("! Your random generator is weak") | 147 print("! Your random generator is weak") |