0
|
1 #!/usr/bin/env python3 |
|
2 """Create a passphrase from a few random words. DRAFT |
|
3 |
|
4 Relies on the entropy of python's |
|
5 random.SystemRandom class |
|
6 which (according to the documentation) calls os.urandom() |
|
7 which (according to the documentation) calls the operating system |
|
8 specific randomness source which "should be unpredictable |
|
9 enough for cryptographic applications" |
|
10 |
|
11 Requires: |
|
12 * Python v>=3.2 |
|
13 * a dictionary, Ding's trans-de-en by default. |
|
14 E.g. on a Debian/Ubuntu system in package "trans-de-en". |
|
15 or from http://ftp.tu-chemnitz.de/pub/Local/urz/ding/de-en/ |
|
16 |
|
17 Uses a hardcodes filepath and selected language. |
|
18 Search for **customize** below to change it. |
|
19 |
|
20 Copyright 2016 by Intevation GmbH. |
|
21 Author: 2016-01-21 Bernhard E. Reiter <bernhard@intevation.de> |
|
22 |
|
23 This file is Free Software under the Apache 2.0 license and thus |
|
24 comes without any warranty (to extend permissible under applicable law). |
|
25 """ |
|
26 |
|
27 import math |
|
28 import re |
|
29 import sys |
|
30 |
|
31 from random import SystemRandom |
|
32 _srandom = SystemRandom() |
|
33 |
|
34 tainted = False # to be set if we find a hint that the passphrase may be weak |
|
35 |
|
36 def buildDictionary(): |
|
37 """Build up a dictionary of unique words, calculate stats.""" |
|
38 global tainted |
|
39 d = [] |
|
40 |
|
41 # dictionary for testing |
|
42 #d = ["abc", "aBc", "cde", "efg", "hij", "blubber", "jikf", "zug", "lmf", "opq"] |
|
43 |
|
44 # Using the dictionary from Ding **customize** |
|
45 d = readDingDict(filename="/usr/share/trans/de-en", useLeft=True) |
|
46 |
|
47 ## for debugging purpuses, dump dictionary |
|
48 #dumpfilename = "ddump.txt" |
|
49 #print("Writing out {}.".format(dumpfilename)) |
|
50 #with open(dumpfilename, "w") as f: |
|
51 # for i in d: |
|
52 # f.write("{}\n".format(i)) |
|
53 |
|
54 # Print some stats on the dictionary to be used |
|
55 dl = len(d) |
|
56 print("Found {:d} dictionary entries".format(dl)) |
|
57 if dl < 2000: |
|
58 print("!Your dictionary is below 2k entries, that is quite small!") |
|
59 tainted = True |
|
60 |
|
61 print("|= Number of words |= possibilities |") |
|
62 for i in range(1,5): |
|
63 print("| {:2d} | 2^{:4.1f} |".format( |
|
64 i, math.log(dl**i,2))) |
|
65 return d |
|
66 |
|
67 |
|
68 def readDingDict(filename = "/usr/share/trans/de-en", useLeft=False): |
|
69 """Read dictionary with unique words from file in Ding format. |
|
70 |
|
71 useLeft: Boolean to control which language to use |
|
72 |
|
73 TODO: add option to use both languages for people that speak them both? |
|
74 """ |
|
75 |
|
76 dset = set() #using the datatype 'set' to aviod duplicates |
|
77 |
|
78 splitter = re.compile(r"""\ \|\ # first pattern ' | ' |
|
79 |;\ # second pattern '; ' |
|
80 |(?<=\S)/(?=\S) # 3.: '\' surrounded by chars |
|
81 |\s+ # by whitespace |
|
82 """,re.VERBOSE) |
|
83 |
|
84 print("Reading entries from {}.".format(filename), end='') |
|
85 counter = 0 # for progress or stopping early |
|
86 with open(filename, "r") as f: |
|
87 for line in f: |
|
88 if line[0] == '#': continue |
|
89 |
|
90 # languages are separated by " :: " |
|
91 p = line.partition(" :: ") |
|
92 languageEntry = p[0] if useLeft else p[2] |
|
93 |
|
94 for word in splitter.split(languageEntry): |
|
95 word = word.strip('(",.)\'!:;').rstrip('/') |
|
96 if len(word) > 2 and not word[0] in '[{/': |
|
97 dset.add(word) |
|
98 |
|
99 #TODO: check for very common words and remove them? |
|
100 |
|
101 counter += 1 |
|
102 ## stop early when debugging |
|
103 #if counter > 10: break |
|
104 if not counter % 10000: |
|
105 print('.', end='') |
|
106 sys.stdout.flush() |
|
107 print() |
|
108 |
|
109 return list(dset) |
|
110 |
|
111 def main(): |
|
112 global tainted |
|
113 dictionary = buildDictionary() |
|
114 |
|
115 howMany = 4 |
|
116 |
|
117 # use a dictionary with lower case words for a simple check if |
|
118 # our random source is okay |
|
119 print("\nGenerated passphrase with {} randomly selected words:\n".format( |
|
120 howMany)) |
|
121 print(" ", end='') |
|
122 words = {} |
|
123 for x in range(howMany): |
|
124 word = _srandom.choice(dictionary) |
|
125 words[word.lower]= True |
|
126 print(word, end='\n ') |
|
127 print("\n") |
|
128 |
|
129 if len(words) < howMany: |
|
130 print("! Your random generator is weak") |
|
131 print("! or you are being very lucky.") |
|
132 tainted = True |
|
133 |
|
134 if tainted: |
|
135 print("!!! Don't use the resulting passphrase !!!") |
|
136 |
|
137 if __name__ == "__main__": |
|
138 main() |