Skip to content

Instantly share code, notes, and snippets.

@mnewt
Created April 17, 2015 03:49
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mnewt/8d2eef4150d93a90d273 to your computer and use it in GitHub Desktop.
Save mnewt/8d2eef4150d93a90d273 to your computer and use it in GitHub Desktop.
Fun Password Generator
#!/usr/bin/env python3
"""
Generate random passwords. Mainly useful for copying and pasting
into password managers
"""
import argparse, random, string, sys, unicodedata, re
"""
Generate an untypable unicode password
"""
def _unicode_character_generator():
# Based on (https://github.com/omaciel/fauxfactory/pull/70/files)
"""Generates unicode characters
:return: a generator which will generates all unicode letters available
"""
# Arbitrarily chosen unicode character categories:
# Ll = Letter, lower case (Latin, Greek, etc)
# Lo = Letter, other (Chinese, Japanese, etc)
# Lu = Letter, upper case (Latin, Greek, etc)
# Nd = Number, decimal digit (Arabic, Bengali, etc)
# Pd = Punctuation, dash
# Pc = Punctuation, close
# Pf = Punctuation, Final quote (may behave like Ps or Pe depending on usage)
# Pi = Punctuation, Initial quote (may behave like Ps or Pe depending on usage)
# Po = Punctuation, Other
# Ps = Punctuation, Open
# Sc = Symbol, Currency
# Sk = Symbol, Modifier
# Sm = Symbol, Math
# Zs = Separator, Space
UNICODE_CATEGORIES = [ 'Ll', 'Lu']
if sys.version_info.major == 2:
chr_function = unichr # pylint:disable=undefined-variable
range_function = xrange # pylint:disable=undefined-variable
else:
chr_function = chr
range_function = range
# Use sys.maxunicode instead of 0x10FFFF to avoid the exception below, in a
# narrow Python build (before Python 3.3)
# ValueError: unichr() arg not in range(0x10000) (narrow Python build)
# For more information, read PEP 261.
for i in range_function(sys.maxunicode):
char = chr_function(i)
if unicodedata.category(char) in UNICODE_CATEGORIES:
# char is a regular unicode character
# See (http://www.fileformat.info/info/unicode/category/index.htm)
yield char
def meets_reqs(pw, args):
"""Evaluates whether the password (pw) meets complexity requirements
as defined in command line arguments (minimum-*)
:return: Boolean indicating whether requirements are met
"""
if args.minimum_lower > 0:
if len(re.findall('[' + string.ascii_lowercase + ']', pw)) < args.minimum_lower:
return False
if args.minimum_upper > 0:
if len(re.findall('[' + string.ascii_uppercase + ']', pw)) < args.minimum_upper:
return False
if args.minimum_digits > 0:
if len(re.findall('[' + string.digits + ']', pw)) < args.minimum_digits:
return False
if args.minimum_special > 0:
if len(re.findall('[' + string.punctuation + ']', pw)) < args.minimum_special:
return False
return True
def main():
"""
Process command line arguments
"""
parser = argparse.ArgumentParser(description='Generate random passwords')
parser.add_argument("length",
nargs='?',
type=int,
default=16,
help="length of password")
parser.add_argument("--number", "-n",
type=int,
default=1,
help="number of passwords to generate")
parser.add_argument("--pronounceable", "-p",
default=False,
action="store_true",
help="Create human pronounceable passwords")
parser.add_argument("--lower", "-l",
default=False,
action="store_true",
help="Use lower case letters")
parser.add_argument("--minimum-lower", "-L",
type=int,
default=0,
help="minimum number of lowercase characters required")
parser.add_argument("--upper", "-u",
default=False,
action="store_true",
help="Use upper case letters")
parser.add_argument("--minimum-upper", "-U",
type=int,
default=0,
help="minimum number of upper characters required")
parser.add_argument("--digits", "-d",
default=False,
action="store_true",
help="Use digits")
parser.add_argument("--minimum-digits", "-D",
type=int,
default=0,
help="minimum number of digit characters required")
parser.add_argument("--special", "-s",
default=False,
action="store_true",
help="Use special characters (punctuation)")
parser.add_argument("--minimum-special", "-S",
type=int,
default=0,
help="minimum number of special characters required")
parser.add_argument("--characters", "-c",
default='',
help="Specify individual characters")
parser.add_argument("--unicode", "-Z",
default=False,
action="store_true",
help="Use a very large unicode character set full of untypable characters")
args = parser.parse_args()
if args.pronounceable:
# not implemented
print("I don't know how to speak human yet so here is a random password")
if any([args.lower, args.upper, args.digits, args.special, args.characters]):
# use only specified character sets
alphabet = ''
if args.lower:
alphabet += string.ascii_lowercase
if args.upper:
alphabet += string.ascii_uppercase
if args.digits:
alphabet += string.digits
if args.special:
alphabet += string.punctuation
alphabet += args.characters
elif args.unicode:
alphabet = ''.join([c for c in _unicode_character_generator()])
else:
# no specific character sets were specified so use the defaults
alphabet = string.digits + string.ascii_letters + string.punctuation
"""
Generate the password
"""
rg = random.SystemRandom()
# generate a completely random password
# but only accept it if it meets the complexity requirements
# this is a super naive way of doing this
# and it will freeze for sufficiently high minimum numbers
# but the randomness is good
n = 0
while n < args.number:
pw = str().join(rg.choice(alphabet) for _ in range(args.length))
if meets_reqs(pw, args):
n += 1
print(pw)
# alternate method of meeting complexity requirements
# it's not quite as random because it makes it more likely that
# the minimum number of specified characters is exceeded
# for _ in range(args.number):
# pw = ''
# pw += str().join(rg.choice(string.ascii_lowercase) for _ in range(args.minimum_lower))
# pw += str().join(rg.choice(string.ascii_uppercase) for _ in range(args.minimum_upper))
# pw += str().join(rg.choice(string.digits) for _ in range(args.minimum_digits))
# pw += str().join(rg.choice(string.punctuation) for _ in range(args.minimum_special))
# pw += str().join(rg.choice(alphabet) for _ in range(len(pw), args.length))
# print(''.join(rg.sample(pw,len(pw))))
# without the complexity requirements
# for _ in range(args.number):
# print(str().join(rg.choice(alphabet) for _ in range(args.length)))
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment