Как стать автором
Обновить

Переписываем генератор паролей

Время на прочтение3 мин
Количество просмотров10K

Password Policy


Пароли вне политики


У меня сложилось ощущение, что я уже раз пять писал функцию для генерации паролей. И каждый раз делал это по-разному. А причина тому — различные требования к паролю для разных проектов и инструментов. Здесь не будет сложного кода, просто краткое изложение простого нового решения, которое пришло ко мне вчера.


Начнем с простых требований к паролю:


  • должен быть произвольной длины
  • должен состоять из любых печатных символов

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)

Пробуем:


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

Отлично, задача выполнена, можем смотреть картинки с котиками до конца рабочего дня.


Внезапная политика


Но тут нам говорят, что все не так просто, и пароль для нашей базы данных MyDB должен отвечать некоторым требованиям безопасности. А именно, пароль:


  • должен быть не меньше 8 символов
  • должен содержать как минимум одну заглавную букву
  • должен содержать как минимум одну строчную букву
  • должен содержать как минимум одну цифру
  • должен содержать как минимум один спец-символ (!&? и прочие)
  • а еще он не должен содержать некоторые символы, чтобы не поломать bash-скрипты

Наш код выше для этого не годится, так что придется дорабатывать. И для решения этого я видел целый ворох разных подходов:


  • генерируем случайный пароль пока в нем не будет всех нужных символов
  • часть пароля генерируем с одними символами, а часть другими, в конец насыпаем спец-символов
  • вырезаем из пароля неподдерживаемые символы, в итоге длина не гарантированна

Новый подход


Окей, это уже сложно, так что начнем с функции generate_random_string, которая будет просто генерировать случайные строки из того, что дали.


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)

Так, попробуем:


>>> # генерируем строку из цифр
>>> generate_random_string(8, string.digits)
... "59197550"
>>> # а тут обязательно должен быть восклицательный знак
>>> generate_random_string(8, string.ascii_letters, "!") 
... "vIOWXN!o"

Отлично, пришло время собственно сгенерировать пароль, отвечающий всем нашим требованиям.


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, # и цифры
        "!&?", # и спец-символы, добавьте нужных по вкусу
    )

Осталось только проверить:


>>> 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

Итого


Мы написали простой в понимании и при этом достаточно случайный генератор паролей, а до конца рабочего дня еще куча времени. Если нет доверия библиотеке random, то можете ее заменить на ту, которая нравится.


Спасибо за внимание!

Теги:
Хабы:
+4
Комментарии26

Публикации

Истории

Работа

Data Scientist
63 вакансии
Python разработчик
142 вакансии

Ближайшие события