Статья Генератор паролей из мнемонических фраз. Делаем скрипт на Python

Генераторы паролей уже давно не являются чем-то редким и эксклюзивным. Тем более, что вы всегда можете воспользоваться своим природным «генератором», то есть мозгом для того, чтобы придумать пароль. Более того, доступно множество как оффлайн, так и онлайн решений для выполнения данной задачи. Но, мне всегда хотелось сделать что-то подобное самостоятельно, чтобы немного понять принцип работы данного продукта. Поэтому я сделал небольшой скрипт на Python, которым хочу с вами поделиться. Может быть он будет кому-то полезен.

000.jpeg



Что потребуется?

Как это не удивительно, но установки сторонних библиотек в данном случае не нужны. Однако, это не значит, что для создания генератора будет достаточно написать только код. Ведь мы будем генерировать не просто набор букв в случайной последовательности, а именно парольные фразы, которые по сути являются мнемоническим паролем. Как показала моя практика, такие парольные фразы запоминаются надолго. Я, к примеру, помню пару парольных фраз из четырех слов уже в течении порядка 10 лет.

Генерировать мы будем случайные фразы, количество слов в которых будет указываться пользователем. Рекомендую делать фразы не более чем из 4 слов. Впрочем, тут уже по желанию. Также, мы будем указывать какое количество символов каждого слова фразы брать для составления пароля, использовать заглавные или строчные буквы, а также указывать количество генерируемых фраз, чтобы у нас была возможность выбора.

Для того, чтобы составить хоть какую-то мнемоническую фразу, надо вспомнить уроки русского языка из начальных классов:

Из чего состоит предложение?

Для примера:
  • из прилагательного, которое отвечает на вопрос — какой?
  • существительного, которое отвечает на вопрос — кто, что?
  • глагола — что делает, что сделает?
Пример: «Красный ботинок плавает». Это в простом варианте, если не углубляться в дебри русского языка.

Для того, чтобы составить фразу из прилагательного, существительного и глагола, то есть, выбрать случайное слово, нам необходимы словари, которые будут содержать только прилагательные, только существительные или только глаголы. Такие словари можно найти в интернете, а возможно составить самостоятельно. Для примера, прилагательные и существительные я нашел в сети. А вот глаголы пришлось составлять самостоятельно, а потом еще и переделывать у них окончания. Однако, все слова не осилил. Потому, некоторые глаголы выглядят странно. Я постараюсь приложить словари к статье во вложении или, если не позволит их объем, размещу на внешнем сервисе.

После того, как у вас будут необходимые словари, их нужно назвать: adjectives, nouns, verb соответственно и положить в папку dictionaries.

Итак, теперь все необходимое для написания скрипта у нас есть. Давайте приступим.

Для начала, импортируем в скрипт нужные библиотеки:

Python:
import random
import sys
import time
from pathlib import Path

Как видите, нужно не так уж и много. random, для псевдослучайного выбора, sys, для завершения работы скрипта, time, для измерения времени его работы и Path, для составления путей к словарям и сохраняемому списку паролей.

Теперь, в самом начале, проверим, существует ли директория со словарями. Если нет, выведем инструкцию, что необходимо сделать. Затем создадим списки со словами из словарей, а также, сразу же объединим все словари и слегка их перемешаем. Последний, объединенный словарь нам понадобиться в том случае, если длина парольной фразы будет больше 4 слов.

Python:
if not (Path.cwd() / 'dictionaries').exists():
    print("""
        Директории со словарями не существует.
        Создайте директорию dictionaries и поместите в нее следующие словари:
            adjectives.txt - словарь прилагательных;
            nouns.txt - словарь существительных;
            verb.txt - словарь глаголов.
    """)
    sys.exit(0)
path = Path.cwd() / 'dictionaries'
adjectives = [x.strip() for x in open(path / 'adjectives.txt', 'r', encoding='utf-8')]
nouns = [x.strip() for x in open(path / 'nouns.txt', 'r', encoding='utf-8')]
verb = [x.strip() for x in open(path / 'verb.txt', 'r', encoding='utf-8')]
merge_vokab = [*adjectives, *nouns, *verb]
s_vokab = list(set(merge_vokab))


Замена русских букв на буквы английского алфавита (qwerty)

Создадим функцию translit(text: str), которая на входе будет принимать слово в русской раскладке, а возвращать его же, но уже переведенное в английскую раскладку. То есть, это не является полноценной транслитерацией, когда русские буквы заменяются на соответствующие им английские, а всего лишь будет происходить замена букв в том порядке, в каком они расположены на клавиатуре.

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

Python:
def translit(text: str):
    """
    Замена русских букв на соответствующие им английские.
    """
    symbols = ("абвгдеёжзийклмнопрстуфхцчшщъыьэюяАБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ",
               "fpdultygpbqrkvyjghcneahwxioismeuzFPDULTYGPBQRKVYJGHCNEAHWXIOISMEUZ")
    tr = {ord(a): ord(b) for a, b in zip(*symbols)}
    return text.translate(tr)


Выборка определенного количества букв каждого слова

Создадим функцию choice(wok: str, count_symbol: int, title: str) -> tuple. На входе она будет принимать слово, количество символов, которое нужно взять из него и переменную title, которая будет указывать, следует ли делать первую букву слова заглавной, делать ли все буквы заглавными или возвращать их как есть, то есть, в нижнем регистре.

Каждый из словарей состоит из слов в нижнем регистре, поэтому дополнительного перевода не требуется. Но, если очень хочется, можно внести немного хаоса. То есть, составить словари из разнородных слов. И просто переводить при указании все символы в нижний регистр или возвращать, если не указано ничего символы, как есть в словаре.

Таким образом, считываем значение переменной title. Если оно равно 1, возвращаем кортеж, который состоит из первых букв слова, переведенных в английскую раскладку, то же самое количество символов, но уже в русской раскладке и оригинальное слово, с первой заглавной буквой. Соответственно, если title равно 2, возвращаем то же самое, но все символы заглавные. Если 3 — все символы строчные.

Python:
def choice(wok: str, count_symbol: int, title: str) -> tuple:
    """
    Выполняется транслитерация символов.
    Возвращает количество символов слова определенных в переменной.
    Возвращает оригинальное слово.
    """
    if title == "1":
        return translit(wok.strip().title()[:count_symbol]), wok.strip().title()[:count_symbol], wok.strip().title()
    elif title == "2":
        return translit(wok.strip().upper()[:count_symbol]), wok.strip().upper()[:count_symbol], wok.strip().upper()
    elif title == "3":
        return translit(wok.strip()[:count_symbol]), wok.strip()[:count_symbol], wok.strip()
    else:
        return translit(wok.strip()[:count_symbol]), wok.strip()[:count_symbol], wok.strip()


Функция генератора мнемонических фраз

Создадим функцию generate(count: int, count_words: int, count_symbol: int, title: str) -> list. На входе она получает количество парольных фраз, количество слов во фразе, количество символов, которые нужно брать из каждого слова, и переменную, указывающую, в какой раскладке генерировать слова.

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

Python:
def generate(count: int, count_words: int, count_symbol: int, title: str) -> list:
    global adjectives, nouns, verb, s_vokab
    words_list = []
    vokab = {0: adjectives, 1: nouns, 2: verb, 3: nouns}

Запустим цикл по диапазону из количества парольных фраз. Определим временные списки, в которые будем помещать: первые буквы слова после транслитерации; первые три буквы оригинального слова; оригинальное слово.

Python:
    for k in range(int(count)):
        m = []
        r = []
        o = []

Теперь проверим количество слов во фразе. Если количество слов меньше или равно 4, то запускаем цикл по диапазону из количества слов. Выбираем случайное слово из словаря, который соответствует цифре из диапазона. Проверяем, не нужно ли пользователю сделать первую букву фразы заглавной, а все остальные прописные. Именно на это указывает «4». Если да, передаем в функцию транслитерации первое слово заглавное, а остальные прописные. Если title не равен 4, то передаем в функцию транслитерации то значение, которое пришло в функцию. После этого добавляем полученные значения во временные списки.

Python:
        if 0 < count_words <= 4:
            for i in range(count_words):
                wok = random.choice(tuple(vokab[i]))
                if title == "4":
                    mix, rus, orig = choice(wok, count_symbol, "1") if i == 0 else choice(wok, count_symbol, "3")
                else:
                    mix, rus, orig = choice(wok, count_symbol, title)
                m.append(mix)
                r.append(rus)
                o.append(orig)

Тоже самое проделываем и в том случае, если количество слов во фразе больше 4. Но, здесь уже выбираем случайные слова из объединенного и перемешанного словаря. Также обрабатываем первую заглавную букву во фразе и добавляем значения во временные списки.

Python:
        elif count_words > 4:
            for i in range(count_words):
                wok = random.choice(tuple(s_vokab))
                if title == "4":
                    mix, rus, orig = choice(wok, count_symbol, "1") if i == 0 else choice(wok, count_symbol, "3")
                else:
                    mix, rus, orig = choice(wok, count_symbol, title)
                m.append(mix)
                r.append(rus)
                o.append(orig)

После того, как фраза будет сгенерирована, добавляем ее в список в виде кортежа, при этом объединив временные списки с помощью join. Когда будет закончена генерация всех фраз, возвращаем список с ними из функции.

Python:
        words_list.append(("".join(m), " ".join(r), " ".join(o)))
    return words_list

Python:
def generate(count: int, count_words: int, count_symbol: int, title: str) -> list:
    """
    Выбираем случайное слово из словарей. Если слов не больше 4,
    выполняется выборка по трем спискам с глаголами, существительными и прилагательными.
    Если слов больше 4, производится случайная выборка по объединенному списку.
    Полученные слова добавляются в списки, которые объединяются и возвращаются из функции.
    """
    global adjectives, nouns, verb, s_vokab
    words_list = []
    vokab = {0: adjectives, 1: nouns, 2: verb, 3: nouns}
    for k in range(int(count)):
        m = []
        r = []
        o = []
        if 0 < count_words <= 4:
            for i in range(count_words):
                wok = random.choice(tuple(vokab[i]))
                if title == "4":
                    mix, rus, orig = choice(wok, count_symbol, "1") if i == 0 else choice(wok, count_symbol, "3")
                else:
                    mix, rus, orig = choice(wok, count_symbol, title)
                m.append(mix)
                r.append(rus)
                o.append(orig)
        elif count_words > 4:
            for i in range(count_words):
                wok = random.choice(tuple(s_vokab))
                if title == "4":
                    mix, rus, orig = choice(wok, count_symbol, "1") if i == 0 else choice(wok, count_symbol, "3")
                else:
                    mix, rus, orig = choice(wok, count_symbol, title)
                m.append(mix)
                r.append(rus)
                o.append(orig)
        words_list.append(("".join(m), " ".join(r), " ".join(o)))
    return words_list


Печать сгенерированных фраз и паролей в терминале

Создадим функцию print_word(mix: list). На входе она получает список с кортежами из парольных фраз. Затем в цикле пробегаемся по данному списку и выводим на печать значения из кортежей, к которым добавляем порядковые номера, полученные из функции enumerate, к которым добавляем 1.

Данная функция не является обязательной, потому ее можно легко убрать или просто закомментировать ее вызов в функции main.

Python:
def print_word(mix: list):
    """
    Печать паролей в терминал.
    """
    print(f"\nСгенерированные парольные фразы\n{'-' * 31}")
    for num, i in enumerate(mix):
        print(f'{num + 1}. {i[0]} | {i[1]} | {i[2]}')


Сохранение сгенерированных парольных фраз в текстовый файл

Создадим функцию save_word(mix: list). На входе она получает список с кортежами из парольных фраз. А логика ее работы напоминает логику работы функции печати, за исключением того, что в данном случае мы выводим значения из кортежей не в терминал, а сохраняем в текстовый документ. Обратите внимание, что при каждой генерации текстовый документ будет перезаписываться, так как указана опция «w». Если вы хотите с каждой генерацией дописывать уже существующий файл, замените ее на «a».

Python:
def save_word(mix: list):
    """
    Сохранение паролей в текстовый документ.
    """
    with open('password_list.txt', 'w', encoding='utf-8') as file:
        file.write("Сгенерированные парольные фразы\n\n")
        for num, i in enumerate(mix):
            file.write(f'{num + 1}. {i[0]} | {i[1]} | {i[2]}\n')


Запуск генерации паролей. Ввод необходимых для генерации параметров

Создадим функцию main(). Здесь мы для начала запрашиваем у пользователя необходимые параметры. Такие как: количество слов в пароле; количество символов каждого слова; с какой буквы начинать слова фразы; количество паролей для генерации.

Затем переменной t присваиваем значение текущего времени. Запускаем функцию генерации паролей и передаем в нее полученные у пользователя значения. Обратите внимание на то, что проверка каждого значения после его ввода не производиться. Вместо этого мы обрабатываем исключение, которое возникнет при попытке перевода введенного значения в int. После этого запускаем функцию печати списка с паролями и сохраняем их в текстовый файл.

Выводим в терминал время генерации паролей.

Python:
def main():
    count_words = input("Введите количество слов в пароле: ")
    count_symbol = input("Введите количество символов каждого слова: ")
    title = input("Каждое слово фразы:\n   "
                  "[1] Начинать с заглавной буквы;\n   "
                  "[2] Все буквы заглавные;\n   "
                  "[3] Все буквы прописные;\n   "
                  "[4] Первая буква заглавная, остальные прописные."
                  ">>> ")
    count = input("Введите количество паролей для генерации: ")

    t = time.monotonic()
    try:
        mix = generate(int(count), int(count_words), int(count_symbol), title)
        print_word(mix)
        save_word(mix)
        print(f'\nВремя генерации паролей: {time.monotonic() - t:.2f}')
    except ValueError:
        print("Вы ввели неверное значение!")
        sys.exit(0)


if __name__ == "__main__":
    main()

И, вот что у нас получилось.

Для примера:
  • количество паролей 3;
  • количество символов каждого слова — 3;
  • количество слов в парольной фразе — 3;
  • парольная фраза начинается с заглавной буквы.
01.png


Вот как это выглядит в текстовом документе:

02.png


Теперь сгенерируем пароли с большим количеством слов во фразе. Для примера — 7.

03.png



Python:
import random
import sys
import time
from pathlib import Path

if not (Path.cwd() / 'dictionaries').exists():
    print("""
        Директории со словарями не существует.
        Создайте директорию dictionaries и поместите в нее следующие словари:
            adjectives.txt - словарь прилагательных;
            nouns.txt - словарь существительных;
            verb.txt - словарь глаголов.
    """)
    sys.exit(0)
path = Path.cwd() / 'dictionaries'
adjectives = [x.strip() for x in open(path / 'adjectives.txt', 'r', encoding='utf-8')]
nouns = [x.strip() for x in open(path / 'nouns.txt', 'r', encoding='utf-8')]
verb = [x.strip() for x in open(path / 'verb.txt', 'r', encoding='utf-8')]
merge_vokab = [*adjectives, *nouns, *verb]
s_vokab = list(set(merge_vokab))


def translit(text: str):
    """
    Замена русских букв на соответствующие им английские.
    """
    symbols = ("абвгдеёжзийклмнопрстуфхцчшщъыьэюяАБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ",
               "fpdultygpbqrkvyjghcneahwxioismeuzFPDULTYGPBQRKVYJGHCNEAHWXIOISMEUZ")
    tr = {ord(a): ord(b) for a, b in zip(*symbols)}
    return text.translate(tr)


def choice(wok: str, count_symbol: int, title: str) -> tuple:
    """
    Выполняется транслитерация символов.
    Возвращает количество символов слова определенных в переменной.
    Возвращает оригинальное слово.
    """
    if title == "1":
        return translit(wok.strip().title()[:count_symbol]), wok.strip().title()[:count_symbol], wok.strip().title()
    elif title == "2":
        return translit(wok.strip().upper()[:count_symbol]), wok.strip().upper()[:count_symbol], wok.strip().upper()
    elif title == "3":
        return translit(wok.strip()[:count_symbol]), wok.strip()[:count_symbol], wok.strip()
    else:
        return translit(wok.strip()[:count_symbol]), wok.strip()[:count_symbol], wok.strip()


def generate(count: int, count_words: int, count_symbol: int, title: str) -> list:
    """
    Выбираем случайное слово из словарей. Если слов не больше 4,
    выполняется выборка по трем спискам с глаголами, существительными и прилагательными.
    Если слов больше 4, производится случайная выборка по объединенному списку.
    Полученные слова добавляются в списки, которые объединяются и возвращаются из функции.
    """
    global adjectives, nouns, verb, s_vokab
    words_list = []
    vokab = {0: adjectives, 1: nouns, 2: verb, 3: nouns}
    for k in range(int(count)):
        m = []
        r = []
        o = []
        if 0 < count_words <= 4:
            for i in range(count_words):
                wok = random.choice(tuple(vokab[i]))
                if title == "4":
                    mix, rus, orig = choice(wok, count_symbol, "1") if i == 0 else choice(wok, count_symbol, "3")
                else:
                    mix, rus, orig = choice(wok, count_symbol, title)
                m.append(mix)
                r.append(rus)
                o.append(orig)
        elif count_words > 4:
            for i in range(count_words):
                wok = random.choice(tuple(s_vokab))
                if title == "4":
                    mix, rus, orig = choice(wok, count_symbol, "1") if i == 0 else choice(wok, count_symbol, "3")
                else:
                    mix, rus, orig = choice(wok, count_symbol, title)
                m.append(mix)
                r.append(rus)
                o.append(orig)
        words_list.append(("".join(m), " ".join(r), " ".join(o)))
    return words_list


def print_word(mix: list):
    """
    Печать паролей в терминал.
    """
    print(f"\nСгенерированные парольные фразы\n{'-' * 31}")
    for num, i in enumerate(mix):
        print(f'{num + 1}. {i[0]} | {i[1]} | {i[2]}')


def save_word(mix: list):
    """
    Сохранение паролей в текстовый документ.
    """
    with open('password_list.txt', 'w', encoding='utf-8') as file:
        file.write("Сгенерированные парольные фразы\n\n")
        for num, i in enumerate(mix):
            file.write(f'{num + 1}. {i[0]} | {i[1]} | {i[2]}\n')


def main():
    count_words = input("Введите количество слов в пароле: ")
    count_symbol = input("Введите количество символов каждого слова: ")
    title = input("Каждое слово фразы:\n   "
                  "[1] Начинать с заглавной буквы;\n   "
                  "[2] Все буквы заглавные;\n   "
                  "[3] Все буквы прописные;\n   "
                  "[4] Первая буква заглавная, остальные прописные.\n   "
                  ">>> ")
    count = input("Введите количество паролей для генерации: ")

    t = time.monotonic()
    try:
        mix = generate(int(count), int(count_words), int(count_symbol), title)
        print_word(mix)
        save_word(mix)
        print(f'\nВремя генерации паролей: {time.monotonic() - t:.2f}')
    except ValueError:
        print("Вы ввели неверное значение!")
        sys.exit(0)


if __name__ == "__main__":
    main()

Как видите, в этом случае получается большая ересь, чем в первом случае. Это потому, что для генерации паролей с количеством слов больше 4 мы используем смиксованные словари. А соответственно, получаем больше хаоса. Однако, такие пароли достаточно сложны для запоминания.

А на этом, пожалуй, все.

Спасибо за внимание. Надеюсь, данная информация будет вам полезна
 

Вложения

  • mnemonic_pass_gen.zip
    256,2 КБ · Просмотры: 180
Последнее редактирование модератором:
  • Нравится
Реакции: Notsaint и InternetMC
Мы в соцсетях:

Обучение наступательной кибербезопасности в игровой форме. Начать игру!