Введение
Многофакторная аутентификация (MFA) снижает риск компрометации учетной записи, требуя более одного типа подтверждения личности. Пароль — это то, что пользователь знает. Вторым фактором может быть код, полученный с отдельного устройства, приложения, файла или аппаратного токена.
В этой лабораторной работе вы начнете с Python-скрипта, использующего только пароль, а затем добавите простой второй фактор. Чтобы лабораторная работа была понятной для новичков и детерминированной, в качестве второго фактора будет использоваться HMAC-код, генерируемый на основе защищенного секретного файла.
Тестирование входа только по паролю
На этом этапе вы запустите исходный скрипт входа. Он проверяет только имя пользователя и пароль, что является однофакторной аутентификацией, так как единственным доказательством является то, что пользователь знает.
Перейдите в директорию проекта:
cd /home/labex/project
Изучите подготовленные файлы:
ls -l
Просмотрите базу данных пользователей:
cat users.json
Выполните успешный вход только по паролю:
python3 login.py alice wonderland
Вывод должен быть таким:
login ok
Теперь попробуйте ввести неверный пароль:
python3 login.py alice wrong
Эта команда должна вывести login failed. Неудачный вход завершается с ненулевым кодом состояния, что является нормальным для этого теста.
Запустите проверку для начального состояния:
python3 mfa-check.py password-only
Ожидаемый результат:
password-only: complete
Создание секрета для второго фактора
На этом этапе вы создадите отдельный секрет для второго фактора. В реальной системе MFA этот секрет может храниться в приложении-аутентификаторе или на аппаратном токене. В этой лабораторной работе это локальный файл, поэтому поведение легко проверить и проконтролировать.
Создайте секретное значение:
printf 'labex-mfa-secret-2026\n' > mfa_secret.txt
Ограничьте права доступа к файлу, чтобы только владелец мог читать и записывать его:
chmod 600 mfa_secret.txt
Проверьте права доступа:
ls -l mfa_secret.txt
Биты прав доступа должны начинаться с -rw-------:
-rw------- ... mfa_secret.txt
Запустите проверку для секрета:
python3 mfa-check.py secret
Ожидаемый результат:
secret: complete
Генерация кода для входа
На этом этапе вы создадите небольшой генератор кодов. Он использует HMAC-SHA256 для объединения секретного файла с именем пользователя. HMAC здесь полезен тем, что одни и те же входные данные и секрет создают один и тот же короткий код, но злоумышленник без секрета не сможет легко его подобрать.
Создайте generate-code.py:
cat > generate-code.py <<'PY'
#!/usr/bin/env python3
import hmac
import hashlib
import sys
from pathlib import Path
if len(sys.argv) != 2:
print("usage: generate-code.py USERNAME")
raise SystemExit(2)
username = sys.argv[1]
secret = Path("mfa_secret.txt").read_text().strip().encode()
digest = hmac.new(secret, username.encode(), hashlib.sha256).hexdigest()
print(digest[:6])
PY
Сделайте его исполняемым:
chmod +x generate-code.py
Сгенерируйте код для alice:
python3 generate-code.py alice
Вы должны увидеть этот детерминированный шестизначный код:
21f18f
Запустите проверку для генератора:
python3 mfa-check.py generator
Ожидаемый результат:
generator: complete
Принудительное использование второго фактора
На этом этапе вы замените скрипт входа так, чтобы он требовал и пароль, и сгенерированный код. Это ключевое изменение для MFA: одного пароля теперь недостаточно.
Замените login.py на эту версию с поддержкой MFA:
cat > login.py <<'PY'
#!/usr/bin/env python3
import hmac
import hashlib
import json
import sys
from pathlib import Path
USERS = Path("users.json")
SECRET = Path("mfa_secret.txt")
def expected_code(username):
secret = SECRET.read_text().strip().encode()
digest = hmac.new(secret, username.encode(), hashlib.sha256).hexdigest()
return digest[:6]
def main():
if len(sys.argv) != 4:
print("usage: login.py USERNAME PASSWORD MFA_CODE")
return 2
username, password, code = sys.argv[1], sys.argv[2], sys.argv[3]
users = json.loads(USERS.read_text())
if users.get(username, {}).get("password") != password:
print("login failed")
return 1
if not hmac.compare_digest(code, expected_code(username)):
print("mfa failed")
return 1
print("login ok")
return 0
if __name__ == "__main__":
raise SystemExit(main())
PY
Убедитесь, что скрипт исполняемый:
chmod +x login.py
Сгенерируйте новый код и сохраните его в переменной оболочки:
CODE="$(python3 generate-code.py alice)"
Используйте правильный пароль и код вместе:
python3 login.py alice wonderland "$CODE"
Вывод должен быть таким:
login ok
Запустите проверку для принудительного использования MFA:
python3 mfa-check.py enforced
Ожидаемый результат:
enforced: complete
Тестирование сбоев и успешного входа с MFA
На этом этапе вы протестируете итоговое поведение входа. Правильный пароль без второго фактора должен приводить к ошибке, неверный код — к ошибке, а правильный пароль вместе с правильным кодом — к успешному входу.
Сначала попробуйте старую команду только с паролем:
python3 login.py alice wonderland || true
Вывод должен показать новое сообщение об использовании, так как код MFA отсутствует:
usage: login.py USERNAME PASSWORD MFA_CODE
Попробуйте ввести неверный код:
python3 login.py alice wonderland 000000 || true
Вывод должен быть таким:
mfa failed
Сгенерируйте правильный код и войдите в систему:
CODE="$(python3 generate-code.py alice)"
python3 login.py alice wonderland "$CODE"
Вывод должен быть таким:
login ok
Запустите полную проверку:
python3 mfa-check.py all
Итоговый вывод должен показать, что все компоненты MFA завершены:
secret: complete
generator: complete
enforced: complete
Резюме
Вы превратили скрипт входа, работающий только по паролю, в простой процесс многофакторной аутентификации. Вы создали отдельный секрет MFA, сгенерировали HMAC-код, потребовали этот код в скрипте входа и протестировали как случаи сбоя, так и успешного входа.
Основная идея безопасности заключается в том, что украденного пароля недостаточно, если требуется независимое второе доказательство подлинности.



