Безопасное управление паролями Python: хеширование и шифрование
При создании приложения, которое проверяет пароли пользователей или требует хранения токенов для будущего использования, крайне важно не хранить эти значения где-либо в виде открытого текста. Если есть нарушение безопасности, вы хотите быть уверены, что данные вашего пользователя защищены. Хеширование и шифрование — это несколько методов, которые можно использовать для достижения этой цели, и мы рассмотрим, как реализовать их с помощью Python.
Хеширование
Если вашему приложению необходимо разрешить пользователям регистрировать учетную запись и создавать пароль, вам необходимо сохранить значения, с которыми они подписались, чтобы позже аутентифицировать их. Вместо хранения паролей в открытом виде следует использовать алгоритм хеширования. Алгоритмы хеширования представляют собой односторонние функции, которые дают один и тот же результат для входных данных, но, учитывая, что выходные данные практически невозможно обратить вспять. Существует много типов хеш-алгоритмов, но SHA-256 — это надежный современный алгоритм, одобренный NIST, который соответствует потребностям большинства приложений с точки зрения надежности и производительности.
Создайте простой файл сценария Python, который будет принимать входные данные и генерировать хэш SHA-256 с помощью стандартной библиотеки hashlib.
import hashlib
password = input("Password: ")
password_hash = hashlib.sha256(password.encode("utf-8")).hexdigest()
print(f"Password Hash: {password_hash}")
Запустите скрипт и введите несколько паролей.
$ python3 hash.py
Password: test123
Password Hash: ecd71870d1963316a97e3ac3408c9835ad8cf0f3c1bc703527c30265534f75ae
$ python3 hash.py
Password: test123
Password Hash: ecd71870d1963316a97e3ac3408c9835ad8cf0f3c1bc703527c30265534f75ae
$ python3 hash.py
Password: test1234
Password Hash: 937e8d5fbb48bd4949536cd65b8d35c426b80d2f830c5c308e2cdec422ae2244
Мы видим, что одни и те же входные данные дают один и тот же результат хеширования, но любое изменение, например добавление дополнительного символа, полностью меняет его. Полученное хэш-значение — это то, что вы должны сохранить в своей базе данных, чтобы позже проверить пароль пользователя.
Шифрование
Хеширование — отличный вариант, когда вам не нужно использовать значение пароля. В случаях, когда вам необходимо использовать фактическое значение пароля, например, для хранения токена долгосрочного доступа для аутентификации от имени пользователя во внешнем приложении, лучшим вариантом является шифрование. Шифрование позволяет безопасно хранить значения и расшифровывать их в памяти, когда вам нужно их использовать. Для этого в Python есть библиотека криптографии, которая включает в себя симметричное шифрование Fernet. Симметричное шифрование означает, что у нас будет секретный ключ, который мы сможем хранить в переменных среды и использовать для расшифровки сохраненных значений.
Установите библиотеку cryptography
и dotenv
.
$ poetry add cryptography python-dotenv
Using version ^41.0.5 for cryptography
Using version ^1.0.0 for python-dotenv
Updating dependencies
Resolving dependencies... (0.1s)
Package operations: 4 installs, 0 updates, 0 removals
• Installing pycparser (2.21)
• Installing cffi (1.16.0)
• Installing cryptography (41.0.5)
• Installing python-dotenv (1.0.0)
Запустите встроенную команду Python, чтобы сгенерировать секретный ключ Fernet.
$ python3 -c "from cryptography.fernet import Fernet; print(Fernet.generate_key())"
b'XvYvP_c4gBDLCLbjgz6Hc47ND_BcoMYt3Cz5pAKx1qQ='
Добавьте это значение в .env
файл.
SECRET_KEY=XvYvP_c4gBDLCLbjgz6Hc47ND_BcoMYt3Cz5pAKx1qQ=
Создайте новый скрипт Python, который загрузит наш секретный ключ из переменных среды, создаст экземпляр клиента Fernet с этим ключом и позволит зашифровать и сохранить новый пароль в простом текстовом файле или распечатать расшифрованное значение существующего сохраненного пароля.
import os
import sys
from cryptography.fernet import Fernet
from dotenv import load_dotenv
load_dotenv()
SECRET_KEY = os.getenv("SECRET_KEY")
assert SECRET_KEY
FERNET = Fernet(SECRET_KEY)
if len(sys.argv) > 1 and sys.argv[1] == "decrypt":
with open("pw.txt") as f:
stored_password = f.read()
stored_dec_password = FERNET.decrypt(stored_password).decode()
print(f"Decrypted Password: {stored_dec_password}")
else:
new_password = input("New Password: ")
new_enc_password = FERNET.encrypt(new_password.encode()).decode()
with open("pw.txt", "w") as f:
f.write(new_enc_password)
print(f"Encrypted Password Stored: {new_enc_password}")
Проверьте его, чтобы убедиться, что он работает должным образом.
$ python3 encrypt.py
New Password: Test123!!
Encrypted Password Stored: gAAAAABlR7V0TLTZMT_ZHEoPtqbW3B9LYgohYdUNG6Lukx9M2NSLgrFN6MUZKCNPP3Hq_KuuEPpJPPqqIktUkZTBh3qenKnQAA==
$ python3 encrypt.py decrypt
Decrypted Password: Test123!!
Здесь показаны основные концепции шифрования/дешифрования значений в производственной среде: вместо хранения их в простом текстовом файле вы просто сохраняете и извлекаете значения из базы данных.