Шифрование на уровне полей: что шифровать, ключи, миграции
Шифрование на уровне полей защищает чувствительные поля, сохраняя работоспособность приложения. Узнайте, что шифровать, как управлять ключами и безопасно мигрировать данные.

Какую проблему на самом деле решает шифрование на уровне полей
Если кто‑то получает копию вашей базы данных, он может прочитать всё, что хранится в открытом виде. Это может произойти из‑за неправильно настроенной резервной копии, украденного ноутбука с дампом, уязвимого админ‑инструмента или бага, который сливает данные. В таких случаях ущерб — это не просто «нас взломали», а «каждая запись пользователя читаема».
Шифрование на уровне полей уменьшает радиус поражения. Вместо того чтобы полагаться только на защиту диска или сети, вы защищаете отдельные значения в базе, чтобы они были нечитаемы без нужного ключа. Если атакующий получает строки таблицы, зашифрованные поля выглядят как набор бессмысленных байт.
Полезно разделять уровни защиты:
- TLS защищает данные в пути между приложением и базой.
- Полное шифрование диска защищает устройство хранения, когда оно офлайн.
- Шифрование на уровне полей защищает отдельные столбцы даже если содержимое базы скопировано.
Компромисс реален: после шифрования поле нельзя просто так искать, сортировать или группировать. Отчёты, фильтры и функции «найти пользователя по X» могут потребовать изменений. Многие команды оставляют ограниченное значение для поиска (например, ключёный хеш email), чтобы сохранить общие запросы без хранения исходного значения в открытом виде.
Обычно шифрование на уровне полей преследует несколько конкретных целей: уменьшить то, что покажет слитый дамп, соответствовать ожиданиям приватности для чувствительных данных, сократить вред от утечки (кража личности, целевые мошенничества) и снизить риск внутреннего доступа, особенно в общих или неаккуратных кодовых базах.
Решение, что шифровать (а что нет)
Шифрование на уровне полей оправдано только для данных, утрата которых причинит реальный вред. Простое определение «чувствительного» — если кто‑то получил копию вашей базы, что поможет им украсть деньги, захватить аккаунты, шантажировать пользователей или нарушить обещания приватности?
Начните с списка полей, которые действительно опасно хранить в открытом виде. Типичные примеры: государственные идентификаторы (например, SSN), дата рождения, API‑токены, коды восстановления пароля, приватные заметки и всё, что позволит атакующему действовать от имени пользователя или получить доступ к стороннему сервису.
Многие поля обычно не требуют шифрования, потому что они нужны для базовой работы и сами по себе имеют низкий риск: внутренние ID и внешние ключи, метки времени (created_at, last_login), булевы флаги и статусы, публичные поля профиля (display name, bio) и не чувствительная метаинформация для сортировки и фильтрации.
Дальше решите, кто должен иметь доступ к открытому тексту. Некоторые поля должны быть читаемы только приложением во время выполнения (например, API‑токен для вызова провайдера). Другие — с tightly controlled доступом для поддержки (паттерн «показать последние 4 цифры»). А часть должна быть читаема только самим пользователем, что часто означает шифрование и ограничение мест, где происходит расшифровка.
Обязуйтесь начинать с малого. Шифрование «всего про запас» ломает поиск, отчёты и интеграции, и усложняет миграции позже.
Выбор подхода, подходящего для вашего приложения
Нет единственного «лучшего» способа делать шифрование на уровне полей. Правильный подход зависит от того, что приложению нужно делать с данными после защиты: поиск, экспорт, инструменты поддержки и аудит.
Если вам нужно только читать значение (например, показать пользователю сохранённый налоговый ID), используйте случайное (неди детерминированное) шифрование. Оно безопаснее, потому что одинаковый ввод не даёт одинакового шифротекста. Минус — невозможность точных запросов по зашифрованному столбцу.
Если нужны точные поиски (например, «найти пользователя по SSN» или «обнаружить дублирование номера счёта»), детерминированное шифрование выглядит привлекательно, потому что поддерживает сравнение на равенство. Но оно раскрывает шаблоны (одно и то же значение — один и тот же шифротекст). Безопаснее во многих случаях хранить зашифрованное значение и отдельный ключёный хеш для поиска.
Используйте аутентифицированное шифрование, а не «только шифрование». Без аутентификации приложение может не заметить подмену. В режиме AEAD приложение увидит, если шифротекст был изменён.
Для управления ключами оболочечное шифрование часто является практичным компромиссом: вы шифруете поле ключом данных (DEK), а затем «оборачиваете» этот DEK мастер‑ключом (KEK). Это можно делать на каждую запись или на арендатора. Ключи по арендаторам уменьшают радиус поражения в мульти‑тенантных приложениях и упрощают отписку.
Когда вам не нужен открытый текст, не шифруйте — хешируйте. Пароли — классический пример: храните медленный парольный хеш, а не зашифрованный пароль.
Токенизация помогает, когда часть рабочего процесса не может работать с шифротекстом (устаревшие инструменты, панели поддержки, сторонние экспорты). Вы заменяете чувствительное значение токеном и храните реальное значение в отдельном, защищённом хранилище.
Короткая шпаргалка:
- Нужно показывать значение позже: случайное шифрование с аутентификацией.
- Нужен точный поиск: зашифрованное значение + ключёный хеш для индекса (или детерминированное шифрование с осторожностью).
- Нужна изоляция арендаторов: оболочечное шифрование с ключами по арендаторам.
- Открытый текст не нужен никогда: хеширование.
- Рабочие процессы ломаются из‑за шифротекста: токенизация.
Основы управления ключами без жаргона
Шифрование на уровне полей безопасно ровно на сколько защищены ваши ключи. Цель проста: приложение должно уметь расшифровывать, когда это действительно нужно, но ключи хранятся отдельно с жёсткими правилами доступа и учётом операций.
Полезная мысленная модель — «разделение ролей». База хранит шифротекст (заблокированные данные). Приложение запрашивает шифрование/расшифровку, когда это разрешено. Система ключей охраняет ключи и решает, кто и когда может их использовать.
Где должны жить ключи
Для большинства команд самый безопасный выбор по умолчанию — управляемый сервис ключей, а не самодельное решение. Распространённые опции: облачный KMS, сервис с поддержкой HSM или менеджер секретов, который выдаёт ключи во время выполнения.
Избегайте частых ошибок:
- Не храните ключи шифрования в базе.
- Не коммитьте ключи в репозиторий.
- Не держите ключи в общих файлах окружения, доступных многим людям и системам.
- Не используйте один и тот же ключ в dev, staging и production.
Практический пример доступа: если инструмент поддержки показывает замаскированные профили, он не должен иметь права расшифровывать полные значения. Только основной API, обслуживающий аутентифицированных пользователей, должен иметь право расшифровки в production.
Доступ и логи (чтобы можно было доказать, что произошло)
Доступ к ключам должен быть явным и минимальным. Определите, какие сервисы могут расшифровывать какие поля, в каких окружениях и под каким идентификатором (сервисный аккаунт или роль). Если фоновой задаче нужно только шифровать при записи, ей, возможно, не нужны права на расшифровку.
Планируйте аудит заранее. Вам нужны логи использования ключей, которые отвечают на вопросы «кто использовал какой ключ, когда и откуда». Это облегчает расследования и помогает заметить ошибки вроде тестового сервиса, обращающегося к продакшен‑ключам.
Ротация ключей и версионирование, которые понадобятся позже
Шифрование на уровне полей — это не «поставил и забыл». Планируйте ротацию ключей заранее, иначе окажетесь с рискованными ключами, которые нельзя сменить без даунтайма.
Сначала решите, какая единица ключей вам подходит. Один ключ для всего приложения — проще, но увеличивает масштаб инцидента. Ключи по арендаторам уменьшают радиус поражения для SaaS. Ключи по пользователям делают правила доступа яснее, но усложняют совместный доступ к данным. Ключи по записям редки и нужны только по серьёзной причине.
Что бы вы ни выбрали, храните идентификатор ключа с каждым зашифрованным значением. Это может быть короткая метка версии (например v3) или ID ключа. Важно, чтобы при расшифровке можно было посмотреть на шифротекст, понять, какая версия ключа использована, и выбрать нужный ключ без догадок.
Практическая схема ротации обычно имеет два уровня:
- Ключ шифрования данных (DEK), которым шифруют поля.
- Мастер‑ключ (KEK), которым оборачивают (зашифровывают) DEK.
С таким подходом вы можете менять мастер‑ключ, не переписывая все зашифрованные данные: достаточно переобернуть DEK, что быстро.
Иногда нужно перешифровать сами данные: если DEK скомпрометирован, если меняются алгоритмы или параметры, или если политика требует другой области применения ключей (например, переход с одного ключа для всего приложения на ключи по арендаторам).
Не пропускайте бэкапы и восстановление ключей. Потеря ключей — это потеря данных. Делайте зашифрованные бэкапы ключей, ограничьте доступ и регулярно тестируйте восстановление. Обычная ошибка: «мы сохранили бэкап базы, но не сохранили ключи».
Пример: стартап переходит с v1 на v2. Новые записи используют v2, старые строки остаются с v1, а фоновая задача постепенно перешифровывает их.
Как добавить шифрование полей пошагово
Считайте это изменением модели данных, а не быстрой «заплаткой безопасности». Аккуратный rollout — это то, что не позволяет слить открытый текст в логи, экспорты или одноразовые админ‑скрипты.
Начните с картирования того, что действительно чувствительно и куда эти данные попадают. Не смотрите только на базу: прострите потоки создания, чтения, обновления, фоновых задач, аналитики и экспортов. Частая ошибка — зашифровать столбец и забыть про CSV‑экспорт, который становится новой точкой утечки.
Выберите проверенную крипто‑библиотеку для вашего стека и одну схему шифрования, которую вы сможете объяснить себе через полгода. Для большинства приложений аутентифицированное шифрование — разумный выбор по умолчанию. Держите ключи вне базы и планируйте версионирование с первого дня.
Пошаговый rollout, который обычно работает:
- Добавьте новые зашифрованные столбцы рядом со старыми plaintext полями и сначала примените эту схему.
- Добавьте небольшой обёртывающий слой, который шифрует при записи и расшифровывает при чтении, и заставьте остальное приложение вызывать только его.
- Как можно скорее выключите запись в plaintext, но на короткое время оставьте чтение plaintext для плавного перехода.
- Бекфиллите существующие строки батчами с мониторингом, ограничением скорости и планом отката.
- Проверьте реальные запросы и экспорты, затем удалите plaintext в следующей миграции.
Во время бекфилла избегайте печати расшифрованных значений в логах, отчётах об ошибках и админ‑дашбордах. Логируйте ID записей и статусы, а не значения.
Как сохранить работу фич: поиск, отчёты и производительность
Шифрование защищает данные, но может нарушить привычный функционал, если это не предусмотреть. До шифрования столбцов перечислите экраны и задачи, которые зависят от этих полей: поисковые строки, админ‑таблицы, экспорты и плановые отчёты.
Поиск и фильтрация
При случайном шифровании одно и то же значение шифруется по‑разному. Точные поиска и дедупликация перестают работать, потому что база не может сравнивать шифротексты. Если нужен точный поиск (например, найти пользователя по SSN), храните рядом токен поиска, например ключёный хеш.
Частичный поиск (contains, starts‑with) обычно нельзя поддержать безопасно на зашифрованном тексте без специальных систем, поэтому большинство команд отключают его для чувствительных полей.
Сортировка и диапазонные фильтры также обычно ломаются: у шифротекста нет порядковой информации, поэтому нельзя сортировать по «заработной плате» или фильтровать «дата рождения между X и Y» напрямую. Распространённый обход — хранить грубый производный признак (например месяц и год) или заранее рассчитанные корзины.
Отчёты, индексация, кэширование и скорость
Для аналитики планируйте отдельный набор данных: агрегаты, счётчики или редактированную копию без чувствительных полей.
Несколько практических правил:
- Индексируйте хеши или ключёные хеши, а не расшифрованные значения.
- Не кешируйте расшифрованные данные в общих кэшей или логах.
- Расшифровывайте как можно позже (непосредственно перед использованием).
- Измеряйте горячие пути: расшифровка в tight‑loop может добавить задержку.
Миграции без раскрытия открытого текста
Предположите, что база какое‑то время будет в смешанном состоянии: часть строк в plaintext, часть — в шифротексте, а некоторые — с разными версиями ключей. Ваш код должен уметь работать со всеми этими состояниями без одноразовых скриптов, которые выводят открытый текст в логи или временные файлы.
Распространённый паттерн — dual‑read: при загрузке значения приложение сначала смотрит на зашифрованное поле. Если оно пустое, падает на наследуемое plaintext поле. Это поддерживает старые данные в рабочем состоянии, пока вы мигрируете фоновой задачей.
В паре с этим идёт dual‑write: при сохранении приложение пишет зашифрованную форму и, на короткий переходный период, обновляет старый plaintext. Это предотвращает появление новых записей в старом формате, пока вы шифруете старые строки.
Для бекфилла запускают фоновую задачу, которая шифрует наследуемые строки небольшими батчами. Обращайтесь к этому как к продакшен‑системе: ограничивайте скорость обновлений, используйте повторные попытки и идемпотентные операции (безопасно запускать дважды), фиксируйте прогресс, готовьтесь к частичным отказам и храните версию ключа рядом с шифротекстом.
Пример: в таблице signup есть phone_plain, вы добавляете phone_enc и phone_key_version. Новые регистрации пишут в phone_enc. Фоновой job проходит по старым пользователям, шифрует phone_plain, ставит версию и не трогает plaintext, пока вы не убедитесь, что чтение, экспорты и инструменты поддержки работают корректно.
Удаляйте наследуемый plaintext только после ясного cutoff: метрики показывают почти 100% покрытие, dual‑read долго работал в продакшене и у вас есть план отката.
Распространённые ошибки и ловушки, которых стоит избегать
Шифрование на уровне полей легко демонстрировать на идеальном сценарии. Проблемы проявляются позже: при простоях, миграциях, экспортax или в рабочих процессах поддержки.
Ловушки, которые приводят к утечке данных (даже если вы шифруете)
Большинство утечек не из базы. Они из всего вокруг неё: логи, экспорты, дашборды, отладочные эндпойнты и сторонние инструменты.
Частые ошибки: открытый текст в логах (debug prints, дампы запросов, трассы исключений), хардкоженные ключи или клиентские ключи (мобильные приложения, бандлы браузера, секреты в git), отсутствие проверки целостности (нет аутентифицированного шифрования), забытые выходы (CSV‑экспорты, письма, webhook‑payloads, админ‑экраны) и несопровождаемые восстановления (бэкапы есть, а ключи отсутствуют или неизвестна версия ключа).
Конкретный пример: приложение шифрует SSN, но при ошибке 500 логирует полное тело запроса для дебага. База в безопасности, но логи превращаются в теневую базу с открытым текстом.
Шифрование не тех полей
Частая ошибка — зашифровать поля, от которых зависит приложение для JOIN, проверок уникальности или рабочих процессов поддержки. Если вы зашифруете email, можете сломать поиск при логине, дедупликацию и «найти клиента» в админ‑панели. Если вам всё ещё нужны равенства, придётся хранить отдельное производное значение (например ключёный хеш) или менять дизайн.
Перед релизом быстро пройдитесь по вопросу «куда идёт это значение?»: запросы в базе, логи, экспорты, письма, аналитика и инструменты поддержки.
Наконец, рассматривайте восстановление ключей как функцию. Зашифрованные данные без ключей — это окончательная потеря данных.
Быстрый чек‑лист перед релизом
Часто шифрование на уровне полей терпит неудачу по скучным причинам: поле копируется куда‑то ещё или задача пишет plaintext «только раз». Перед релизом выполните финальную проверку, где данные могут утечь.
- Просчитайте все места появления значения: колонки БД, логи приложения, события аналитики, отчёты об ошибках, кэши, индексы поиска и бэкапы.
- Убедитесь, что plaintext нигде не пишется «временно»: никаких debug‑логов, экспорта файлов или временных файлов на диске.
- Храните информацию о версии вместе с шифротекстом: версия ключа (и по возможности метка алгоритма), чтобы можно было расшифровать старые записи после изменений.
- Докажите, что можете ротировать ключи без даунтайма: читайте старые данные, пишите новые с новым ключом, затем переоборачивайте в фоне.
- Применяйте принцип наименьших привилегий для расшифровки: только узкий набор сервисов и ролей должен иметь доступ к открытому тексту.
Проверьте, что у вас есть отлаженная процедура бэкапа и восстановления и для данных, и для ключей, и что она работает в стрессовых условиях (новое окружение, новая машина, новый человек её выполняет).
Пример: шифруем несколько полей в реальном приложении
Небольшой стартап хранит налоговые ID клиентов и внутренние заметки поддержки в таблице customers. Приложение выросло из прототипа и имеет плохую привычку: при ошибке логировать полную запись «для дебага». Это значит, что налоговые ID и приватные заметки могут оказаться в логах, дашбордах или сторонних инструментах для ошибок.
Они выбирают шифрование на уровне полей для двух колонок: tax_id и support_notes. Всё остальное остаётся в открытом виде, чтобы приложение могло по‑прежнему фильтровать, сортировать и строить отчёты без лишних усилий.
Чтобы поддержать скорость работы поддержки, они добавляют отдельный столбец вроде tax_id_hash (однонаправленный ключёный хеш). Служба поддержки может выполнять точные совпадения (клиент звонит и называет свой налоговый ID), но база никогда не хранит этот ID в поисковом открытом виде. Приложение сравнивает путём хеширования ввода и поиска по хешу.
Их план rollout держит приложение в работе, пока данные конвертируются:
- Добавить новые зашифрованные столбцы (или версии
*_enc) и полеkey_version. - Dual‑write: сохранять и старый plaintext, и зашифрованное значение короткое время.
- Dual‑read: отдавать в приоритете зашифрованное значение, падать на plaintext при отсутствии.
- Бекфилл батчами с алертами при ошибках расшифровки или злонамеренно неисправных записях.
- Когда покрытие близко к 100%, прекратить запись plaintext, затем удалить его в следующей миграции.
После изменения логи содержат редактированные плейсхолдеры вместо секретов. Сотрудники поддержки видят расшифрованные заметки только если их роль это позволяет.
Следующие шаги при обновлении существующей кодовой базы
Обновление уже работающего приложения — место, где шифрование становится грязной задачей. Цель — двигаться вперёд, не создавая долгого окна, где чувствительные данные открыты, копируются в логи или тихо записываются обратно в plaintext.
Начните с краткого документа решения, которым команда может поделиться. На одной странице: какие поля шифруются, почему (закон, риск, доверие клиентов) и какие системы или роли имеют право расшифровки. Это предотвратит случайные «зашифруем всё» изменения, которые сломают фичи позже.
Начните с небольшого пилота, который вы полностью понимаете: одна таблица, один пользовательский путь, один маршрут миграции. Зашифруйте что‑то вроде SSN или номера банковского счёта в одной таблице клиентов, затем обновите только экраны «просмотреть профиль» и «обновить профиль». Вы быстро обнаружите места утечек plaintext (логи отладки, экспорты, трекеры ошибок) до масштабирования на другие поля.
Добавьте предохранители перед релизом:
- Отключите логирование секретов (простое правило: не логировать тело запроса).
- Сделайте ошибки безопасными (без стектрейсов и расшифрованных значений в сообщениях пользователю).
- Пересмотрите доступы (кто может запускать экспорты, кто может запросы к продакшену, что уходит в аналитику).
- Добавьте тесты, которые падают при сохранении или возврате plaintext.
Если вы унаследовали кодовую базу, сгенерированную ИИ, выполните целенаправленный security pass перед миграцией: такие проекты часто имеют открытые секреты, чрезмерные права и «полезное» логирование, которое печатает всё.
Если вам нужна помощь очистить AI‑сгенерированное приложение перед добавлением шифрования, FixMyMess выполняет аудит и восстановление, включая усиление безопасности и безопасные миграции, чтобы вы не накладывали шифрование поверх существующих утечек. Большинство правок выполняются в течение 48–72 часов.
Часто задаваемые вопросы
What does field-level encryption actually protect me from?
Он защищает конкретные значения внутри таблиц, чтобы срез базы данных не раскрывал эти поля в открытом виде. Это в первую очередь защита от сценариев «кто‑то получил копию строк», а не способ остановить атаки против работающего приложения.
Which fields should I encrypt first?
Начните с полей, утрата которых может причинить реальный вред: государственные идентификаторы, дата рождения, коды восстановления, API‑токены или приватные заметки. Оставьте служебные метаданные (ID, метки времени, флаги статуса) без шифрования, чтобы приложение оставалось удобным для запросов и отчётов.
Should I use random (non-deterministic) encryption or deterministic encryption?
Если вам нужно только показывать значение позже — используйте случайное (неди детерминированное) шифрование: одно и то же значение даёт разный шифротекст и это безопаснее. Минус в том, что по зашифрованному столбцу обычно нельзя делать точные совпадения.
How can I still find a user by a sensitive value if it’s encrypted?
Держите зашифрованное значение и добавляйте отдельное поле для поиска, например ключёный хеш, для точных совпадений. Так можно находить записи по совпадению, не сохраняя исходное значение в поисковом открытом виде.
Do I need authenticated encryption, or is encryption alone enough?
Используйте аутентифицированное шифрование (часто называется AEAD), чтобы приложение могло обнаружить подмену шифротекста. «Только шифрование» без аутентификации не даёт гарантии от изменения данных.
Where should encryption keys live?
Выносите ключи из базы и из репозитория; лучше использовать управляемый сервис ключей (cloud KMS, HSM или менеджер секретов). Хорошее правило — только основной продакшен‑сервис, которому действительно нужен открытый текст, имеет право на расшифровку.
How do I handle key rotation without downtime?
Храните идентификатор ключа (версию или ID) рядом с каждым зашифрованным значением, чтобы можно было расшифровать старые записи. Часто применяют оболочечное шифрование (envelope encryption), чтобы сменить мастер‑ключ без перезаписи всех данных.
What’s the safest way to migrate existing plaintext data to encrypted fields?
Добавьте новые зашифрованные столбцы, переключитесь на запись зашифрованных данных и бекфиллите старые строки батчами. В переходный период чтение должно предпочитать зашифрованное поле и падать на plaintext только при отсутствии зашифрованного значения — затем удаляйте plaintext после проверки экспорта и инструментов.
What are the most common ways teams leak plaintext even after encrypting fields?
Большинство утечек происходит вне базы: логи, отчёты об ошибках, экспорты, админ‑панели и аналитика. Также опасны «временные» отладочные дампы во время миграций — они могут создать тень из открытых данных.
What if my codebase is messy (or AI-generated) and I’m worried encryption will break things?
Если кодовая база хромает (или сгенерирована ИИ), сначала исправьте логирование секретов и права доступа: шифрование ничего не даст, если открытый текст уже утекает в логи и экспорты. FixMyMess выполняет аудит кода и ремонт безопасности, чтобы не накладывать шифрование поверх существующих утечек.