Mercurial > ppgen
comparison ppgen.py @ 8:200c2c3c5f67
Adds comfort.
* Adds options
** '--just-passphrase': to get a passphrase on one line for use in skripts.
** '--number-of-words': the passphrase shall consists of
* Refactors to implement the options and preparing for future developments:
** Warnings will be written to stderr and tained will bail out with sys.exit().
** The output_string is build before printing.
author | Bernhard Reiter <bernhard@intevation.de> |
---|---|
date | Thu, 18 Jan 2018 08:44:33 +0100 |
parents | 8b2f8f439817 |
children | 35c468a37b54 |
comparison
equal
deleted
inserted
replaced
7:8b2f8f439817 | 8:200c2c3c5f67 |
---|---|
19 | 19 |
20 Related: There is a Go implementation started by Sascha L. Teichmann at | 20 Related: There is a Go implementation started by Sascha L. Teichmann at |
21 https://bitbucket.org/s_l_teichmann/ppgen | 21 https://bitbucket.org/s_l_teichmann/ppgen |
22 | 22 |
23 | 23 |
24 Copyright 2016, 2017 by Intevation GmbH. | 24 Copyright 2016, 2017, 2018 by Intevation GmbH. |
25 Author: Bernhard E. Reiter <bernhard@intevation.de> | 25 Author: Bernhard E. Reiter <bernhard@intevation.de> |
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 """ |
49 # "jikf", "zug", "lmf", "opq"] | 49 # "jikf", "zug", "lmf", "opq"] |
50 # second test dictionary to show that different string functions are used. | 50 # second test dictionary to show that different string functions are used. |
51 #d = [''.join('A' * 1000) for _ in range(1000)] | 51 #d = [''.join('A' * 1000) for _ in range(1000)] |
52 | 52 |
53 # Using the dictionary from Ding **customize** | 53 # Using the dictionary from Ding **customize** |
54 d = readDingDict(filename="/usr/share/trans/de-en", useLeft=True) | 54 d = readDingDict(options, filename="/usr/share/trans/de-en", useLeft=True) |
55 | 55 |
56 ## for debugging purposes, dump dictionary | 56 # for debugging purposes, dump dictionary |
57 if options.ddump_filename: | 57 if options.ddump_filename: |
58 print("Writing out dictionary in '{}'.".format(options.ddump_filename)) | 58 print("Writing out dictionary in '{}'.".format(options.ddump_filename)) |
59 with open(options.ddump_filename, "w") as f: | 59 with open(options.ddump_filename, "w") as f: |
60 for i in d: | 60 for i in d: |
61 f.write("{}\n".format(i)) | 61 f.write("{}\n".format(i)) |
62 | 62 |
63 # Print some stats on the dictionary to be used | 63 # Print some stats on the dictionary to be used |
64 dl = len(d) | 64 dl = len(d) |
65 print("Found {:d} dictionary entries.".format(dl)) | 65 if not options.just_passphrase: |
66 print("Found {:d} dictionary entries.".format(dl)) | |
67 print("|= Number of words |= possibilities |") | |
68 for i in range(1, 5): | |
69 print("| {:2d} | 2^{:4.1f} |".format( | |
70 i, math.log(dl**i, 2))) | |
71 | |
66 if dl < 8000: | 72 if dl < 8000: |
67 print("!Your dictionary is below 8k entries, that is quite small!") | 73 sys.stderr.write("!Your dictionary is below 8k entries, " |
74 "that is quite small!\n") | |
68 tainted = True | 75 tainted = True |
69 | |
70 print("|= Number of words |= possibilities |") | |
71 for i in range(1, 5): | |
72 print("| {:2d} | 2^{:4.1f} |".format( | |
73 i, math.log(dl**i, 2))) | |
74 return d | 76 return d |
75 | 77 |
76 | 78 |
77 def readDingDict(filename="/usr/share/trans/de-en", useLeft=False): | 79 def readDingDict(options, filename="/usr/share/trans/de-en", useLeft=False): |
78 """Read dictionary with unique words from file in Ding format. | 80 """Read dictionary with unique words from file in Ding format. |
79 | 81 |
80 useLeft: Boolean to control which language to use | 82 useLeft: Boolean to control which language to use |
81 | 83 |
82 TODO: add option to use both languages for people that speak them both? | 84 TODO: add option to use both languages for people that speak them both? |
88 |;\ # second pattern '; ' | 90 |;\ # second pattern '; ' |
89 |(?<=\S)/(?=\S) # 3.: '/' surrounded by chars | 91 |(?<=\S)/(?=\S) # 3.: '/' surrounded by chars |
90 |\s+ # by whitespace | 92 |\s+ # by whitespace |
91 """, re.VERBOSE) | 93 """, re.VERBOSE) |
92 | 94 |
93 print("Reading entries from {}.".format(filename), end='') | 95 if not options.just_passphrase: |
96 print("Reading entries from {}.".format(filename), end='') | |
94 counter = 0 # for progress or stopping early | 97 counter = 0 # for progress or stopping early |
95 with open(filename, "r") as f: | 98 with open(filename, "r") as f: |
96 for line in f: | 99 for line in f: |
97 if line[0] == '#': | 100 if line[0] == '#': |
98 continue | 101 continue |
109 #TODO: check for very common words and remove them? | 112 #TODO: check for very common words and remove them? |
110 | 113 |
111 counter += 1 | 114 counter += 1 |
112 ## stop early when debugging | 115 ## stop early when debugging |
113 #if counter > 10: break | 116 #if counter > 10: break |
114 if not counter % 10000: | 117 if not options.just_passphrase and counter % 10000 == 0: |
115 print('.', end='') | 118 print('.', end='') |
116 sys.stdout.flush() | 119 sys.stdout.flush() |
117 print() | 120 if not options.just_passphrase: |
121 print() | |
118 | 122 |
119 return list(dset) | 123 return list(dset) |
120 | 124 |
121 | 125 |
122 def main(): | 126 def main(): |
123 global tainted | 127 global tainted |
124 | 128 |
125 parser = argparse.ArgumentParser(description=__doc__.splitlines()[0]) | 129 parser = argparse.ArgumentParser( |
130 description=__doc__.splitlines()[0], | |
131 formatter_class=argparse.ArgumentDefaultsHelpFormatter) | |
132 parser.add_argument('-n', '--number-of-words', type=int, default=4, | |
133 help='how many words to draw for the passphrase, ' | |
134 'most useful with -j') | |
135 parser.add_argument('-j', '--just-passphrase', action="store_true", | |
136 help='only output the passphrase on a single line') | |
126 parser.add_argument('--ddump-filename', | 137 parser.add_argument('--ddump-filename', |
127 help='filename to dump the dictionary to') | 138 help='filename to dump the dictionary to') |
128 options = parser.parse_args() | 139 options = parser.parse_args() |
129 | 140 |
130 dictionary = buildDictionary(options) | 141 dictionary = buildDictionary(options) |
131 | 142 |
132 howMany = 4 | 143 how_many = options.number_of_words |
133 | 144 |
134 # use a dictionary with lower cased words for a simple check if | 145 output_string = "" |
135 # our random source is okay | 146 if not options.just_passphrase: |
136 print("\nGenerated passphrase with {} randomly selected words:\n".format( | 147 print("\nGenerated passphrase with {}" |
137 howMany)) | 148 " randomly selected words:\n".format(how_many)) |
138 print(" ", end='') | 149 print(" ", end='') |
150 separator = '\n ' | |
151 else: | |
152 separator = ' ' | |
153 | |
154 # use a dictionary `words` with lower cased words for a rudimentary check | |
139 words = {} | 155 words = {} |
140 for x in range(howMany): | 156 for x in range(how_many): |
141 word = _srandom.choice(dictionary) | 157 word = _srandom.choice(dictionary) |
142 words[word.lower()] = True | 158 words[word.lower()] = True |
143 print(word, end='\n ') | 159 output_string += word + separator |
144 print("\n") | |
145 | 160 |
146 if len(words) < howMany: | 161 print(output_string) |
147 print("! Your random generator is weak") | 162 |
148 print("! or you are being very lucky.") | 163 if len(words) < how_many: |
164 sys.stderr.write("! You've drawn a word more than once, this means:\n" | |
165 "! Your random generation is weak" | |
166 " or you are being very lucky.\n") | |
149 tainted = True | 167 tainted = True |
150 | 168 |
151 if tainted: | 169 if tainted: |
152 print("!!! Don't use the resulting passphrase !!!") | 170 sys.exit("!!! Don't use the resulting passphrase !!!") |
153 | 171 |
154 if __name__ == "__main__": | 172 if __name__ == "__main__": |
155 main() | 173 main() |