The following two programs are simple examples of how one might go about creating a password generator. The first one was created several years ago and was designed to return passwords where similarly classed characters would not appear beside each other. It works but gets unbearably slow when passwords exceed about forty characters. The second program was designed with similar requirements runs considerably better. Eight-character passwords appear to have the same randomness to their construction, and this program is considerably faster than the first. Hopefully, these program design differences will be helpful to others trying to write their own password generator.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 | ###############################
# old_password_generator.py #
###############################
import string, random, sys
SELECT = string.ascii_letters + string.punctuation + string.digits
SAMPLE = random.SystemRandom().sample
def main():
while True:
size = get_size()
password = generate_pw(size)
print_pause(password)
def get_size():
while True:
try:
size = int(input('Size: '))
except ValueError:
print('Please enter a number.')
except EOFError:
sys.exit()
else:
if 1 <= size <= 80:
return size
print('Valid number range is 1 - 80.')
def generate_pw(size):
password = ''.join(SAMPLE(SELECT, size))
while not approved(password):
password = ''.join(SAMPLE(SELECT, size))
return password
def approved(password):
group = select(password[0])
for character in password[1:]:
trial = select(character)
if trial is group:
return False
group = trial
return True
def select(character):
for group in (string.ascii_uppercase,
string.ascii_lowercase,
string.punctuation,
string.digits):
if character in group:
return group
raise ValueError('Character was not found in any group!')
def print_pause(*values, sep=' ', end='\n', file=sys.stdout):
print(*values, sep=sep, end=end, file=file)
try:
input()
except EOFError:
pass
if __name__ == '__main__':
main()
###############################
# new_password_generator.py #
###############################
from random import SystemRandom
from string import ascii_lowercase, ascii_uppercase, digits, punctuation
CHOICE = SystemRandom().choice
GROUPS = ascii_lowercase, ascii_uppercase, digits, punctuation
def main():
while True:
print('Code:', make_password(get_size()))
def get_size():
while True:
try:
size = int(input('Size: '))
except ValueError:
print('Please enter a number.')
except EOFError:
raise SystemExit()
else:
if 10 <= size <= 80:
return size
print('Valid number range is 10 - 80.')
def make_password(size):
while True:
password = ''
pool = using = tuple(map(set, GROUPS))
while True:
selection = CHOICE(using)
character = CHOICE(tuple(selection))
password += character
if len(password) == size:
return password
selection.remove(character)
if not selection:
break
using = tuple(group for group in pool if group is not selection)
if __name__ == '__main__':
main()
|
The two-step choosing process in make_password
is used to prevent shorter character classes from being consumed and not being represented near the end of a password. Also, it gives each available character class an equal opportunity to be selected. If classes were concatenated together and a character selected from them, smaller classes would have less opportunity to be chosen, thus skewing probabilities as the password was being constructed. The current scheme should prevent such imbalances.
I'm a newbie to Python. I've learned a lot from your script. Thank you!!