Шифрование персональных данных с надёжным хранением ключей.
В современных ИТ-системах конфиденциальные данные, как правило, хранятся в базах данных. А ещё практически любая система требует от пользователей его персональные данные: телефон, почта и др. Одним словом — ИСПДн.
Естественно, в любой системе СУБД прячется за семью файерволами. И напрямую доступа ко всей базе персональных данных нет. Естественно, это не сильно помогает. Огромная часть сливов персональных данных — получение несанкционированного доступа к инфраструктуре, как внешнего, так и внутреннего.
При получении несанкционированного доступа на уровне файловой системы или хранилища, самым простым и надёжным средством защиты остаётся шифрование. При краже дампа БД, конфиденциальная информация остаётся зашифрованной. Однако как защитить ключи шифрования? В случае внутреннего нарушителя, ключи можно украсть точно так же, как и файлы БД.
В этой статье мы рассматриваем шифрование в СУБД PostgreSQL с закрытыми ключами в аппаратном хранилище. В этом случае вероятность кражи ключей практически исключается. Используем связку PgSodium для шифрования и TPM для безопасного хранения криптографических ключа.
Репозиторий на Гитхабе: https://github.com/laboratory50/pgsodium-tpm
Что такое PgSodium?
PgSodium — это расширение PostgreSQL, предоставляющее криптографические функции на основе библиотеки libsodium. В отличие от встроенного расширения pgcrypto, PgSodium предлагает более современные и безопасные алгоритмы, включая криптографию с открытым ключом, аутентифицированное шифрование и безопасное управление ключами.
Основные возможности PgSodium:
- Шифрование/расшифровка: шифрование данных в столбцах (например, типа
bytea) с использованием симметричных ключей. - Управление ключами: создание, хранение и использование криптографических ключей.
- Хэширование и подписи: функции для безопасного хранения паролей и проверки целостности данных.
- Ролевая модель: разделение прав доступа (например, роли
pgsodium_keymaker,pgsodium_keyholder) для контроля над ключами.
PgSodium предоставляет два варианта использования:
- Со сторонними ключами: ваше приложение самостоятельно генерирует ключи и передаёт их в функции шифрования.
- С внутренние ключами: первичный ключ (root key) загружается в память сервера PostgreSQL и используются для создания вторичных ключей шифрования.
В данной статье мы рассматриваем вариант использования внутренних ключей, как наиболее применимый на собственной ИТ-инфраструктуре. Почему так? А вот почему:
- Безопасность: в запросах SQL используются идентификаторы ключей, сами ключи не передаются.
- Разграничение полномочий: роль
pgsodium_keyiduserможет только использовать ключи (косвенно по идентификатору), а для создания ключей необходима рольpgsodium_keymaker. - Автоматизация: облегчает ротацию ключей и управление жизненным циклом без необходимости переписывать код приложения.
- Сложные сценарии: упрощает реализацию шифрования «один ключ на пользователя» через триггеры.
Первичный ключ будет храниться за семью замками (точнее одним, но аппаратным), а непосредственно для шифрования будут применяться вторичные ключи, не раскрываемые при взаимодействии с приложением.
Основной опасностью могут стать ошибки при настройке шифрования, что может привести к потере доступа к данным.
Тестируем PgSodium
1. Контейнер с PostgreSQL
Официальный образ PostgreSQL не содержит PgSodium, поэтому его нужно собрать самостоятельно, добавив библиотеку libsodium и само расширение.
Пример Dockerfile:
FROM postgres:16
# Установка зависимостей для сборки
RUN apt-get update && apt-get install -y \
libsodium-dev \
postgresql-server-dev-$PG_MAJOR \
make \
gcc \
git
# Сборка и установка pgsodium
RUN git clone https://github.com/tembo/pgsodium /tmp/pgsodium \
&& cd /tmp/pgsodium \
&& make install \
&& rm -rf /tmp/pgsodium
2. Управления внутренними ключами
Чтобы PgSodium работал в режиме использования внутренних ключей, сервер должен загрузить модуль при запуске с помощью параметра shared_preload_libraries.
Создание скрипта получения серверного ключа
В каталог с расширениями (обычно /usr/share/postgresql/$PG_MAJOR/extension/) нужно поместить исполняемый файл pgsodium_getkey. Пример скрипта, генерирующего ключ из /dev/urandom:
#!/bin/sh
# pgsodium_getkey - должен выводить 64-символьную hex-строку (32 байта)
if [ ! -f /var/lib/postgresql/data/pgsodium_root.key ]; then
head -c 32 /dev/urandom | od -An -vtx1 | tr -d ' \n' > /var/lib/postgresql/data/pgsodium_root.key
fi
cat /var/lib/postgresql/data/pgsodium_root.key
Важно сделать файл исполняемым:
chmod +x /usr/share/postgresql/16/extension/pgsodium_getkey
Подключение в docker-compose.yml
services:
db:
build: .
environment:
POSTGRES_PASSWORD: your_password
command: postgres -c 'shared_preload_libraries=pgsodium'
volumes:
- ./pgsodium_getkey:/usr/share/postgresql/16/extension/pgsodium_getkey:ro
- pgdata:/var/lib/postgresql/data
Включение расширения в базе данных
CREATE EXTENSION IF NOT EXISTS pgsodium;
Использование PgSodium
- Генерация ключей с использованием первичного ключа:
-- Генерация вторичного ключа шифрования SELECT pgsodium.create_key('my_key'); - Шифрование данных на уровне столбцов:
-- Создание таблицы с зашифрованными данными CREATE TABLE users ( id SERIAL PRIMARY KEY, name TEXT, email BYTEA, -- зашифрованное поле ssn BYTEA -- зашифрованное поле ); -- Шифрование данных при вставке INSERT INTO users (name, email, ssn) VALUES ( 'Иван Иванов', pgsodium.crypto_aead_det_encrypt( convert_to('ivan@example.com', 'utf8'), convert_to('context', 'utf8'), 'your_key_id_here'::uuid ), pgsodium.crypto_aead_det_encrypt( convert_to('123-45-6789', 'utf8'), convert_to('context', 'utf8'), 'your_key_id_here'::uuid ) ); -- Расшифровка данных при выборке SELECT name, convert_from( pgsodium.crypto_aead_det_decrypt( email, convert_to('context', 'utf8'), 'your_key_id_here'::uuid ), 'utf8' ) as email FROM users;
Проблема хранения серверного ключа и решение с TPM
Остаётся проблема хранения серверного ключа. PgSodium получает ключ при старте сервера с помощью скрипта pgsodium_getkey. Ключ можно создать средствами PgSodium:
select encode(randombytes_buf(32), 'hex')
Однако безопасное хранение ключа — отдельная задача. Распространённые решения: чтение ключа с SSH-сервера, использование облачных систем хранения (Vault, Infisical) или аппаратного модуля безопасности (TPM).
Для тех, кто использует собственную инфраструктуру, наиболее безопасным представляется использование TPM.
Что такое TPM?
TPM (Trusted Platform Module) — это специализированный криптографический процессор, который обеспечивает аппаратную защиту ключей шифрования. Он предоставляет безопасное хранилище для криптографических ключей и выполняет криптографические операции без экспорта ключей в память.
Интеграция TPM с PostgreSQL в Docker
Для работы с TPM в контейнере необходимо подключить реальный или виртуальный TPM и установить пакет для работы с ним.
Дополним Dockerfile:
RUN apt-get update && apt-get install -y tpm2-tools
В docker-compose.yml добавим доступ к TPM:
devices: - "/dev/tpm0:/dev/tpm0" - "/dev/tpmrm0:/dev/tpmrm0" privileged: true
Хранение ключа в TPM
В TPM количество хранимых пользовательских данных ограничено (обычно до 7 объектов). Чтобы сохранить ключ в TPM, выполните следующий скрипт:
# 1. Создание случайного ключа длиной 32 байта openssl rand 32 > root.key # 2. Создание первичного объекта в TPM (Primary Object) tpm2_createprimary -C o -c primary.ctx tpm2_create -C primary.ctx -i root.key -u key.pub -r key.priv tpm2_load -C primary.ctx -u key.pub -r key.priv -c key.ctx # 3. Сохранение ключа в NVRAM TPM под постоянным дескриптором (например, 0x81000000) tpm2_evictcontrol -C o -c key.ctx 0x81000000 # 4. Удаление исходного файла ключа! rm -f root.key
Для переноса данных на другую физическую машину необходимо сохранить ключ, созданный OpenSSL, но не хранить его на сервере.
Изменение скрипта pgsodium_getkey для работы с TPM
Чтобы считывать серверный ключ из TPM, изменим файл pgsodium_getkey:
#!/bin/bash -e
# Конфигурация
PERSISTENT_HANDLE="0x81000000" # Дескриптор ключа в TPM
if ! tpm2_getcap handles-persistent 2>/dev/null | grep -q "$PERSISTENT_HANDLE"; then
echo "ERROR: Key handle $PERSISTENT_HANDLE not found in TPM" >&2
exit 1
fi
tpm2_unseal -c "$PERSISTENT_HANDLE" 2>/dev/null | hexdump -ve '1/1 "%.2x"'
Таким образом, ключ PgSodium хранится в TPM, а на сервере его нет.
Плюсы и минусы использования TPM
Плюсы:
- Аппаратная защита: ключи никогда не покидают TPM в открытом виде.
- Защита от физических атак: TPM устойчив к попыткам извлечения ключей.
- Целостность системы: TPM может проверять целостность загрузочного процесса.
- Изоляция ключей: каждый TPM имеет уникальные ключи, привязанные к оборудованию.
- Поддержка стандартов: соответствие международным стандартам безопасности.
Минусы:
- Привязка к оборудованию: ключи не могут быть легко перенесены на другой TPM.
- Ограниченная производительность: TPM не предназначен для высокоскоростных операций.
- Сложность управления: требуются специальные инструменты и знания.
- Ограниченное пространство: ограниченное количество ключей, которые можно хранить.
Анализ векторов атаки на связку PgSodium + TPM
Злоумышленник с доступом к скрипту и файлам системы может попытаться:
- Получить первичный ключ с правами PostgreSQL. Если злоумышленник уже имеет привилегии пользователя/процесса, от которого запускается PostgreSQL (например,
postgres), он может запустить скрипт сам и получить ключ в момент работы сервера. Меры защиты: разграничение прав, изоляция процесса, контроль целостности. - Украсть дамп памяти процесса PostgreSQL. Можно попытаться сделать дамп памяти процесса и найти ключ там. Меры защиты: защита от эксплуатации уязвимостей в СУБД, использование мандатной защиты (PARSEC/SELinux/AppArmor).
- Скомпроментировать защиту операционной системы. Сложная атака, как правило требующая физического доступа. Меры защиты: Secure Boot, trusted boot.
- Изменить скрипт так, чтобы он копировал или пересылал ключ при следующей загрузке PostgreSQL. Меры защиты: защита целостности файлов (PARSEC, immutable flags,
chattr +i, режим «только для чтения»). - Украсть резервные копии ключа. Резервная копия ключа, не защищённая TPM (например, для восстановления), его кража — это катастрофа. Меры защиты: хранение резервной копии в сейфе.
Рекомендации по безопасности
- Минимизация прав. Скрипт
pgsodium_getkeyи файл сciphertextдолжны быть доступны только на чтение пользователю/группе учётки PostgreSQL (например,postgres:postgres, режим400или440). - Целостность файлов. Установите флаг immutable (
chattr +i) на скрипт и файл сciphertext, чтобы предотвратить их изменение даже root-пользователем (сбросить флаг можно, но это оставит след). Используйте PARSEC в Astra Linux Special Edition. - Изоляция. Запускайте PostgreSQL в отдельном контейнере или виртуальной машине с минимальным набором инструментов.
- Аудит. Настройте мониторинг и аудит запуска скрипта
pgsodium_getkeyи доступа к связанным файлам. - Резервные ключи. Храните резервные копии первичного ключа в сейфе или используйте аппаратные токены.
Заключение
Связка PgSodium и TPM предоставляет надёжную защиту конфиденциальных данных в PostgreSQL, сочетая современные криптографические алгоритмы и аппаратные возможности по безопасности. Настройка такой системы требует значительных усилий и экспертизы, она обеспечивает уровень защиты, необходимый для обработки чувствительных данных в регулируемых отраслях.

