John the Ripper и функции вывода ключей (KDFs)

Kali LinuxBeginner
Практиковаться сейчас

Введение

В этой лабораторной работе вы изучите функции вывода ключей (KDFs), которые представляют собой криптографические алгоритмы, разработанные для усложнения атак методом перебора паролей. В отличие от простых хеш-функций, KDFs намеренно увеличивают вычислительные затраты, что значительно замедляет тестирование множества вариантов паролей. Вы узнаете об общих KDFs, таких как PBKDF2, bcrypt и scrypt, поймете, как они генерируют уникальные хеши паролей, и понаблюдаете за тем, как мощный инструмент для взлома паролей, такой как John the Ripper, взаимодействует с этими функциями. Наконец, вы получите практический опыт в реализации KDFs для безопасного хранения паролей, укрепляя лучшие практики в области кибербезопасности.

Понимание KDFs (например, PBKDF2, bcrypt, scrypt)

На этом этапе вы получите фундаментальное понимание функций вывода ключей (KDFs). KDFs — это криптографические алгоритмы, которые выводят один или несколько секретных ключей из секретного значения, такого как мастер-ключ или пароль. Их основная цель при хранении паролей — сделать атаки методом перебора и словарные атаки вычислительно затратными, даже если злоумышленник получит хешированные пароли. Это достигается за счет намеренного замедления процесса хеширования посредством итеративных вычислений и использования "соли" (salt).

"Соль" (salt) — это случайная строка данных, уникальная для каждого пароля. При хешировании пароля соль объединяется с паролем перед хешированием. Это предотвращает использование злоумышленниками предварительно вычисленных радужных таблиц для взлома паролей и гарантирует, что два одинаковых пароля дадут разные хеши, если их соли различны.

Давайте рассмотрим некоторые распространенные KDFs:

  • PBKDF2 (Password-Based Key Derivation Function 2): Эта функция применяет псевдослучайную функцию (например, HMAC-SHA256) к входному паролю вместе с солью и повторяет процесс много раз (итераций) для увеличения вычислительных затрат.
  • bcrypt: Основанный на шифре Blowfish, bcrypt разработан как адаптивный, что означает, что его вычислительные затраты могут увеличиваться со временем, чтобы соответствовать растущей скорости процессоров. Он известен своей устойчивостью к атакам с использованием GPU.
  • scrypt: Этот KDF был специально разработан для устойчивости к аппаратным атакам (ASIC и FPGA), требуя значительного объема памяти в дополнение к вычислительной мощности.

Для начала убедимся, что John the Ripper установлен, так как мы будем использовать его для демонстрации KDFs позже.

john --version

Вы должны увидеть вывод, похожий на этот, указывающий на то, что John the Ripper установлен:

John the Ripper password cracker, version 1.9.0-jumbo-1+bleeding-e7022e5 64-bit
Copyright (c) 1996-2020 by Solar Designer
...

Далее, давайте создадим простой скрипт на Python, чтобы продемонстрировать, как PBKDF2 может использоваться для хеширования пароля.

Создайте файл с именем kdf_demo.py:

nano kdf_demo.py

Добавьте следующий код Python в файл:

import hashlib
import os

def pbkdf2_hash(password, salt=None, iterations=100000):
    if salt is None:
        salt = os.urandom(16) ## Generate a random 16-byte salt

    ## PBKDF2 with HMAC-SHA256
    hashed_password = hashlib.pbkdf2_hmac(
        'sha256',
        password.encode('utf-8'),
        salt,
        iterations
    )
    return salt, hashed_password

password = "mysecretpassword"
salt, hashed = pbkdf2_hash(password)

print(f"Password: {password}")
print(f"Salt (hex): {salt.hex()}")
print(f"Hashed Password (hex): {hashed.hex()}")
print(f"Iterations: 100000")

Сохраните файл, нажав Ctrl+X, затем Y, затем Enter.

Теперь запустите скрипт Python:

python3 kdf_demo.py

Вы увидите вывод, показывающий исходный пароль, сгенерированную соль и хеш PBKDF2. Обратите внимание, что соль представляет собой случайную шестнадцатеричную строку, а хеш также является шестнадцатеричной строкой. Каждый раз, когда вы запускаете скрипт, будет генерироваться новая соль, что приведет к другому хешу для одного и того же пароля.

Password: mysecretpassword
Salt (hex): <random_hex_string>
Hashed Password (hex): <random_hex_string>
Iterations: 100000

Это демонстрирует основную концепцию KDFs: объединение пароля с уникальной солью и применение множества итераций хеш-функции для получения вычислительно затратного хеша.

Определение хешей, сгенерированных KDFs

На этом этапе вы научитесь определять формат хешей, сгенерированных различными KDFs. Хотя необработанный вывод KDF может представлять собой двоичную строку, при хранении в системах они часто кодируются (например, Base64 или шестнадцатеричным кодом) и предваряются идентификаторами, указывающими используемый KDF, соль, а иногда и количество итераций или фактор стоимости. Этот стандартизированный формат позволяет таким инструментам, как John the Ripper, правильно распознавать и обрабатывать их.

Рассмотрим распространенные форматы хешей для PBKDF2, bcrypt и scrypt.

PBKDF2:
Хеши PBKDF2 часто имеют формат, включающий алгоритм, количество итераций, соль и выведенный ключ. Например, в файлах /etc/shadow в Linux хеши PBKDF2 (в частности, SHA512) могут выглядеть следующим образом:
$6$rounds=5000$<salt>$<hash>
Здесь $6$ указывает на SHA-512, rounds= задает количество итераций, за которыми следуют соль и сам хеш.

bcrypt:
Хеши bcrypt легко узнаваемы по префиксу $2a$, $2b$ или $2y$, за которым следует фактор стоимости (например, 10), соль и хеш.
Пример: $2a$10$<salt><hash>

scrypt:
Хеши scrypt обычно начинаются с $7$ или $scrypt$, за которыми следуют параметры, такие как ln, r, p (логарифмическая стоимость, размер блока и фактор параллелизма), соль и хеш.
Пример: $7$<ln>$<r>$<p>$<salt><hash>

Для демонстрации давайте создадим файл, содержащий несколько примеров хешей KDF. Мы будем использовать инструмент под названием mkpasswd (который является частью пакета whois) для генерации хеша bcrypt, а затем вручную составим хеш PBKDF2 для демонстрации.

Сначала установите пакет whois, чтобы получить mkpasswd:

sudo apt install -y whois

Теперь сгенерируем хеш bcrypt для пароля "password123" с фактором стоимости 10.

mkpasswd -m bcrypt -S $(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 16) -s 10 password123

Эта команда генерирует хеш bcrypt. Опция -S предоставляет случайную соль, а -s 10 устанавливает фактор стоимости равным 10. Вывод будет представлять собой строку хеша bcrypt.

$2a$10$<random_salt_string><hash_string>

Теперь создадим файл с именем kdf_hashes.txt, который сможет прочитать John the Ripper. Мы включим хеш bcrypt и вручную созданный хеш PBKDF2.

nano kdf_hashes.txt

Добавьте следующее содержимое в файл. Замените <YOUR_GENERATED_BCRYPT_HASH> фактическим хешем bcrypt, который вы сгенерировали на предыдущем шаге.

user1:$2a$10$<YOUR_GENERATED_BCRYPT_HASH>
user2:$pbkdf2-sha256$100000$c0ffee$a1b2c3d4e5f678901234567890abcdef1234567890abcdef1234567890abcdef

Примечание: Хеш PBKDF2 для user2 является заполнитель для демонстрационных целей. Это не реальный хеш известного пароля, но он соответствует формату, который John the Ripper ожидает для PBKDF2-SHA256. Формат: $pbkdf2-sha256$<iterations>$<salt_hex>$<hash_hex>.

Сохраните файл, нажав Ctrl+X, затем Y, затем Enter.

Теперь используем John the Ripper для идентификации типов хешей в kdf_hashes.txt:

john --format=raw-md5 --show kdf_hashes.txt

Примечание: Мы используем --format=raw-md5 как фиктивный формат, потому что John требует указания формата для --show, даже если это просто идентификация. John автоматически определит фактические форматы KDF.

Вывод покажет, как John идентифицирует типы хешей:

0 password hashes cracked, 2 left

John правильно определяет, что есть два хеша, и распознает их типы KDF при попытке их взлома. Этот шаг в основном фокусируется на распознавании форматов хешей.

Наблюдение за поддержкой KDFs в John the Ripper

На этом этапе вы увидите, как John the Ripper поддерживает взлом хешей, сгенерированных KDFs. John the Ripper имеет встроенную поддержку широкого спектра типов хешей, включая различные KDFs, такие как PBKDF2, bcrypt и scrypt. Когда вы предоставляете John файл с этими хешами, он автоматически определяет тип хеша и применяет соответствующие алгоритмы взлома.

Мы будем использовать файл kdf_hashes.txt, созданный на предыдущем шаге. Мы попытаемся взломать хеш user1 (bcrypt) с помощью небольшого словаря.

Сначала создадим небольшой файл словаря под названием wordlist.txt, содержащий распространенные пароли, включая "password123".

nano wordlist.txt

Добавьте следующее содержимое в wordlist.txt:

test
123456
password
password123
qwerty

Сохраните файл, нажав Ctrl+X, затем Y, затем Enter.

Теперь используем John the Ripper для взлома хешей в kdf_hashes.txt с помощью нашего wordlist.txt.

john kdf_hashes.txt --wordlist=wordlist.txt

John начнет процесс взлома. Поскольку "password123" присутствует в нашем словаре, а хеш bcrypt для user1 был сгенерирован из него, John должен быстро его взломать.

Using default input encoding: UTF-8
Loaded 2 password hashes with 2 different salts to test (bcrypt [Blowfish])
Will run 4 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
password123      (user1)
1g 0:00:00:00 DONE (2023-10-27 08:30) 100% <random_speed>c/s <random_speed>p/s <random_speed>L/s <random_speed>PC/s user1
Session completed.

Вы можете видеть, что John успешно взломал user1 и определил пароль как password123. Хеш user2 (PBKDF2) не был взломан, потому что его "пароль" не был в нашем словаре (это был хеш-заполнитель).

Чтобы просмотреть взломанные пароли, вы можете использовать опцию --show:

john --show kdf_hashes.txt

Вывод покажет взломанный пароль для user1:

user1:password123

1 password hash cracked, 1 left

Это демонстрирует способность John the Ripper автоматически обнаруживать и взламывать хеши KDF, подчеркивая важность использования надежных, уникальных паролей даже с KDF.

Понимание вычислительной стоимости KDFs

На этом этапе вы получите более глубокое понимание вычислительной стоимости, связанной с KDFs, и почему это является критически важной функцией безопасности. Основная цель KDFs — сделать взлом паролей вычислительно дорогим, тем самым увеличивая время и ресурсы, необходимые злоумышленнику для угадывания паролей. Эта стоимость контролируется такими параметрами, как количество итераций (для PBKDF2) или фактор стоимости (для bcrypt и scrypt).

Давайте вернемся к нашему скрипту kdf_demo.py и модифицируем его, чтобы включить хеширование bcrypt и scrypt, а также понаблюдаем за временем, затраченным на каждое из них.

Сначала установите библиотеку passlib, которая предоставляет реализации для различных KDFs:

pip install passlib

Теперь модифицируйте kdf_demo.py, чтобы включить хеширование bcrypt и scrypt.

nano kdf_demo.py

Замените существующее содержимое следующим кодом:

import time
from passlib.hash import pbkdf2_sha256, bcrypt, scrypt

password = "mysecretpassword"

print("--- PBKDF2-SHA256 ---")
start_time = time.time()
## Default iterations for pbkdf2_sha256 in passlib is 29000
pbkdf2_hash = pbkdf2_sha256.hash(password)
end_time = time.time()
print(f"Hash: {pbkdf2_hash}")
print(f"Time taken: {end_time - start_time:.4f} seconds")
print(f"Iterations: {pbkdf2_sha256.identify(pbkdf2_hash).get('rounds')}")
print("-" * 20)

print("--- bcrypt ---")
start_time = time.time()
## Default rounds for bcrypt in passlib is 12
bcrypt_hash = bcrypt.hash(password)
end_time = time.time()
print(f"Hash: {bcrypt_hash}")
print(f"Time taken: {end_time - start_time:.4f} seconds")
print(f"Cost factor (rounds): {bcrypt.identify(bcrypt_hash).get('rounds')}")
print("-" * 20)

print("--- scrypt ---")
start_time = time.time()
## Default parameters for scrypt in passlib are N=2^14, r=8, p=1
scrypt_hash = scrypt.hash(password)
end_time = time.time()
print(f"Hash: {scrypt_hash}")
print(f"Time taken: {end_time - start_time:.4f} seconds")
print(f"N (CPU/Memory cost): {scrypt.identify(scrypt_hash).get('N')}")
print(f"r (Block size): {scrypt.identify(scrypt_hash).get('r')}")
print(f"p (Parallelization): {scrypt.identify(scrypt_hash).get('p')}")
print("-" * 20)

Сохраните файл, нажав Ctrl+X, затем Y, затем Enter.

Теперь запустите обновленный скрипт Python:

python3 kdf_demo.py

Наблюдайте за выводом. Вы увидите хеш, сгенерированный каждым KDF, и время, затраченное на его генерацию.

--- PBKDF2-SHA256 ---
Hash: $pbkdf2-sha256$<iterations>$<salt>$<hash>
Time taken: <time> seconds
Iterations: <iterations_value>
--------------------
--- bcrypt ---
Hash: $2a$<cost_factor>$<salt_and_hash>
Time taken: <time> seconds
Cost factor (rounds): <cost_factor_value>
--------------------
--- scrypt ---
Hash: $scrypt$<N>$<r>$<p>$<salt_and_hash>
Time taken: <time> seconds
N (CPU/Memory cost): <N_value>
r (Block size): <r_value>
p (Parallelization): <p_value>
--------------------

Вы заметите, что даже для одного хеша требуется измеримое количество времени (например, миллисекунды). Это намеренная вычислительная стоимость. Если злоумышленник пытается взломать пароль, угадывая миллионы или миллиарды паролей, эта небольшая задержка на каждый хеш значительно накапливается, делая атаки методом полного перебора непрактичными.

Например, если KDF занимает 0,1 секунды для хеширования пароля, злоумышленник, пробующий 1000 угадываний в секунду, сможет протестировать только 10 паролей в секунду. Это резко замедляет процесс взлома по сравнению с простыми, быстрыми алгоритмами хеширования.

Параметры (количество итераций, фактор стоимости, N, r, p) могут быть настроены для увеличения или уменьшения этой вычислительной стоимости. Поскольку вычислительная мощность со временем увеличивается, эти параметры следует увеличивать для поддержания того же уровня безопасности.

Реализация KDFs для безопасного хранения паролей

На этом этапе вы узнаете, как реализовать KDFs для безопасного хранения паролей в практическом сценарии. Цель состоит в том, чтобы хранить хеши паролей, а не сами пароли, и использовать KDFs, чтобы сделать эти хеши устойчивыми к взлому. Это включает генерацию уникальной соли для каждого пароля, хеширование пароля с использованием выбранного KDF и соли, а также хранение полученного хеша (который включает соль и параметры KDF) в базе данных или файле.

Мы продолжим использовать Python и библиотеку passlib для имитации простой системы регистрации и входа пользователей.

Сначала убедитесь, что вы находитесь в каталоге ~/project.

cd ~/project

Теперь создайте новый скрипт Python с именем secure_auth.py:

nano secure_auth.py

Добавьте следующий код Python в файл. Этот скрипт позволит вам зарегистрировать нового пользователя с паролем (который будет хеширован с использованием bcrypt) и затем проверить попытку входа.

from passlib.hash import bcrypt

## Имитация базы данных пользователей
## В реальном приложении это была бы база данных (например, SQLite, PostgreSQL)
user_db = {}

def register_user(username, password):
    """Хеширует пароль с использованием bcrypt и сохраняет его."""
    if username in user_db:
        print(f"Error: User '{username}' already exists.")
        return False

    ## Хеширование пароля с использованием bcrypt. Passlib обрабатывает генерацию соли и фактор стоимости.
    hashed_password = bcrypt.hash(password)
    user_db[username] = hashed_password
    print(f"User '{username}' registered successfully.")
    print(f"Stored hash: {hashed_password}")
    return True

def verify_login(username, password):
    """Проверяет пароль по сохраненному хешу."""
    if username not in user_db:
        print(f"Login failed: User '{username}' not found.")
        return False

    stored_hash = user_db[username]

    ## Проверка пароля с использованием bcrypt.verify().
    ## Эта функция автоматически извлекает соль и стоимость из хеша.
    if bcrypt.verify(password, stored_hash):
        print(f"Login successful for user '{username}'.")
        return True
    else:
        print(f"Login failed: Incorrect password for user '{username}'.")
        return False

## --- Демонстрация ---
print("--- Registering Users ---")
register_user("alice", "securepassword123")
register_user("bob", "anothersecret")
register_user("alice", "duplicateuser") ## Попытка регистрации существующего пользователя

print("\n--- Attempting Logins ---")
verify_login("alice", "securepassword123") ## Правильный пароль
verify_login("bob", "wrongpassword")      ## Неправильный пароль
verify_login("charlie", "anypassword")    ## Несуществующий пользователь
verify_login("bob", "anothersecret")     ## Правильный пароль

Сохраните файл, нажав Ctrl+X, затем Y, затем Enter.

Теперь запустите скрипт secure_auth.py:

python3 secure_auth.py

Наблюдайте за выводом. Вы увидите процесс регистрации, включая сгенерированные хеши bcrypt, и результаты попыток входа.

--- Registering Users ---
User 'alice' registered successfully.
Stored hash: $2a$<cost_factor>$<salt_and_hash>
User 'bob' registered successfully.
Stored hash: $2a$<cost_factor>$<salt_and_hash>
Error: User 'alice' already exists.

--- Attempting Logins ---
Login successful for user 'alice'.
Login failed: Incorrect password for user 'bob'.
Login failed: User 'charlie' not found.
Login successful for user 'bob'.

Этот скрипт демонстрирует основные принципы безопасного хранения паролей с использованием KDFs:

  1. Хеширование при регистрации: При регистрации пользователя его пароль в открытом виде никогда не сохраняется. Вместо этого он немедленно хешируется с использованием KDF (в данном случае bcrypt), и полученный хеш сохраняется. Функция bcrypt.hash() автоматически обрабатывает генерацию соли и применяет фактор стоимости по умолчанию.
  2. Проверка при входе: Когда пользователь пытается войти в систему, его предоставленный пароль снова хешируется с использованием того же KDF и параметров (соли и фактора стоимости), извлеченных из сохраненного хеша. Затем вновь сгенерированный хеш сравнивается с сохраненным хешем. Если они совпадают, вход в систему успешен. Функция bcrypt.verify() упрощает этот процесс.

Используя KDFs, даже если злоумышленник получит доступ к вашей user_db (или реальной базе данных), он будет иметь доступ только к вычислительно дорогим хешам, что значительно затруднит и замедлит восстановление исходных паролей.

Резюме

В этой лабораторной работе вы получили полное представление о функциях вывода ключей (KDFs) и их критически важной роли в безопасном хранении паролей. Вы узнали о популярных KDFs, таких как PBKDF2, bcrypt и scrypt, ознакомились с их уникальными форматами хешей и параметрами, контролирующими их вычислительную стоимость. Вы наблюдали, как John the Ripper, мощный инструмент для взлома паролей, поддерживает эти KDFs, подчеркивая важность надежных паролей даже при использовании KDFs. Наконец, вы реализовали базовую систему безопасного хранения паролей с использованием Python и библиотеки passlib, продемонстрировав практическое применение KDFs для хеширования и проверки паролей. Эта лабораторная работа укрепила принцип того, что KDFs необходимы для того, чтобы атаки методом полного перебора и словарные атаки стали вычислительно неосуществимыми, значительно повышая безопасность учетных данных пользователей.