Rewriting the password generator

Password policy


Passwords out of politics


I got the feeling that I’ve written a function for generating passwords five times already. And he did it differently every time. And the reason for this is the different password requirements for different projects and tools. There will be no complicated code, just a summary of a simple new solution that came to me yesterday.


Let's start with simple password requirements:


  • must be of arbitrary length
  • must consist of any printed characters

import string
import random
from typing import List

def generate_password(length: int) -> str:
     """
     Generate a password of a given `length`.
     """
     result: List[str] = []
     choices = string.printable #    ,    
     while len(result) < length:
         symbol = random.choice(string.printable)
         result.append(symbol)
     return "".join(result)

We try:


>>> generate_password(8)
... "1{k]/2)h"
>>> generate_password(13)
... "9ar|&:a+U]Il$"

Well, the task is completed, we can watch pictures with cats until the end of the working day.


Sudden politics


, , MyDB . , :


  • 8
  • - (!&? )
  • , bash-

, . :


  • , , -
  • ,


Okay, this is already complicated, so let's start with a function generate_random_stringthat will simply generate random strings from what they gave.


import string
import random
from typing import List

def generate_random_string(length: int, *choices: str) -> str:
    """
    Generate a string of a given `length`.

    The result has at least one symbol from each of `choices` if `length` allows.

    Arguments:
        length -- Result string length.
        choices -- Strings with available symbols.
    """
    if not choices:
        #        ,    
        choices = (string.ascii_letters, ) 

    #      
    all_choices = "".join(choices)
    result: List[str] = []
    choice_index = 0
    while len(result) < length:
        #      , 
        #        
        if choice_index < len(choices):
            symbol = random.choice(choices[choice_index])
            result.append(symbol)
            choice_index += 1
            continue

        #        
        symbol = random.choice(all_choices)
        result.append(symbol)

    #       
    random.shuffle(result)
    return "".join(result)

So, let's try:


>>> #    
>>> generate_random_string(8, string.digits)
... "59197550"
>>> #       
>>> generate_random_string(8, string.ascii_letters, "!") 
... "vIOWXN!o"

Great, it's time to actually generate a password that meets all our requirements.


def generate_mydb_password(length: int) -> str:
    """
    Generate a random password for MyDB of a given `length`.

    The result has at least:
    - one uppercase letter
    - one lowercase letter
    - one digit
    - one special character

    Raises:
        ValueError -- If `length` is lesser than 8.
    """
    if length < 8:
        raise ValueError("Password length should be at least 8")

    return generate_random_string(
        length,
        string.ascii_uppercase, #      
        string.ascii_lowercase, #  
        string.digits, #  
        "!&?", #  -,    
    )

It remains only to check:


>>> generate_mydb_password(8)
... "P?P1&7zL"
>>> generate_mydb_password(13)
... "tR!QslK!Sl7EO"
>>> generate_mydb_password(2)
... ValueError: Password length should be at least 8

Total


We wrote an easy-to-understand and at the same time fairly random password generator, and there is still a lot of time until the end of the working day. If there is no trust in the library random, then you can replace it with the one you like.


Thank you for the attention!

Source: https://habr.com/ru/post/undefined/


All Articles