Что такое argon2-cffi
Библиотека argon2-cffi представляет собой официальную Python-обёртку над алгоритмом хэширования паролей Argon2, реализованную через интерфейс CFFI (C Foreign Function Interface). Argon2 — это современный алгоритм хэширования паролей, который победил в конкурсе Password Hashing Competition в 2015 году и с тех пор считается золотым стандартом для безопасного хранения паролей.
Основное преимущество argon2-cffi заключается в том, что она обеспечивает высокую производительность благодаря C-реализации, при этом предоставляя удобный Python-интерфейс. Библиотека активно поддерживается сообществом и регулярно обновляется в соответствии с последними стандартами безопасности.
Установка и настройка
Стандартная установка
pip install argon2-cffi
Установка с дополнительными зависимостями
pip install argon2-cffi[dev] # для разработки
pip install argon2-cffi[tests] # для тестирования
Проверка установки
import argon2
print(argon2.__version__)
Варианты алгоритма Argon2
Argon2i (Data-Independent)
Argon2i оптимизирован для защиты от атак по сторонним каналам (side-channel attacks). Он обращается к памяти независимо от содержимого пароля, что делает его устойчивым к атакам по времени выполнения. Рекомендуется для сценариев, где важна защита от анализа времени выполнения.
Argon2d (Data-Dependent)
Argon2d обеспечивает максимальную защиту от GPU-атак и атак с использованием специализированного оборудования. Он использует зависимые от данных обращения к памяти, что затрудняет распараллеливание атак. Однако он менее защищён от атак по сторонним каналам.
Argon2id (Hybrid)
Argon2id — это гибридный режим, который сочетает преимущества обоих вариантов. Он используется по умолчанию в argon2-cffi и рекомендуется для большинства применений. Первые проходы выполняются как Argon2i, а остальные — как Argon2d.
Быстрый старт
Базовое использование
from argon2 import PasswordHasher
# Создание экземпляра хэшера
ph = PasswordHasher()
# Хэширование пароля
password = "мой_супер_секретный_пароль"
hash_result = ph.hash(password)
print(f"Хэш: {hash_result}")
# Проверка пароля
try:
ph.verify(hash_result, password)
print("Пароль правильный!")
except Exception as e:
print(f"Пароль неправильный: {e}")
Работа с различными типами данных
from argon2 import PasswordHasher
ph = PasswordHasher()
# Хэширование строки
string_hash = ph.hash("строковый_пароль")
# Хэширование байтов
bytes_password = b"bytes_password"
bytes_hash = ph.hash(bytes_password)
# Проверка
ph.verify(string_hash, "строковый_пароль")
ph.verify(bytes_hash, bytes_password)
Конфигурация PasswordHasher
Параметры конструктора
from argon2 import PasswordHasher, Type
ph = PasswordHasher(
time_cost=3, # Количество итераций (по умолчанию 2)
memory_cost=65536, # Объём памяти в KB (по умолчанию 51200)
parallelism=2, # Количество потоков (по умолчанию 1)
hash_len=32, # Длина хэша в байтах (по умолчанию 32)
salt_len=16, # Длина соли в байтах (по умолчанию 16)
encoding="utf-8", # Кодировка (по умолчанию "utf-8")
type=Type.ID # Тип Argon2 (по умолчанию Type.ID)
)
Влияние параметров на безопасность
time_cost увеличивает время выполнения линейно. Каждое увеличение на 1 удваивает время хэширования.
memory_cost определяет объём памяти в килобайтах. Большие значения затрудняют атаки с использованием специализированного оборудования.
parallelism позволяет использовать несколько потоков для хэширования, что может ускорить процесс на многоядерных системах.
Настройка для различных сценариев
# Для веб-приложений (быстрая проверка)
web_hasher = PasswordHasher(time_cost=2, memory_cost=51200, parallelism=1)
# Для критичных данных (повышенная безопасность)
secure_hasher = PasswordHasher(time_cost=4, memory_cost=102400, parallelism=2)
# Для ограниченных ресурсов (IoT устройства)
iot_hasher = PasswordHasher(time_cost=1, memory_cost=8192, parallelism=1)
Методы и функции библиотеки
Основные методы класса PasswordHasher
| Метод | Описание | Возвращаемый тип |
|---|---|---|
hash(password) |
Создаёт хэш из пароля | str |
verify(hash, password) |
Проверяет соответствие пароля хэшу | bool |
check_needs_rehash(hash) |
Проверяет необходимость обновления хэша | bool |
Низкоуровневые функции
| Функция | Описание | Параметры |
|---|---|---|
hash_secret() |
Прямое хэширование | secret, salt, time_cost, memory_cost, parallelism, hash_len, type |
verify_secret() |
Прямая проверка | hash, secret, type |
hash_secret_raw() |
Хэширование с возвратом raw-данных | Аналогично hash_secret |
Константы и типы
| Константа/Тип | Описание |
|---|---|
Type.I |
Argon2i тип |
Type.D |
Argon2d тип |
Type.ID |
Argon2id тип |
ARGON2_VERSION |
Текущая версия алгоритма |
Валидация и обновление хэшей
Обработка неправильных паролей
from argon2 import PasswordHasher
from argon2.exceptions import VerifyMismatchError, InvalidHash
ph = PasswordHasher()
hash_result = ph.hash("правильный_пароль")
try:
ph.verify(hash_result, "неправильный_пароль")
print("Пароль верен")
except VerifyMismatchError:
print("Неверный пароль")
except InvalidHash:
print("Повреждённый хэш")
Автоматическое обновление хэшей
from argon2 import PasswordHasher
old_hasher = PasswordHasher(time_cost=1, memory_cost=8192)
new_hasher = PasswordHasher(time_cost=3, memory_cost=65536)
# Старый хэш
old_hash = old_hasher.hash("пароль")
# Проверка необходимости обновления
if new_hasher.check_needs_rehash(old_hash):
print("Хэш устарел, требуется обновление")
new_hash = new_hasher.hash("пароль")
print(f"Новый хэш: {new_hash}")
Миграция паролей
def migrate_password(old_hash, password, old_hasher, new_hasher):
"""Функция для миграции паролей на новые параметры"""
try:
# Проверяем старый пароль
old_hasher.verify(old_hash, password)
# Создаём новый хэш
new_hash = new_hasher.hash(password)
return new_hash
except Exception as e:
raise ValueError(f"Не удалось мигрировать пароль: {e}")
Низкоуровневое API
Прямое использование функций
from argon2.low_level import hash_secret, verify_secret, Type
# Хэширование на низком уровне
password = b"секретный_пароль"
salt = b"случайная_соль_16б"
hash_result = hash_secret(
secret=password,
salt=salt,
time_cost=2,
memory_cost=65536,
parallelism=2,
hash_len=32,
type=Type.ID
)
# Проверка на низком уровне
is_valid = verify_secret(
hash=hash_result,
secret=password,
type=Type.ID
)
Работа с сырыми данными
from argon2.low_level import hash_secret_raw
# Получение сырого хэша без кодирования
raw_hash = hash_secret_raw(
secret=b"пароль",
salt=b"соль_16_байт_мин",
time_cost=2,
memory_cost=65536,
parallelism=1,
hash_len=32,
type=Type.ID
)
print(f"Сырой хэш: {raw_hash.hex()}")
Исключения и обработка ошибок
Иерархия исключений
| Исключение | Описание | Родительский класс |
|---|---|---|
VerificationError |
Базовое исключение проверки | Exception |
VerifyMismatchError |
Пароль не соответствует хэшу | VerificationError |
InvalidHash |
Невалидный формат хэша | VerificationError |
HashingError |
Ошибка при создании хэша | Exception |
Комплексная обработка ошибок
from argon2 import PasswordHasher
from argon2.exceptions import (
VerifyMismatchError,
InvalidHash,
HashingError,
VerificationError
)
def safe_password_check(hash_str, password):
"""Безопасная проверка пароля с обработкой всех исключений"""
ph = PasswordHasher()
try:
ph.verify(hash_str, password)
return True, "Пароль верен"
except VerifyMismatchError:
return False, "Неверный пароль"
except InvalidHash:
return False, "Повреждённый хэш"
except VerificationError as e:
return False, f"Ошибка проверки: {e}"
except Exception as e:
return False, f"Неожиданная ошибка: {e}"
Интеграция с популярными фреймворками
FastAPI
from fastapi import FastAPI, HTTPException, Depends
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from argon2 import PasswordHasher
from argon2.exceptions import VerifyMismatchError
app = FastAPI()
security = HTTPBearer()
ph = PasswordHasher()
class AuthService:
def __init__(self):
self.ph = PasswordHasher(time_cost=3, memory_cost=65536)
def hash_password(self, password: str) -> str:
"""Хэширование пароля для сохранения"""
return self.ph.hash(password)
def verify_password(self, password: str, hashed: str) -> bool:
"""Проверка пароля"""
try:
self.ph.verify(hashed, password)
return True
except VerifyMismatchError:
return False
def needs_rehash(self, hashed: str) -> bool:
"""Проверка необходимости обновления хэша"""
return self.ph.check_needs_rehash(hashed)
auth_service = AuthService()
@app.post("/register")
async def register(username: str, password: str):
hashed_password = auth_service.hash_password(password)
# Сохранение в базу данных
return {"message": "Пользователь зарегистрирован"}
@app.post("/login")
async def login(username: str, password: str):
# Получение хэша из базы данных
stored_hash = get_user_hash(username) # Ваша функция
if auth_service.verify_password(password, stored_hash):
# Проверка необходимости обновления
if auth_service.needs_rehash(stored_hash):
new_hash = auth_service.hash_password(password)
update_user_hash(username, new_hash) # Ваша функция
return {"message": "Вход выполнен успешно"}
else:
raise HTTPException(status_code=401, detail="Неверные учётные данные")
Django
from django.contrib.auth.hashers import BasePasswordHasher
from django.utils.crypto import constant_time_compare
from argon2 import PasswordHasher
from argon2.exceptions import VerifyMismatchError
class Argon2PasswordHasher(BasePasswordHasher):
"""Пользовательский хэшер для Django"""
algorithm = "argon2"
def __init__(self):
self.ph = PasswordHasher(
time_cost=2,
memory_cost=51200,
parallelism=1
)
def encode(self, password, salt):
"""Кодирование пароля"""
hash_result = self.ph.hash(password)
return f"{self.algorithm}${hash_result}"
def verify(self, password, encoded):
"""Проверка пароля"""
algorithm, hash_part = encoded.split('$', 1)
assert algorithm == self.algorithm
try:
self.ph.verify(hash_part, password)
return True
except VerifyMismatchError:
return False
def must_update(self, encoded):
"""Проверка необходимости обновления"""
algorithm, hash_part = encoded.split('$', 1)
return self.ph.check_needs_rehash(hash_part)
def safe_summary(self, encoded):
"""Безопасное отображение информации о хэше"""
algorithm, hash_part = encoded.split('$', 1)
return {
'algorithm': algorithm,
'hash': hash_part[:6] + '...',
}
# В settings.py
PASSWORD_HASHERS = [
'myapp.hashers.Argon2PasswordHasher',
'django.contrib.auth.hashers.Argon2PasswordHasher',
'django.contrib.auth.hashers.PBKDF2PasswordHasher',
]
Flask
from flask import Flask, request, jsonify
from argon2 import PasswordHasher
from argon2.exceptions import VerifyMismatchError
app = Flask(__name__)
class PasswordManager:
def __init__(self):
self.ph = PasswordHasher(
time_cost=2,
memory_cost=51200,
parallelism=1
)
def hash_password(self, password):
"""Хэширование пароля"""
return self.ph.hash(password)
def check_password(self, password, hash_value):
"""Проверка пароля"""
try:
self.ph.verify(hash_value, password)
return True
except VerifyMismatchError:
return False
def update_hash_if_needed(self, hash_value, password):
"""Обновление хэша при необходимости"""
if self.ph.check_needs_rehash(hash_value):
return self.hash_password(password)
return hash_value
pwd_manager = PasswordManager()
@app.route('/register', methods=['POST'])
def register():
data = request.get_json()
username = data.get('username')
password = data.get('password')
hashed_password = pwd_manager.hash_password(password)
# Сохранение в базе данных
save_user(username, hashed_password)
return jsonify({'message': 'Пользователь зарегистрирован'})
@app.route('/login', methods=['POST'])
def login():
data = request.get_json()
username = data.get('username')
password = data.get('password')
stored_hash = get_user_hash(username)
if pwd_manager.check_password(password, stored_hash):
# Обновление хэша при необходимости
updated_hash = pwd_manager.update_hash_if_needed(stored_hash, password)
if updated_hash != stored_hash:
update_user_hash(username, updated_hash)
return jsonify({'message': 'Вход выполнен успешно'})
else:
return jsonify({'error': 'Неверные учётные данные'}), 401
Сравнение с другими алгоритмами
Детальное сравнение
| Критерий | Argon2 | bcrypt | PBKDF2 | scrypt |
|---|---|---|---|---|
| Год создания | 2015 | 1999 | 2000 | 2009 |
| Память | Настраиваемая | Фиксированная | Низкая | Настраиваемая |
| Защита от GPU | Отличная | Хорошая | Плохая | Хорошая |
| Защита от ASIC | Отличная | Средняя | Плохая | Хорошая |
| Параллелизм | Да | Нет | Да | Частично |
| Стандартизация | RFC 9106 | OpenBSD | RFC 2898 | RFC 7914 |
| Скорость | Быстрая | Средняя | Быстрая | Средняя |
Практические тесты производительности
import time
from argon2 import PasswordHasher
import hashlib
def benchmark_argon2():
ph = PasswordHasher()
password = "тестовый_пароль"
start = time.time()
hash_result = ph.hash(password)
hash_time = time.time() - start
start = time.time()
ph.verify(hash_result, password)
verify_time = time.time() - start
return hash_time, verify_time
def benchmark_pbkdf2():
password = b"тестовый_пароль"
salt = b"случайная_соль"
start = time.time()
hash_result = hashlib.pbkdf2_hmac('sha256', password, salt, 100000)
hash_time = time.time() - start
start = time.time()
verification = hashlib.pbkdf2_hmac('sha256', password, salt, 100000)
verify_time = time.time() - start
return hash_time, verify_time
# Запуск бенчмарков
argon2_hash, argon2_verify = benchmark_argon2()
pbkdf2_hash, pbkdf2_verify = benchmark_pbkdf2()
print(f"Argon2 - Хэширование: {argon2_hash:.4f}с, Проверка: {argon2_verify:.4f}с")
print(f"PBKDF2 - Хэширование: {pbkdf2_hash:.4f}с, Проверка: {pbkdf2_verify:.4f}с")
Безопасность и лучшие практики
Выбор параметров безопасности
from argon2 import PasswordHasher
# Для веб-приложений (баланс безопасности и производительности)
web_hasher = PasswordHasher(
time_cost=2, # 2 итерации
memory_cost=51200, # 50 MB памяти
parallelism=1, # 1 поток
hash_len=32, # 32 байта хэш
salt_len=16 # 16 байт соль
)
# Для критически важных данных
critical_hasher = PasswordHasher(
time_cost=4, # 4 итерации
memory_cost=102400, # 100 MB памяти
parallelism=2, # 2 потока
hash_len=64, # 64 байта хэш
salt_len=32 # 32 байта соль
)
# Для мобильных приложений (ограниченные ресурсы)
mobile_hasher = PasswordHasher(
time_cost=1, # 1 итерация
memory_cost=8192, # 8 MB памяти
parallelism=1, # 1 поток
hash_len=32, # 32 байта хэш
salt_len=16 # 16 байт соль
)
Рекомендации по безопасности
Никогда не используйте одинаковую соль для разных паролей. Argon2-cffi автоматически генерирует уникальную соль для каждого хэша.
Регулярно обновляйте параметры хэширования по мере роста вычислительных мощностей.
Не передавайте хэши клиенту — вся проверка должна происходить на сервере.
Используйте HTTPS для передачи паролей по сети.
Логируйте попытки входа для обнаружения атак.
Защита от атак
import time
from collections import defaultdict
from argon2 import PasswordHasher
from argon2.exceptions import VerifyMismatchError
class SecurePasswordManager:
def __init__(self):
self.ph = PasswordHasher()
self.failed_attempts = defaultdict(int)
self.lockout_time = defaultdict(float)
def is_locked_out(self, username):
"""Проверка блокировки пользователя"""
if username in self.lockout_time:
if time.time() - self.lockout_time[username] < 300: # 5 минут
return True
else:
del self.lockout_time[username]
self.failed_attempts[username] = 0
return False
def verify_password(self, username, password, stored_hash):
"""Безопасная проверка пароля с защитой от брутфорса"""
if self.is_locked_out(username):
return False, "Аккаунт временно заблокирован"
try:
self.ph.verify(stored_hash, password)
# Сброс счётчика при успешном входе
self.failed_attempts[username] = 0
return True, "Пароль верен"
except VerifyMismatchError:
self.failed_attempts[username] += 1
if self.failed_attempts[username] >= 5:
self.lockout_time[username] = time.time()
return False, "Превышено количество попыток. Аккаунт заблокирован."
return False, "Неверный пароль"
Практические применения
Система аутентификации для API
import jwt
from datetime import datetime, timedelta
from argon2 import PasswordHasher
class APIAuthSystem:
def __init__(self, secret_key):
self.ph = PasswordHasher()
self.secret_key = secret_key
def register_user(self, username, password, email):
"""Регистрация нового пользователя"""
hashed_password = self.ph.hash(password)
user_data = {
'username': username,
'password_hash': hashed_password,
'email': email,
'created_at': datetime.utcnow().isoformat()
}
# Сохранение в базу данных
save_user_to_db(user_data)
return True
def authenticate_user(self, username, password):
"""Аутентификация пользователя"""
user_data = get_user_from_db(username)
if not user_data:
return None
try:
self.ph.verify(user_data['password_hash'], password)
# Проверка необходимости обновления хэша
if self.ph.check_needs_rehash(user_data['password_hash']):
new_hash = self.ph.hash(password)
update_user_hash(username, new_hash)
return user_data
except Exception:
return None
def generate_token(self, user_data):
"""Генерация JWT токена"""
payload = {
'user_id': user_data['id'],
'username': user_data['username'],
'exp': datetime.utcnow() + timedelta(hours=24)
}
token = jwt.encode(payload, self.secret_key, algorithm='HS256')
return token
Система двухфакторной аутентификации
import pyotp
from argon2 import PasswordHasher
class TwoFactorAuthSystem:
def __init__(self):
self.ph = PasswordHasher()
def setup_2fa(self, username, password, stored_hash):
"""Настройка двухфакторной аутентификации"""
try:
# Проверка пароля
self.ph.verify(stored_hash, password)
# Генерация секретного ключа для TOTP
secret = pyotp.random_base32()
# Создание QR-кода для Google Authenticator
totp = pyotp.TOTP(secret)
qr_url = totp.provisioning_uri(
username,
issuer_name="My App"
)
return {
'secret': secret,
'qr_url': qr_url,
'backup_codes': self.generate_backup_codes()
}
except Exception:
return None
def verify_2fa(self, username, password, totp_code, stored_hash, totp_secret):
"""Проверка с двухфакторной аутентификацией"""
try:
# Проверка пароля
self.ph.verify(stored_hash, password)
# Проверка TOTP кода
totp = pyotp.TOTP(totp_secret)
if totp.verify(totp_code, valid_window=1):
return True
return False
except Exception:
return False
def generate_backup_codes(self):
"""Генерация резервных кодов"""
import secrets
return [secrets.token_hex(4) for _ in range(10)]
Тестирование и отладка
Модульные тесты
import unittest
from argon2 import PasswordHasher
from argon2.exceptions import VerifyMismatchError, InvalidHash
class TestPasswordHashing(unittest.TestCase):
def setUp(self):
self.ph = PasswordHasher()
self.test_password = "тестовый_пароль_123"
def test_hash_and_verify(self):
"""Тест хэширования и проверки"""
hash_result = self.ph.hash(self.test_password)
self.assertTrue(self.ph.verify(hash_result, self.test_password))
def test_wrong_password(self):
"""Тест неправильного пароля"""
hash_result = self.ph.hash(self.test_password)
with self.assertRaises(VerifyMismatchError):
self.ph.verify(hash_result, "неправильный_пароль")
def test_hash_format(self):
"""Тест формата хэша"""
hash_result = self.ph.hash(self.test_password)
self.assertTrue(hash_result.startswith("$argon2id$"))
def test_different_hashes(self):
"""Тест различных хэшей для одного пароля"""
hash1 = self.ph.hash(self.test_password)
hash2 = self.ph.hash(self.test_password)
self.assertNotEqual(hash1, hash2)
def test_needs_rehash(self):
"""Тест проверки необходимости обновления"""
old_hasher = PasswordHasher(time_cost=1)
new_hasher = PasswordHasher(time_cost=2)
old_hash = old_hasher.hash(self.test_password)
self.assertTrue(new_hasher.check_needs_rehash(old_hash))
def test_invalid_hash(self):
"""Тест невалидного хэша"""
with self.assertRaises(InvalidHash):
self.ph.verify("invalid_hash", self.test_password)
if __name__ == '__main__':
unittest.main()
Нагрузочное тестирование
import time
import threading
from concurrent.futures import ThreadPoolExecutor
from argon2 import PasswordHasher
class LoadTester:
def __init__(self, num_threads=10, num_operations=100):
self.ph = PasswordHasher()
self.num_threads = num_threads
self.num_operations = num_operations
self.results = []
def hash_operation(self, password):
"""Операция хэширования"""
start_time = time.time()
hash_result = self.ph.hash(password)
hash_time = time.time() - start_time
start_time = time.time()
self.ph.verify(hash_result, password)
verify_time = time.time() - start_time
return hash_time, verify_time
def run_load_test(self):
"""Запуск нагрузочного теста"""
passwords = [f"password_{i}" for i in range(self.num_operations)]
with ThreadPoolExecutor(max_workers=self.num_threads) as executor:
futures = [executor.submit(self.hash_operation, pwd) for pwd in passwords]
for future in futures:
hash_time, verify_time = future.result()
self.results.append((hash_time, verify_time))
return self.analyze_results()
def analyze_results(self):
"""Анализ результатов тестирования"""
hash_times = [r[0] for r in self.results]
verify_times = [r[1] for r in self.results]
return {
'total_operations': len(self.results),
'avg_hash_time': sum(hash_times) / len(hash_times),
'avg_verify_time': sum(verify_times) / len(verify_times),
'max_hash_time': max(hash_times),
'max_verify_time': max(verify_times),
'min_hash_time': min(hash_times),
'min_verify_time': min(verify_times)
}
# Запуск тестирования
tester = LoadTester(num_threads=5, num_operations=50)
results = tester.run_load_test()
print(f"Результаты нагрузочного тестирования: {results}")
Часто задаваемые вопросы
Как выбрать правильные параметры для моего приложения?
Параметры зависят от ваших требований к безопасности и производительности. Для веб-приложений рекомендуется начать с time_cost=2, memory_cost=51200 и настроить под свои нужды. Целевое время хэширования должно быть 0.5-2 секунды.
Можно ли изменить параметры хэширования после развёртывания?
Да, используйте метод check_needs_rehash() для проверки необходимости обновления существующих хэшей. Обновляйте хэши при следующем входе пользователя.
Безопасно ли хранить хэши в базе данных?
Да, хэши Argon2 спроектированы для безопасного хранения. Они содержат соль и параметры, необходимые для проверки. Однако обеспечьте защиту базы данных от несанкционированного доступа.
Как обрабатывать ошибки при хэшировании?
Используйте блоки try-except для обработки специфических исключений argon2-cffi. Логируйте ошибки для мониторинга, но не раскрывайте детали пользователям.
Влияет ли количество потоков на безопасность?
Параметр parallelism влияет на производительность, но не на уровень безопасности. Больше потоков может ускорить хэширование на многоядерных системах, но увеличивает потребление ресурсов.
Можно ли использовать argon2-cffi для хэширования данных, не являющихся паролями?
Технически возможно, но Argon2 оптимизирован специально для паролей. Для других типов данных рассмотрите использование криптографических хэш-функций типа SHA-256.
Как обеспечить совместимость между различными версиями библиотеки?
Argon2-cffi использует стандартный формат хэшей, совместимый между версиями. Однако рекомендуется использовать одинаковые версии в разных частях системы.
Что делать, если хэширование работает слишком медленно?
Уменьшите параметры time_cost и memory_cost, но помните, что это снижает безопасность. Найдите баланс между производительностью и защитой для вашего конкретного случая использования.
Заключение
Библиотека argon2-cffi представляет собой надёжное и современное решение для хэширования паролей в Python-приложениях. Она сочетает в себе высокий уровень безопасности, гибкость настройки и простоту использования.
Ключевые преимущества argon2-cffi включают защиту от современных атак, настраиваемые параметры безопасности, официальную поддержку и широкую совместимость с популярными фреймворками. Библиотека подходит для проектов любого масштаба — от небольших веб-приложений до крупных корпоративных систем.
Правильная настройка параметров, соблюдение лучших практик безопасности и регулярное обновление хэшей обеспечат надёжную защиту пользовательских данных на долгие годы. Argon2-cffi — это инвестиция в безопасность вашего приложения, которая окупается защитой от утечек данных и атак на аутентификацию.
Настоящее и будущее развития ИИ: классической математики уже недостаточно
Эксперты предупредили о рисках фейковой благотворительности с помощью ИИ
В России разработали универсального ИИ-агента для роботов и индустриальных процессов