Простое шифрование БД SQlite
Так получилось, что я очень люблю использовать SQLite СУБД.
Программируя на ассемблере, я иногда нуждаюсь в полноценной СУБД. Мои программы редко превышают в размере несколько сотен килобайт. Понятно, что использовать с ним СУБД в несколько сотен мегабайт по меньшей мере просто смешно, а в конце концов, очень неудобно — сразу возрастают требования к оборудованию и сложности установки и настройки, а в итоге уменьшается надежность всей системы.
SQLite совершенно другое дело. Во-первых, она маленькая — всего несколько сот килобайт, прекрасное дополнение к компактным программам на ассемблере. Во-вторых, это ультра-надежная система хранения данных. Никаких специальных установок и настроек ей не нужно. Ну и насчет быстродействия — не из последних.
К примеру, я использовал SQLite в моем движке форума AsmBB о котором уже писал на хабре. (Кстати, после этого он так и не упал).
С того времени проект медленно, но уверенно развивается. Появились новые функции, повысились безопасность и быстродействие.
И вот однажды я задумался как повысить и так неплохую безопасность проекта. И сразу подумал, что неплохо было бы сделать шифрование БД форума. Ведь если даже база и утечёт, то доступ к личным данным пользователей никто не получит.
Быстрый поиск по Интернету показал, что есть нескольких расширений SQLite для шифрования БД. К сожалению, официальное расширение SEE несвободно и вообще продается за деньги.
Но, конечно, свято место пусто не бывает и я сразу наткнулся на расширение SQLeet. И в нем мне понравилось буквально все.
SQLeet использует алгоритм ChaCha20 для шифрования БД. Ключ шифрования вычисляется через PBKDF2-HMAC-SHA256, используя 16-байтовую соль и 12345 итераций хеширования. Для аутентификации используется Poly1305.
И SQLeet и SQLite распространяются на условиях общественного достояния (public domain). Это удобно, так как не увеличивает лицензионный хаос в проекте.
Еще SQLeet очень компактный. Весь код занимает только около полутора тысяч строк на C и не имеет внешних зависимостей.
Проект активно поддерживается и автор оперативно отвечает на вопросы и исправляет баги, если такие найдутся.
SQLeet распространяется тем же самым образом, как и SQLite — в форме единственного исходного файла на C, который можно просто скомпилировать тем же самым образом как компилируется и SQLite.
К тому же, так как расширение никак не меняет код SQLite, то обновления основной СУБД можно делать очень просто — через замены файла sqlite3.c
и пересоздание объединенного исходника.
Так как в AsmBB я использую не совсем стандартную компиляцию (SQLite в AsmBB компилируется через MUSL libc), а я не являюсь C программистом, то для меня простота компиляции очень важна.
Вот, например, баш код, который я использую, чтобы скачать последнюю версию SQLeet и создать и скомпоновать исходник:
wget -q -O - https://github.com/resilar/sqleet/archive/master.tar.gz | tar -xz
cd ./sqleet-master
script/amalgamate.sh < ./sqleet.c > ../sqlite3.c
cd ..
rm -rf ./sqleet-master/
В результате получается файл sqlite3.c
который можно вставить там, где раньше вставлялся оригинальный файл SQLite и использовать тем же образом.
Использование расширения тоже никак не отличается от использования SQLite. Разница только в том, что если БД зашифрована, то сразу после открытия надо вызвать функцию sqlite3_key (), в которой указать пароль шифрования. Ну или даже лучше, просто исполнить SQL pragma key='%пароль%'
. (Это лучше потому что API интерфейс к SQLite не меняется и
всегда можно заменить SQLeet на SQLite и обратно).
Начальное шифрование БД, а также замена пароля, происходит через функцию sqlite3_rekey()
или pragma командой pragma rekey='%NEW_PASSWORD%'
.
И вот здесь у меня возник такой вопрос. Откуда вообще брать пароль? Ведь если пароль сохраняется на сервере в каком-нибудь файле, то потенциальный хакер сможет прочесть его.
Поэтому я решил сделать по-другому. Дело в том, что AsmBB является долгоживущим FastCGI приложением. Однажды запущенный на сервере, он работает месяцы и даже годы без необходимости в перезагрузке.
А раз так, то пароль просто может ввести администратор через веб-интерфейс сразу после запуска AsmBB. Таким образом, пароль существует только в RAM и только во время исполнения POST-запроса во время запуска приложения. (Конечно, не надо забывать затирать нулями всю память, в которой пароль существовал во время исполнения POST-запроса.)
Из заданного пароля SQLeet генерирует ключ шифрования через PBKDF2-HMAC-SHA256, и этот ключ тоже хранится только в RAM.
Конечно, такое решение не совершенно. Ключ шифрования, наверное, можно найти в RAM памяти, во время исполнения AsmBB, если у атакующего есть права администратора.
Но даже если и так, то система все равно получается намного безопаснее, чем без шифровки. Например, теперь резервные копии БД можно хранить вообще повсюду и пересылать по открытым каналам, не опасаясь утечки данных.
Есть, кстати, и грабли, на которые можно наступить, применяя SQLeet (или другие криптографические расширения SQLite). И я на них, конечно, наступил.
Проблема в размере странницы БД. В версиях SQLite более ранных, чем 3.12.0 (март 2016 года) дефолтный размер страницы был 1024 байта. В v3.12.0 его сделали 4096 байта. Этот размер, пользователь БД может менять из соображений производительности, а размер страницы записан в самой БД.
Но если БД зашифрована, то размер страницы нельзя прочесть, а для расшифровки этот размер нужен, потому что каждый блок зашифровывается отдельно.
Поэтому, если зашифровать БД с нестандартным размером страницы (для SQLeet стандарт 4096 байт), потом расшифровать не удастся, даже если задавать правильный пароль.
Исправляется это просто — прежде, чем задавать пароль через sqlite3_key()
или pragma key='%пароль%'
, надо задать правильный размер страницы через pragma page_size=%размер%
.
Другая возможная проблема в том, что после шифровки ОС уже не сможет распознать, что файл является SQLite БД. Это (насколько я читал) иногда приводит к каким-то проблемам, в частности в iOS. Решение этой проблемы есть, просто не шифровать первые 32 байта файла, но в детали я не вдавался.
И напоследок насчет производительности. SQLeet работает очень быстро. После шифрования я не заметил какого-либо замедления работы системы на фоне нормальных флуктуаций производительности VPS. Прецизионные измерения, возможно, покажут какое-нибудь замедление, но оно наверняка будет в пределах меньше чем 10% от скорости не зашифрованной БД.
Конечно, есть и другие свободные расширения SQLite для шифрования. Например, SQLcipher. Мне оно не подошло потому что у него другая лицензия распространения (BSD), намного больше размер кода и имеются внешние зависимости.
Но, с другой стороны, SQLcipher намного старше и поэтому (возможно) более стабильный. Кому-то может и пригодится.