comparison cutils/genpwd.py @ 254:655f9e4bc6f2

genpwd: allow grouping of the output
author Franz Glasner <fzglas.hg@dom66.de>
date Thu, 13 Feb 2025 11:57:32 +0100
parents 4314ee20927a
children 48430941c18c
comparison
equal deleted inserted replaced
253:4314ee20927a 254:655f9e4bc6f2
99 aparser.add_argument( 99 aparser.add_argument(
100 "-E", dest="use_bin_length", action="store_true", 100 "-E", dest="use_bin_length", action="store_true",
101 help="For some repertoires make OUTPUT-LENGTH the number of bytes" 101 help="For some repertoires make OUTPUT-LENGTH the number of bytes"
102 " that is to be read from random sources instead of output bytes") 102 " that is to be read from random sources instead of output bytes")
103 aparser.add_argument( 103 aparser.add_argument(
104 "--group", "-G", action="store_true",
105 help="""Group the results. If no "--group-sep" or "--group-size" is
106 given use their respective defaults.""")
107 aparser.add_argument(
108 "--group-sep", action="store", default=None, metavar="GROUP-SEP",
109 help="""Group the result using GROUP-SEP as separator.
110 Option "--group" is implied.
111 Default when grouping is enabled is the SPACE character ` '.""")
112 aparser.add_argument(
113 "--group-size", action="store", type=int, default=None,
114 metavar="GROUP-SIZE",
115 help="""Group the result using a group size of GROUP-SIZE characters.
116 Option "--group" is implied.
117 Default when grouping is enabled is 6.""")
118 aparser.add_argument(
104 "req_length", metavar="OUTPUT-LENGTH", type=int, 119 "req_length", metavar="OUTPUT-LENGTH", type=int,
105 help="The required length of the generated output") 120 help="The required length of the generated output")
106 121
107 opts = aparser.parse_args(args=argv) 122 opts = aparser.parse_args(args=argv)
123
124 grouper = None
125 if opts.group or opts.group_sep is not None or opts.group_size is not None:
126 if opts.group_sep is None:
127 gsep = b' '
128 else:
129 if PY2:
130 gsep = opts.group_sep
131 else:
132 gsep = opts.group_sep.encode("utf8")
133 gsize = 6 if opts.group_size is None else opts.group_size
134 grouper = make_grouper(sep=gsep, size=gsize)
108 135
109 if opts.repertoire: 136 if opts.repertoire:
110 try: 137 try:
111 repertoire = (opts.repertoire 138 repertoire = (opts.repertoire
112 if PY2 139 if PY2
113 else opts.repertoire.encode("iso-8859-15")) 140 else opts.repertoire.encode("iso-8859-15"))
114 except UnicodeError: 141 except UnicodeError:
115 raise ValueError("non ISO-8859-15 character in given repertoire") 142 raise ValueError("non ISO-8859-15 character in given repertoire")
116 pwd = gen_from_repertoire(opts.req_length, repertoire) 143 pwd = gen_from_repertoire(opts.req_length, repertoire, grouper=grouper)
117 elif opts.algorithm == "web": 144 elif opts.algorithm == "web":
118 pwd = gen_from_repertoire(opts.req_length, WEB_CHARS) 145 pwd = gen_from_repertoire(
146 opts.req_length, WEB_CHARS, grouper=grouper)
119 elif opts.algorithm == "safe-web": 147 elif opts.algorithm == "safe-web":
120 pwd = gen_from_repertoire(opts.req_length, SAFE_WEB_CHARS) 148 pwd = gen_from_repertoire(
149 opts.req_length, SAFE_WEB_CHARS, grouper=grouper)
121 elif opts.algorithm == "safe-web-2": 150 elif opts.algorithm == "safe-web-2":
122 pwd = gen_from_repertoire(opts.req_length, SAFE_WEB_CHARS_2) 151 pwd = gen_from_repertoire(
152 opts.req_length, SAFE_WEB_CHARS_2, grouper=grouper)
123 elif opts.algorithm == "uri": 153 elif opts.algorithm == "uri":
124 pwd = gen_from_repertoire(opts.req_length, URI_CHARS) 154 pwd = gen_from_repertoire(
155 opts.req_length, URI_CHARS, grouper=grouper)
125 elif opts.algorithm == "safe-uri": 156 elif opts.algorithm == "safe-uri":
126 pwd = gen_from_repertoire(opts.req_length, SAFE_URI_CHARS) 157 pwd = gen_from_repertoire(
158 opts.req_length, SAFE_URI_CHARS, grouper=grouper)
127 elif opts.algorithm == "safe-uri-2": 159 elif opts.algorithm == "safe-uri-2":
128 pwd = gen_from_repertoire(opts.req_length, SAFE_URI_CHARS_2) 160 pwd = gen_from_repertoire(
161 opts.req_length, SAFE_URI_CHARS_2, grouper=grouper)
129 elif opts.algorithm == "ascii": 162 elif opts.algorithm == "ascii":
130 pwd = gen_from_repertoire(opts.req_length, FULL_ASCII) 163 pwd = gen_from_repertoire(
164 opts.req_length, FULL_ASCII, grouper=grouper)
131 elif opts.algorithm == "safe-ascii": 165 elif opts.algorithm == "safe-ascii":
132 pwd = gen_from_repertoire(opts.req_length, SAFE_ASCII) 166 pwd = gen_from_repertoire(
167 opts.req_length, SAFE_ASCII, grouper=grouper)
133 elif opts.algorithm == "alnum": 168 elif opts.algorithm == "alnum":
134 pwd = gen_from_repertoire(opts.req_length, ALNUM) 169 pwd = gen_from_repertoire(opts.req_length, ALNUM, grouper=grouper)
135 elif opts.algorithm == "safe-alnum": 170 elif opts.algorithm == "safe-alnum":
136 pwd = gen_from_repertoire(opts.req_length, SAFE_ALNUM) 171 pwd = gen_from_repertoire(
172 opts.req_length, SAFE_ALNUM, grouper=grouper)
137 elif opts.algorithm == "bin-base64": 173 elif opts.algorithm == "bin-base64":
138 encoder = base64.b64encode 174 encoder = base64.b64encode
139 pwd = gen_bin(opts.req_length, opts.use_bin_length, encoder, 175 pwd = gen_bin(opts.req_length, opts.use_bin_length, encoder,
140 rstrip_chars=b"=") 176 rstrip_chars=b"=", grouper=grouper)
141 elif opts.algorithm == "bin-urlsafe-base64": 177 elif opts.algorithm == "bin-urlsafe-base64":
142 encoder = base64.urlsafe_b64encode 178 encoder = base64.urlsafe_b64encode
143 pwd = gen_bin(opts.req_length, opts.use_bin_length, encoder, 179 pwd = gen_bin(opts.req_length, opts.use_bin_length, encoder,
144 rstrip_chars=b"=") 180 rstrip_chars=b"=", grouper=grouper)
145 elif opts.algorithm == "bin-base32": 181 elif opts.algorithm == "bin-base32":
146 encoder = base64.b32encode 182 encoder = base64.b32encode
147 pwd = gen_bin(opts.req_length, opts.use_bin_length, encoder, 183 pwd = gen_bin(opts.req_length, opts.use_bin_length, encoder,
148 rstrip_chars=b"=") 184 rstrip_chars=b"=", grouper=grouper)
149 elif opts.algorithm == "bin-ascii85": 185 elif opts.algorithm == "bin-ascii85":
150 encoder = base64.a85encode 186 encoder = base64.a85encode
151 pwd = gen_bin(opts.req_length, opts.use_bin_length, encoder) 187 pwd = gen_bin(
188 opts.req_length, opts.use_bin_length, encoder, grouper=grouper)
152 elif opts.algorithm == "bin-hex": 189 elif opts.algorithm == "bin-hex":
153 encoder = binascii.hexlify 190 encoder = binascii.hexlify
154 pwd = gen_bin(opts.req_length, opts.use_bin_length, encoder) 191 pwd = gen_bin(
192 opts.req_length, opts.use_bin_length, encoder, grouper=grouper)
155 else: 193 else:
156 raise NotImplementedError("algorithm not yet implemented: %s" 194 raise NotImplementedError("algorithm not yet implemented: %s"
157 % opts.algorithm) 195 % opts.algorithm)
158 if opts.use_bin_length: 196 if opts.group or opts.group_size or opts.group_sep:
159 if len(pwd) < opts.req_length: 197 if len(pwd) < opts.req_length:
160 raise AssertionError("internal length mismatch") 198 raise AssertionError("internal length mismatch")
161 else: 199 else:
162 if len(pwd) != opts.req_length: 200 if opts.use_bin_length:
163 raise AssertionError("internal length mismatch") 201 if len(pwd) < opts.req_length:
202 raise AssertionError("internal length mismatch")
203 else:
204 if len(pwd) != opts.req_length:
205 raise AssertionError("internal length mismatch")
164 if PY2: 206 if PY2:
165 print(pwd) 207 print(pwd)
166 sys.stdout.flush() 208 sys.stdout.flush()
167 else: 209 else:
168 sys.stdout.buffer.write(pwd) 210 sys.stdout.buffer.write(pwd)
169 sys.stdout.buffer.write(b'\n') 211 sys.stdout.buffer.write(b'\n')
170 sys.stdout.buffer.flush() 212 sys.stdout.buffer.flush()
171 213
172 214
173 def gen_from_repertoire(length, repertoire): 215 def gen_from_repertoire(length, repertoire, grouper=None):
174 """Select `length` characters randomly from given character repertoire 216 """Select `length` characters randomly from given character repertoire
175 `repertoire`. 217 `repertoire`.
176 218
177 """ 219 """
178 assert len(repertoire) <= 256 220 assert len(repertoire) <= 256
188 break 230 break
189 if PY2: 231 if PY2:
190 pwd = b''.join(pwd) 232 pwd = b''.join(pwd)
191 else: 233 else:
192 pwd = bytes(pwd) 234 pwd = bytes(pwd)
235 if grouper:
236 pwd = grouper(pwd)
193 return pwd 237 return pwd
194 238
195 239
196 def gen_bin(length, use_bin_length, encoder, rstrip_chars=None): 240 def gen_bin(length, use_bin_length, encoder, rstrip_chars=None, grouper=None):
197 """Generate from :func:`os.urandom` and just encode with given `encoder`. 241 """Generate from :func:`os.urandom` and just encode with given `encoder`.
198 242
199 """ 243 """
200 pwd = encoder(os.urandom(length)) 244 pwd = encoder(os.urandom(length))
201 return pwd.rstrip(rstrip_chars) if use_bin_length else pwd[:length] 245 pwd = pwd.rstrip(rstrip_chars) if use_bin_length else pwd[:length]
246 if grouper:
247 pwd = grouper(pwd)
248 return pwd
249
250
251 def make_grouper(sep=b' ', size=6):
252
253 def _grouper(pwd):
254 if not pwd or size <= 0:
255 return pwd
256 assert isinstance(pwd, bytes)
257 groups = []
258 idx = 0
259 while idx < len(pwd):
260 groups.append(pwd[idx:idx+size])
261 idx += size
262 return sep.join(groups)
263
264 assert isinstance(sep, bytes)
265 return _grouper
202 266
203 267
204 if __name__ == "__main__": 268 if __name__ == "__main__":
205 main() 269 main()