-
-
Save mnewt/8d2eef4150d93a90d273 to your computer and use it in GitHub Desktop.
Fun Password Generator
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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