Welcome, guest | Sign In | My Account | Store | Cart

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.

Python, 106 lines
  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.

1 comment

Jace Perham 5 years, 3 months ago  # | flag

I'm a newbie to Python. I've learned a lot from your script. Thank you!!