I want a password that doesn't flicker, so I use python to create a password generator that specifies a pattern.
ppwgen.py
#!/usr/bin/env python
flag_debug = False
flag_verbose = False
flag_syntax = False
def debug(msg):
if flag_debug:
print(msg)
def verbose(msg):
if flag_verbose:
print(msg)
def import_random():
try:
from Crypto.Random import random
verbose('import Crypto.Random')
return random
except ImportError:
pass
try:
import secrets
verbose('import secrets')
return secrets
except ImportError:
pass
import random
verbose('import random')
return random
def charset_(r, x=''):
s = ''
for c in r:
c = chr(c)
if c not in x:
s = s + c
return s
def charset(s, x=''):
r = ''
while len(s) >= 3:
if s[1] != '-':
break
cs = ord(s[0])
ce = ord(s[2])
r = r + charset_(range(cs, ce+1), x)
s = s[3:]
for c in s:
if c not in x:
r = r + c
return r
def isescape(c):
return (len(c) > 1) and (c[0] == '\\')
def unescape(c):
if isescape(c):
return c[1:]
return c
class InvalidPattern(Exception):
def __init__(self):
Exception.__init__(self)
class PatIter:
def __init__(self, pat):
self.pos = 0
s = []
for c in list(pat):
if len(s) and s[-1] == '\\':
s[-1] = s[-1] + c
else:
s.append(c)
if len(s) and s[-1] == '\\':
if flag_syntax:
raise InvalidPattern()
s = s[:-1]
self.pat = s
def __iter__(self):
return self
def __next__(self):
return self.next()
def next(self):
p = self.pos
if p >= len(self.pat):
raise StopIteration()
self.pos = p + 1
return self.pat[p]
def rewind(self):
self.pos = self.pos - 1
return self
class Generator:
def __init__(self, data):
self.data = data
def generate(self, random):
s = self.data
if type(s) == str:
return random.choice(s)
if type(s) == tuple:
n = s[0]
s = [s[1]]
elif type(s) == list:
n = 1
else:
raise Exception("Bug!")
r = ''
for i in range(n):
for g in s:
r = r + g.generate(random)
return r
PATTERN_MAP = {
'b': '01',
'o': '01234567',
'd': charset('0-9'),
'X': charset('0-9A-F'),
'x': charset('0-9a-f'),
'A': charset('A-Za-z'),
'C': charset('A-Z'),
'c': charset('a-z'),
'B': 'AEIOUaeiou',
'V': 'AEIOU',
'v': 'aeiou',
'D': charset('A-Za-z', 'AEIUOaeiou'),
'Q': charset('A-Z', 'AEIOU'),
'q': charset('a-z', 'aeiou'),
'Y': charset('0-9A-Za-z'),
'Z': charset('0-9A-Z'),
'z': charset('0-9a-z'),
'W': charset('0-9A-Za-z_'),
'L': charset('0-9A-Z'),
'l': charset('0-9a-z'),
'%': '%',
}
def check_not_close(c, q):
return ((c is None) or (c != q)) and flag_syntax
class CustomPasswordGenerator:
def __init__(self, pattern):
ptr = PatIter(pattern)
r = []
for c in ptr:
if c != '%':
g = Generator(unescape(c))
else:
g = self.parse_pattern(ptr, False)
if g != None:
r.append(g)
self.generator = Generator(r)
def parse_pattern(self, ptr, nest):
n = None
c = None
for c in ptr:
if not c.isdigit():
break
if n is None:
n = 0
n = (n * 10) + int(c, 10)
if n is None:
n = 1
if c is None:
if flag_syntax:
raise InvalidPattern()
return None
if c in PATTERN_MAP:
return Generator((n, Generator(PATTERN_MAP[c])))
if c == '{':
return self.parse_subpat(ptr, n)
if c == '[':
return self.parse_charset(ptr, n)
if isescape(c):
c = unescape(c)
elif (not nest) or (c in ']}'):
raise InvalidPattern()
return Generator((n, Generator(c)))
def parse_subpat(self, ptr, rep):
r = []
c = None
for c in ptr:
if c == '}':
break
g = self.parse_pattern(ptr.rewind(), True)
if g != None:
r.append(g)
if check_not_close(c, '}'):
raise InvalidPattern()
return Generator((rep, Generator(r)))
def parse_charset(self, ptr, rep):
r = []
s = []
c = None
for c in ptr:
if c == ']':
break
if len(s) < 2:
s.append(c)
continue
a = unescape(s[0])
if s[-1] != '-':
r.append(a)
s = s[1:]
s.append(c)
continue
b = unescape(c)
r.append(charset_(range(ord(a), ord(b)+1)))
s = []
if check_not_close(c, ']'):
raise InvalidPattern()
for a in s:
r.append(unescape(a))
return Generator((rep, Generator(''.join(r))))
def generate(self, rnd=None, length=0):
if rnd is None:
rnd = import_random()
pw = self.generator.generate(rnd)
if length == 0:
return pw
while length > len(pw):
pw = pw + self.generator.generate(rnd)
return pw[:length]
def main():
import argparse
global flag_debug
global flag_verbose
global flag_syntax
parser = argparse.ArgumentParser()
parser.add_argument('-d', '--debug', action='store_true', default=False)
parser.add_argument('-v', '--verbose', action='store_true', default=False)
parser.add_argument('-l', '--length', type=int, default=0)
parser.add_argument('-U', '--upper', action='store_true', default=False)
parser.add_argument('-L', '--lower', action='store_true', default=False)
parser.add_argument('-n', '--count', type=int, default=0)
parser.add_argument('-p', '--syntax', action='store_true', default=False)
parser.add_argument('pattern', type=str, default='%l')
args = parser.parse_args()
flag_debug = args.debug
flag_verbose = args.verbose or flag_debug
flag_syntax = args.syntax
converter = str
if args.upper:
converter = str.upper
if args.lower:
converter = str.lower
pattern = args.pattern
gen_length = args.length
gen_count = args.count
if gen_count == 0:
gen_count = {False: 20, True: 2}[flag_debug]
verbose('pattern = \'%s\'' % pattern)
verbose('length = %d' % gen_length)
verbose('count = %d' % gen_count)
if flag_debug:
verbose('PATTERN_MAP')
for k in sorted(PATTERN_MAP):
verbose(' \'%s\' : \'%s\'' % (k, PATTERN_MAP[k]))
verbose('')
try:
gen = CustomPasswordGenerator(pattern)
except InvalidPattern:
print('invalid pattern: \'%s\'' % pattern)
return
rnd = import_random()
fmt = '%' + str(len(str(gen_count))) + 'd: %s'
for i in range(gen_count):
print(fmt % (i+1, converter(gen.generate(rnd, gen_length))))
if __name__ == '__main__':
main()
Describe the generated pattern in C format style.
Immediately after the character% is the character selected by the random number.
Specifier | type | Remarks |
---|---|---|
b | Binary number | [01] |
o | 8 base | [0-7] |
d | Numbers | [0-9] |
X | Hexadecimal capital | [0-9A-F] |
X | Hexadecimal lowercase | [0-9a-f] |
A | English letters | [A-Za-z] |
C | Uppercase letters | [A-Z] |
c | Lowercase letters | [A-Z] |
B | English vowels | [AEIOUaeiou] |
V | English vowels(Big) | [AEIOU] |
v | English vowels(small) | [aeiou] |
D | Eiko sound | [AEIOUaeiuo]except for[A-Za-z] |
Q | Eiko sound(Big) | [AEIOU]except for[A-Z] |
q | Eiko sound(small) | [AEIOU]except for[A-Z] |
Y | Alphanumeric | [0-9A-Za-z] |
Z | Alphanumeric(Big) | [0-9A-Z] |
z | Alphanumeric(small) | [0-9a-z] |
W | Label character | [0-9A-Za-z_] |
L | Label character(Big) | [0-9A-Z_] |
l | Label character(small) | [0-9a-z_] |
Example of arranging specifiers
$ python ppwgen.py -n 5 'qiita-%d%X%x%x%A%C%c%c%B%V%v%D%Q%q%Y%Z%z%W%L%l'
1: qiita-2D1cHSuhIEotMrdXh53r
2: qiita-2454ZXzlOIeXHc9GpD3n
3: qiita-2481ZFxhOUeZGjjHoP5y
4: qiita-7BffcNydeEaGZnUMyDUe
5: qiita-52f5nZqtAAiWTbGTphHj
The number immediately after the character% is the number of characters selected by the random number. % [Numeric] specifier It is in the form of. For example,'% 3d' will be a 3-digit number.
$ python ppwgen.py -n 5 'qiita-%3d'
1: qiita-419
2: qiita-879
3: qiita-452
4: qiita-054
5: qiita-025
Above sample 'qiita-%d%X%x%x%A%C%c%c%B%V%v%D%Q%q%Y%Z%z%W%L%l' Is abbreviated in the'% {...}' format 'qiita-%{dXxxACccBVvDQqYZzWLl}' I can do it.
$ python ppwgen.py -n 5 'qiita-%{dXxxACccBVvDQqYZzWLl}'
1: qiita-32b3eMjmeUolYkdFjsNj
2: qiita-3F84MNzfAOoLNt1Y8GSi
3: qiita-9A3dURxtAAovPg7V8p9b
4: qiita-4D47iTquuEiDKjsQ3bM4
5: qiita-87dcXGjkuIuCMk1H5u9x
Specifies the character to select with'[...]' instead of the specifier.
#Example of 8-digit binary number
$ python ppwgen.py -n 5 '%8[01]'
1: 01010110
2: 00100001
3: 10100111
4: 11011000
5: 00101110
Specify the character to select with'['start character'-' end character']'.
#Select from the letters STUVWXYZ
$ python ppwgen.py -n 5 '%8[S-Z]'
1: XXTTZWTS
2: YUTTXUTV
3: UVUWXTXX
4: UTSZUWZS
5: UTWWYUVX
The character immediately following the backslash \ has no special meaning.
#Letter S,-,Choose from 3 of Z
$ python ppwgen.py -n 5 '%8[S\-Z]'
1: -ZZ---S-
2: ZZZ--ZZZ
3: -ZZSS-SS
4: ZSS-SS-Z
5: Z----SZ-
License key style
$ python ppwgen.py -n 10 'qiita%4{-c3d}'
1: qiita-w119-i910-y458-q980
2: qiita-j159-p127-u802-b125
3: qiita-f923-o256-o568-h824
4: qiita-k280-q899-a826-q831
5: qiita-d264-a506-v904-q475
6: qiita-u866-p655-n009-o483
7: qiita-m101-a054-p896-x707
8: qiita-o524-b339-j527-n802
9: qiita-o030-k832-y107-s425
10: qiita-w842-a925-h295-p339
Repeat'-consonant vowel consonant number'.
$ python ppwgen.py -n 10 'qiita%4{-Qvqd}'
1: qiita-Sap0-Woy0-Yeg6-Vig0
2: qiita-Viq4-Giq7-Yoq0-Cam4
3: qiita-Luz7-Woc8-Wek8-Xek0
4: qiita-Saf8-Mac9-Kar4-Mif1
5: qiita-Xen3-Kur3-Gix9-Mom4
6: qiita-Xew6-Var0-Luf5-Guq9
7: qiita-Gew8-Ded2-Jon0-Qac5
8: qiita-Tiq2-Yef7-Reg3-Qiz9
9: qiita-Paw8-Lap9-Jex7-Xuc2
10: qiita-Rud5-Yos6-Vam8-Pip8
A complex pattern with a nested structure.
$ python ppwgen.py -n 10 'qiita%2{-C3{2c[13579]}}'
1: qiita-Oqv5ct1eg9-Cfv3ha3yo1
2: qiita-Vtw3yq5ka3-Nha9zh7vr1
3: qiita-Ulk5yb3ne3-Dpz7wu5xu3
4: qiita-Yqb1lg3uf5-Iqn5qy9xa1
5: qiita-Qck5jr1ru1-Tgo9ab1rh3
6: qiita-Vdc7tj3oj5-Iba3eu1yc3
7: qiita-Pqn9wn1ws1-Ksb3ys5bt1
8: qiita-Vpo7hq1wi7-Iyh3uc3lt1
9: qiita-Fmq5mu3wh7-Tdd3gq7ea1
10: qiita-Sqb1iv1gb5-Boy9va7ve9