Безопасное хранение секретных ключей в модели Django (версия Fernet)
Безопасность – ответственность разработчика – хранить открытый текст в БД нельзя.
1. Зачем нужна шифрация?
- API Key / Secret / OAuth токен – при утечке это как открытая дверь для всей службы.
- Хардкод в
settings.py→ Git‑репозиторий, артефакты развертывания, ноутбук разработчика – всё это может раскрыть данные. - Даже если хранить в переменных окружения, запись в БД без шифрации приводит к открытию в момент утечки БД.
Важно различать:
- Пароль – серверу не нужен открытый текст → хеш (BCrypt, Argon2).
- API Key / Secret / токен – серверу нужен открытый текст → шифрование с возможностью расшифровки.
Итог
- Данные, которые нужно использовать позже → шифруем и сохраняем.
- Данные, которые не нужно использовать (пароли) → хешируем.
2. Что такое Fernet?
cryptography.fernet.Fernet – высокоуровневый API, который всё делает за вас:
- Внутренне использует надёжные алгоритмы (AES + HMAC).
- Автоматически генерирует случайный IV, таймстамп, проверку целостности.
- Разработчик просто хранит один ключ и вызывает:
encrypt(plaintext)→ токен‑строкаdecrypt(token)→ открытый текст
from cryptography.fernet import Fernet
key = Fernet.generate_key() # генерация ключа
f = Fernet(key)
token = f.encrypt(b"hello") # шифрование
plain = f.decrypt(token) # расшифровка
Что нужно сделать:
- Управление ключом (переменные окружения, Secret Manager и т.д.)
- В Django автоматически шифровать/расшифровывать при чтении/записи.
3. Генерация ключа Fernet и настройка Django
3‑1. Генерация ключа
# Генерация одного ключа Fernet (Base64‑строка)
python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
Пример вывода:
twwhe-3rGfW92V5rvvW3o4Jhr0rVg7x8lQM6zCj6lTY=
Сохраняем в переменную окружения сервера.
export DJANGO_FERNET_KEY="twwhe-3rGfW92V5rvvW3o4Jhr0rVg7x8lQM6zCj6lTY="
3‑2. Подготовка экземпляра Fernet в settings.py
# settings.py
import os
from cryptography.fernet import Fernet
DJANGO_FERNET_KEY = os.environ["DJANGO_FERNET_KEY"] # строка
FERNET = Fernet(DJANGO_FERNET_KEY.encode("utf-8"))
Теперь в любом месте проекта можно импортировать settings и вызывать settings.FERNET.encrypt(...) / settings.FERNET.decrypt(...).
4. Добавление поля Fernet‑шифрования в модель Django
Нужно только один простой пользовательский тип поля.
4‑1. Основные идеи
- В БД хранится токен‑строка.
- В коде всегда виден открытый текст.
- Паттерн
_secret+secretразделяет имя колонки и логическое поле.
4‑2. Реализация EncryptedTextField
# myapp/utils/encrypted_field.py
from django.db import models
from django.conf import settings
class EncryptedTextField(models.TextField):
"""Fernet‑шифрованное поле TextField."""
description = "Fernet encrypted text field"
def get_prep_value(self, value):
if value is None:
return None
if isinstance(value, str):
if value == "":
return ""
token = settings.FERNET.encrypt(value.encode("utf-8"))
return token.decode("utf-8")
return value
def from_db_value(self, value, expression, connection):
if value is None:
return None
try:
token = value.encode("utf-8")
decrypted = settings.FERNET.decrypt(token)
return decrypted.decode("utf-8")
except Exception:
return value
def to_python(self, value):
return value
Практический совет
Если в БД уже есть открытые данные,
from_db_valueможет вернуть их как есть, а при последующем чтении‑записи они будут автоматически зашифрованы.
5. Применение в модели
# myapp/models.py
from django.db import models
from .utils.encrypted_field import EncryptedTextField
class MyModel(models.Model):
name = models.CharField(max_length=100)
_secret = EncryptedTextField() # реальная колонка в БД (токен)
@property
def secret(self):
return self._secret
@secret.setter
def secret(self, value: str):
self._secret = value
_secret– имя колонки в БД, хранит токен.secret– логическое поле, всегда открытый текст.
6. Примеры использования
6‑1. Django shell
>>> from myapp.models import MyModel
# Создание объекта
>>> obj = MyModel(name="Test")
>>> obj.secret = "my-very-secret-key"
>>> obj.save()
# В БД хранится токен
>>> MyModel.objects.get(id=obj.id)._secret
'gAAAAABm...4xO8Uu1f0L3K0Ty6fX5y6Zt3_k6D7eE-'
# В коде виден открытый текст
>>> obj.secret
'my-very-secret-key'
6‑2. В API
# myapp/views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from .models import MyModel
class SecretView(APIView):
def get(self, request, pk):
obj = MyModel.objects.get(pk=pk)
return Response({"secret": obj.secret})
В реальной работе обычно obj.secret используется только внутри сервера, а клиенту не передаётся.
7. Что важно знать о Fernet
7‑1. Ограничения поиска/фильтрации
Fernet генерирует разные токены для одинакового текста, поэтому:
obj1.secret = "abc"
obj2.secret = "abc"
_secret в БД будет разными, и запросы по открытым значениям невозможны.
Если нужна поиск/сортировка по открытым данным, добавьте отдельный хеш‑или‑префикс‑поле или пересмотрите архитектуру.
7‑2. Ротация ключей
Не держите один ключ навсегда. Стратегия:
DJANGO_FERNET_KEY– основной ключ.- При смене ключа создайте
OLD_KEYиNEW_KEY. - При шифровании используйте только
NEW_KEY. - При расшифровке пробуйте
OLD_KEYиNEW_KEY. - Постепенно обновляйте данные до
NEW_KEYи удаляйтеOLD_KEY.
8. Чеклист безопасности (Fernet)
| Пункт | Проверка |
|---|---|
| Управление ключом | Не хранить в Git, загружать из Secrets Manager, Parameter Store, Vault и т.д. |
| Разделение прав | Ключ доступен только на продакшн‑сервере, в dev/локале – другой ключ |
| Шифрование только нужных данных | API‑ключи, токены – шифруем; пароли – хешируем |
| Логи | Никогда не выводить открытый текст, токены, ключи в логи (особенно DEBUG) |
| Резервные копии | Без ключа резервные копии БД бесполезны |
| Тесты | encrypt -> decrypt совпадает, миграции с открытыми данными работают |
9. Итоги
Низкоуровневый AES‑код писать сложно и рискованно. Используя Fernet:
- Вы получаете надёжный набор алгоритмов.
- Автоматически обрабатываются IV, таймстамп, HMAC.
- API прост и безопасен.
В Django достаточно EncryptedTextField + паттерна _secret/secret, чтобы разработчики работали только с открытым текстом, а в БД всегда хранились зашифрованные данные.
Вывод
- Секретные данные всегда шифруются.
- Сложные алгоритмы доверяйте библиотекам. Это повышает уровень безопасности вашего Django‑сервиса.

Комментариев нет.