Введение
Криптографический цикл (статьи 371-380) разобрал шифрование на общем, прикладном уровне — в этой статье применим эти концепции конкретно к данным, хранимым в БД: шифрование «в покое» (at-rest, данные на диске) и «в движении» (in-transit, данные при передаче между приложением и СУБД по сети).
Концепция
Шифрование at-rest защищает данные, физически хранящиеся на диске сервера БД, от доступа в случае физической кражи носителя или несанкционированного доступа к файловой системе сервера, минуя сам процесс СУБД — реализуется либо на уровне всей файловой системы/диска (transparent data encryption, прозрачно для самой СУБД и приложения), либо на уровне отдельных, особо чувствительных столбцов (column-level encryption, требующее явного шифрования/расшифровки конкретных значений на уровне приложения или функций СУБД, статья 373). Шифрование in-transit (через TLS, статья 376) защищает данные при передаче по сети между клиентским приложением и сервером БД от перехвата на сетевом уровне, что особенно критично, если соединение проходит через недоверенную сеть (общедоступный интернет, а не изолированная внутренняя сеть дата-центра).
Пример кода: Шифрование данных в БД
-- Column-level encryption — шифрование конкретного, особо чувствительного столбца
-- (концептуальный пример через pgcrypto, расширение PostgreSQL)
CREATE EXTENSION IF NOT EXISTS pgcrypto;
CREATE TABLE sensitive_data (
id SERIAL PRIMARY KEY,
customer_id INTEGER REFERENCES customers(id),
encrypted_ssn BYTEA -- зашифрованное значение, а не обычный текстовый столбец
);
INSERT INTO sensitive_data (customer_id, encrypted_ssn)
VALUES (1, pgp_sym_encrypt('123-45-6789', 'encryption_key_from_secure_storage'));
-- Расшифровка требует знания того же ключа — без него значение остаётся недоступным
SELECT pgp_sym_decrypt(encrypted_ssn, 'encryption_key_from_secure_storage') FROM sensitive_data WHERE id = 1;
// Подключение к БД через TLS (in-transit шифрование) — параметры зависят от драйвера/СУБД
QSqlDatabase db = QSqlDatabase::addDatabase("QPSQL");
db.setHostName("dbserver.example.com");
db.setConnectOptions("sslmode=require"); // требование TLS для соединения (PostgreSQL)
db.open();
# Transparent data encryption на уровне файловой системы — настраивается на уровне ОС/диска,
# полностью прозрачно для самой СУБД и приложения (концептуальный пример, Linux LUKS)
cryptsetup luksFormat /dev/sdb1
cryptsetup luksOpen /dev/sdb1 encrypted_db_volume
mkfs.ext4 /dev/mapper/encrypted_db_volume
mount /dev/mapper/encrypted_db_volume /var/lib/postgresql/data
Пояснения к коду
pgp_sym_encrypt()/pgp_sym_decrypt() показывают column-level encryption на практике — конкретный столбец (encrypted_ssn) хранится как зашифрованные, не читаемые в обычном виде байты, и только код, владеющий правильным ключом, может расшифровать реальное значение; это даёт защиту даже от лиц с прямым административным доступом к самой БД (включая, теоретически, администратора СУБД), не знающих ключа шифрования, что отличается от transparent data encryption на уровне файловой системы, где сама СУБД (и любой, имеющий доступ к работающему процессу СУБД) видит данные в открытом виде, защита действует только против доступа непосредственно к файлам на диске, минуя СУБД. sslmode=require для подключения через QSqlDatabase показывает практичную настройку in-transit шифрования на уровне клиентского кода — без этой настройки соединение с БД по умолчанию во многих конфигурациях может происходить без шифрования, что для соединения через недоверенную сеть представляет реальный риск.
Подводные камни
- Управление ключом шифрования для column-level encryption тем же способом, что и сами зашифрованные данные (например, хранение ключа в той же БД, в обычном, незашифрованном столбце или конфигурационной таблице) — это полностью обесценивает защиту: любой, кто получает доступ к БД (включая ключ), автоматически получает возможность расшифровать защищённые данные; ключ должен храниться отдельно от защищаемых им данных, в специализированном защищённом хранилище секретов (статья 377), недоступном тому же кругу лиц/процессов, что имеет доступ непосредственно к зашифрованным данным.
- Восприятие transparent data encryption как полной, достаточной защиты данных, без понимания, что эта защита действует только против доступа к физическим файлам на диске в отрыве от работающего процесса СУБД — против атакующего, получившего полноценный доступ к работающей СУБД (через скомпрометированные учётные данные, статья 434, или уязвимость самого приложения), transparent encryption не даёт никакой дополнительной защиты, поскольку для самой СУБД (и через неё — для атакующего, использующего тот же доступ) данные доступны в полностью расшифрованном, читаемом виде.
- Подключение к БД без шифрования транспорта (без
sslmode=requireили эквивалентной настройки) через сеть, не являющуюся полностью изолированной и доверенной — данные, передаваемые в открытом виде по сети, потенциально доступной для перехвата (любая сеть, не находящаяся под полным, прямым контролем и наблюдением, особенно публичный интернет), могут быть прочитаны третьей стороной без какого-либо взлома самой СУБД; для любого соединения, проходящего через потенциально недоверенную сеть, шифрование транспорта (TLS) должно быть обязательным, а не опциональным. - Существенное снижение производительности запросов из-за необходимости расшифровки зашифрованных столбцов при каждом обращении к ним, особенно для столбцов, активно используемых в условиях фильтрации/JOIN/сортировки — зашифрованные значения, по своей природе, не могут эффективно использоваться обычными индексами (статья 413) для поиска по их расшифрованному значению (поиск по зашифрованному значению как таковому — отдельная, более специализированная задача), и column-level encryption обычно следует применять выборочно, только к действительно чувствительным столбцам, для которых эта защита оправдывает связанные с ней практические компромиссы производительности.