Fully rewrite passwordy.

master
voussoir 2021-09-11 11:15:31 -07:00
parent 3032ed135d
commit 79c4f59cf2
No known key found for this signature in database
GPG Key ID: 5F7554F8C26DACCB
1 changed files with 114 additions and 175 deletions

View File

@ -1,135 +1,98 @@
''' '''
passwordy
=========
This module provides functions for generating random strings. This module provides functions for generating random strings.
Command line usage:
> passwordy <length> [flags]
length:
Integer number of characters, or words when using sentence mode.
# Sentence mode:
--sentence:
If this argument is passed, `length` random words are chosen.
Only --separator, --upper, and --lower can be used in sentence mode.
--separator <string>:
When using sentence mode, the words will be joined with this string.
# urandom mode:
--urandom:
If this argument is passed, os.urandom is called for cryptographically
strong randomness and the password is shown as hex.
Only --upper and --lower can be used in urandom mode (though the hex is
lowercase by default).
# Normal mode:
--letters:
Include ASCII letters in the password.
If none of the other following options are chosen, letters is the default.
--digits:
Include digits 0-9 in the password.
--hex
Include 0-9, a-f in the password.
--binary
Include 0, 1 in the password.
--punctuation
Include punctuation symbols in the password.
--upper
Convert the entire password to uppercase.
--lower
Convert the entire password to lowercase.
''' '''
import argparse
import math import math
import os import os
import random import random
import string import string
import sys import sys
DEFAULT_LENGTH = 32 from voussoirkit import betterhelp
DEFAULT_SENTENCE = 5 from voussoirkit import pipeable
HELP_MESSAGE = '''
===============================================================================
Generates a randomized password.
> passwordy [length] [options] def make_password(
length,
letters=False,
digits=False,
hex=False,
binary=False,
punctuation=False,
):
alphabet = set()
if letters:
alphabet.update(string.ascii_letters)
if digits:
alphabet.update(string.digits)
if hex:
alphabet.update('0123456789abcdef')
if binary:
alphabet.update('01')
if punctuation:
alphabet.update(string.punctuation)
length: How many characters. Default %03d. if not alphabet:
options: raise ValueError('No alphabet options chosen.')
h : consist entirely of hexadecimal characters.
b : consist entirely of binary characters.
dd : consist entirely of decimal characters.
default : consist entirely of upper+lower letters.
p : allow punctuation in conjunction with above. return ''.join(random.choices(tuple(alphabet), k=length))
d : allow digits in conjunction with above.
l : convert to lowercase. def make_sentence(length, separator=' '):
u : convert to uppercase.
nd : no duplicates. Each character can only appear once.
Examples:
> passwordy 32 h l
98f17b6016cf08cc00f2aeecc8d8afeb
> passwordy 32 h u
2AA706866BF7A5C18328BF866136A261
> passwordy 32 u
JHEPTKCEFZRFXILMASHNPSTFFNWQHTTN
> passwordy 32 p
Q+:iSKX!Nt)ewUvlE*!+^D}hp+|<wpJ}
> passwordy 32 l p
m*'otz/"!qo?-^wwdu@fasf:|ldkosi`
===============================================================================
Generates a randomized sentence of words.
> passwordy sent [length] [join]
length : How many words. Default %03d.
join : The character that will join words together.
Default space.
Examples:
> passwordy sent
arrowroot sheared rustproof undo propionic acid
> passwordy sent 8
cipher competition solid angle rigmarole lachrymal social class critter consequently
> passwordy sent 8 _
Kahn_secondary_emission_unskilled_superior_court_straight_ticket_voltameter_hopper_crass
===============================================================================
'''.strip() % (DEFAULT_LENGTH, DEFAULT_SENTENCE)
def listget(li, index, fallback=None):
try:
return li[index]
except IndexError:
return fallback
def make_password(length=None, passtype='standard'):
'''
Returns a string of length `length` consisting of a random selection
of uppercase and lowercase letters, as well as punctuation and digits
if parameters permit
'''
if length is None:
length = DEFAULT_LENGTH
alphabet = ''
if 'standard' in passtype:
alphabet = string.ascii_letters
elif 'digit_only' in passtype:
alphabet = string.digits
elif 'hex' in passtype:
alphabet = '0123456789abcdef'
elif 'binary' in passtype:
alphabet = '01'
if '+digits' in passtype:
alphabet += string.digits
if '+punctuation' in passtype:
alphabet += string.punctuation
if '+lowercase' in passtype:
alphabet = alphabet.lower()
elif '+uppercase' in passtype:
alphabet = alphabet.upper()
alphabet = list(set(alphabet))
if '+noduplicates' in passtype:
if len(alphabet) < length:
message = 'Alphabet "%s" is not long enough to support no-dupe password of length %d'
message = message % (alphabet, length)
raise Exception(message)
password = ''
for x in range(length):
random.shuffle(alphabet)
password += alphabet.pop(0)
else:
password = ''.join(random.choices(alphabet, k=length))
return password
def make_sentence(length=None, joiner=' '):
''' '''
Returns a string containing `length` words, which come from Returns a string containing `length` words, which come from
dictionary.common. dictionary.common.
''' '''
import dictionary.common as common import dictionary.common as common
if length is None:
length = DEFAULT_LENGTH
words = random.choices(common.words, k=length) words = random.choices(common.words, k=length)
words = [w.replace(' ', joiner) for w in words] words = [w.replace(' ', separator) for w in words]
result = joiner.join(words) result = separator.join(words)
return result return result
def random_hex(length): def random_hex(length):
@ -144,72 +107,48 @@ def urandom_hex(length):
token = token[:length] token = token[:length]
return token return token
def main_password(argv): def passwordy_argparse(args):
length = listget(argv, 0, DEFAULT_LENGTH) if args.sentence:
options = [a.lower() for a in argv[1:]] password = make_sentence(args.length, args.separator)
elif args.urandom:
if '-' in length: password = urandom_hex(args.length)
length = length.replace(' ', '') else:
length = [int(x) for x in length.split('-', 1)] if not any([args.letters, args.digits, args.hex, args.binary, args.punctuation]):
length = random.randint(*length) letters = True
else:
elif not length.isdigit() and options == []: letters = args.letters
options = [length] password = make_password(
length = DEFAULT_LENGTH length=args.length,
letters=letters,
length = int(length) digits=args.digits,
hex=args.hex,
passtype = 'standard' binary=args.binary,
if 'dd' in options: punctuation=args.punctuation,
passtype = 'digit_only' )
if 'b' in options: if args.lower:
passtype = 'binary' password = password.lower()
if 'h' in options: elif args.upper:
passtype = 'hex' password = password.upper()
pipeable.stdout(password)
if 'l' in options: return 0
passtype += '+lowercase'
elif 'u' in options:
passtype += '+uppercase'
if 'p' in options:
passtype += '+punctuation'
if 'd' in options:
passtype += '+digits'
if 'nd' in options:
passtype += '+noduplicates'
return make_password(length, passtype=passtype)
def main_sentence(argv):
length = listget(argv, 1, DEFAULT_SENTENCE)
joiner = listget(argv, 2, ' ')
try:
length = int(length)
except ValueError:
joiner = length
length = DEFAULT_SENTENCE
return make_sentence(length, joiner)
def main_urandom(argv):
length = listget(argv, 1, DEFAULT_LENGTH)
length = int(length)
return urandom_hex(length)
def main(argv): def main(argv):
mode = listget(argv, 0, 'password') parser = argparse.ArgumentParser(description=__doc__)
if 'help' in mode:
print(HELP_MESSAGE)
quit()
if 'sent' in mode: parser.add_argument('length', type=int)
print(main_sentence(argv)) parser.add_argument('--urandom', action='store_true')
elif 'urandom' in mode: parser.add_argument('--sentence', action='store_true')
print(main_urandom(argv)) parser.add_argument('--separator', nargs='?', default=' ')
else: parser.add_argument('--letters', action='store_true')
print(main_password(argv)) parser.add_argument('--digits', action='store_true')
parser.add_argument('--hex', action='store_true')
parser.add_argument('--binary', action='store_true')
parser.add_argument('--punctuation', action='store_true')
parser.add_argument('--lower', action='store_true')
parser.add_argument('--upper', action='store_true')
parser.set_defaults(func=passwordy_argparse)
return betterhelp.single_main(argv, parser, __doc__)
if __name__ == '__main__': if __name__ == '__main__':
raise SystemExit(main(sys.argv[1:])) raise SystemExit(main(sys.argv[1:]))