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

от автора

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, то можете ее заменить на ту, которая нравится.

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

ссылка на оригинал статьи https://habr.com/ru/post/486414/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *