1

I'm trying to generate random passwords for the Active Directory that has the following password requirements: at least 8 characters, at least one special character, at least one digit, at least one lowercase and at least one uppercase.

With the following code I'm able to generate a random password and check whether it contains a special character. New password is generated until a special character is found.

special_char = "!@%/()=?+.-"

password_string = "".join([random.choice(string.ascii_lowercase + string.ascii_digits + string.ascii_uppercase + special_char) for n in range(8)])

while any(c in password_string for c in special_char) is not True:
    password_string = "".join([random.choice(string.ascii_lowercase + string.ascii_digits + string.ascii_uppercase + special_char) for n in range(8)])

The problem with the following is that it's only checking for the special character and generating a new password might get rid of the other requirements assuming they existed. How could I implement the AD password requirements efficiently? Thanks for any help.

1
  • 1
    Another remark: don't use (boolean expr) is not True; use not (boolean expr). Commented Nov 18, 2015 at 8:22

5 Answers 5

9

You could generate a matching password to begin with, by first picking a special char and a digit (and one lower- and one uppercase letter in the same manner), filling up with anything, and shuffling the order in the end:

pwlist = ([random.choice(special_char),
           random.choice(string.ascii_digits),
           random.choice(string.ascii_lowercase),
           random.choice(string.ascii_uppercase),
          ]  
         + [random.choice(string.ascii_lowercase
                          + string.ascii_uppercase
                          + special_char
                          + string.ascii_digits) for i in range(4)])
random.shuffle(pwlist)
pw = ''.join(pwlist)
Sign up to request clarification or add additional context in comments.

2 Comments

Good solution.... I like it. The expected number of characters from each character class in the final password will probably be different from the OPs algo... but I don't think that a constraint here.
@Jug: yes, it "feels true" that this should have the same properties of randomness as "discarding until you get it right", but it's not immediately obvious.
3

Use sets and intersection to enforce constraints...

import string
import random

special_char = "!@%/()=?+.-"
set_lower = set(string.ascii_lowercase)
set_upper = set(string.ascii_uppercase)
set_digits = set(string.digits)
set_sp = set(special_char)

all_chars = string.ascii_lowercase + \
    string.digits + \
    string.ascii_uppercase + \
    special_char

password_string = "".join([random.choice(all_chars) for n in range(8)])

def isOK(pw):
    pw = set(pw)
    for x in (set_lower, set_upper, set_digits, set_sp):
        if len(pw.intersection(x)) == 0:
            return False
    return True

while not isOK(password_string):
    password_string = "".join([random.choice(all_chars) for n in range(8)])

print password_string

5 Comments

string.lowercase and string.uppercase vary with locale. I'd stick to string.ascii_letters (which contains both string.ascii_lowercase and string.ascii_uppercase).
This solution has an unfortunate hit-and-miss aspect for each iteration of the while loop.
@DevPlayer agreed... This only fixes the ops approach while keeping the generated password space the same. Ulrich's answer has the side effect that it changes the probability of different passwords, from the algo that op provided. The change is minor, but it's there.
@Jug I suspected, but wasn't sure, that if the special chars were removed from the inner list comprehension of Ulrich's solutions the probabilities would be the same as the OPs. Althought I felt (not very imperical I know) that that would actually make the password harder to crack. Also I suspected my posted solution at the bottom has the same probability difference as Ulrich's.
@DevPlayer On closer inspection... I do see that the probability distribution of characters is identically same. So Ulrich's and your answers are indeed superior in that respect. Cheers.
2

Test for all conditions in your while test. It is easier to delegate testing for valid passwords to a function:

special_char = "!@%/()=?+.-"
password_characters = string.ascii_letters + string.ascii_digits + special_char

def is_valid(password):
    if len(password) < 8:
        return False
    if not any(c in password for c in special_char):
        return False
    if not any(c.isdigit() for c in password):
        return False
    if not any(c.islower() for c in password):
        return False
    if not any(c.isupper() for c in password):
        return False
    return True

password_string = ''
while not is_valid(password_string):
    password_string = "".join([random.choice(password_characters)
                               for n in range(8)])

2 Comments

This solution has the unfortunate potential for wasting time being invalid a lot until all requirements are met.
@DevPlayer: in theory, the loop could run on forever. In practice, you won't ever notice. But yes, picking a random digit, random special character, random lowercase and random uppercase letter, padding this out with all valid characters and shuffling the result is going to avoid the theoretical everlasting loop.
2

I like Ulrich's answer better. He beat me to it.

import string
import random
string_specials = '!@%/()=?+.-'
string_pool = string_specials + string.ascii_letters + string.digits
pre_password = [
    random.choice(string_specials),
    random.choice(string.ascii_uppercase),
    random.choice(string.ascii_lowercase),
    random.choice(string.digits),
    random.choice(string_pool),
    random.choice(string_pool),
    random.choice(string_pool),
    random.choice(string_pool)]
scrambled = []
while pre_password:
    scrambled.append(pre_password.pop(random.randint(0, len(pre_password)-1)))
password = ''.join(scrambled)

Comments

0

You could implement the requirements one by one..

  • at least 8 characters
  • at least one special character
  • at least one digit
  • at least one lowercase and
  • at least one uppercase.

so before you do anything else create a string that consists of 1 special character randomly chosen. 1 digit character randomly chosen. 1 lowercase character randomly chosen. and 1 uppercase character randomly chosen. thats 4 characters in total, the remaining characters can be randomly chosen, length must be larger than 8.

all generated passwords will no be valid, so no need to check for that..

now you could complain that the passwords will all have the same predictable pattern: special, digit, lowercase, uppercase, randomized stuff..

so we shuffle it..

import string
SpecialChars = "!@%/()=?+.-"

password = ""

import random
password += random.choice(string.ascii_uppercase)
password += random.choice(string.ascii_lowercase)
password += random.choice(string.digits)
password += random.choice(SpecialChars)

i = random.randint(4, 8)

for i in range(0,i):
    password += random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits+ SpecialChars)

password = ''.join(random.sample(password,len(password)))

print password

1 Comment

What's wrong with using string.ascii_digits here? For a random integer in a range, just use random.randint(4, 8).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.