Технологии безопасности

Сайт посвященный вопросам безопасности

Шифрование БД под управлением Firebird 3.0

(adsbygoogle = window.adsbygoogle || []).push({});

В современном информационном мире, информация играет значительную роль в жизни человека, общества и государства. Рост размера накапливаемых и обрабатываемых данных подымает вопросы об их хранении и обеспечении конфиденциальности. Уже существует немало технических решений и предложений для решения подобных задач. Среди них конечно же есть и системы управления базами данных (СУБД) которые поддерживают шифрование хранимых данных. Вот об одном из таких решений и пойдёт речь.

В апреле 2016 года вышла новая версия СУБД Firebird под номером 3. Из нововведений, среди прочего, появилось и немало механизмов защиты хранимых и передаваемых данных. Там есть и защита канала передачи данных, есть управление пользователями, а также есть шифрование самой БД, которое реализовано как прозрачное шифрование на уровне страниц данных. Реализуется это всё с помощью написания специальных расширений для Firebird. Можно конечно и самому разобраться и написать эти расширения, но почему бы не взять существующие. Тем более, что для написания, как минимум, нужно понимать в криптографии, иметь на вооружении знания какого-нибудь криптографического пакета и разобраться с новым С++ Firebird API.

Можно воспользоваться существующим готовым решением, скачав по этой ссылке для платформы Windows x64. В бесплатном ознакомительном варианте оно ограничено используемым алгоритмом шифрования — поддерживается Triple DES (3DES). Но для защиты своей БД этого вполне достаточно. В состав пакета входит 4 библиотеки и файлы с описанием интерфейсов.

Наименование файла
Описание

CiKeyHolder.dll
расширение Firebird – хранитель секретного ключа

CiDbCrypt.dll
расширение Firebird – шифрования данных

CiFbEnc_x86.dll
модуль активации ключа шифрования для платформы 32 bit.

CiFbEnc_x86-64.dll
модуль активации ключа шифрования для платформы 64 bit.

ICiFbEncActivator.h
описание интерфейса модуля для С++

CI.ICiFbEncActivator.pas
описание интерфейса модуля для Delphi

Как пример, можно написать приложение, которое использует некий справочник (условно с конфиденциальными данными), который как обновляемый доступен по ссылке в глобальной сети. Его может скачать кто угодно, но при этом расшифровать и работать с ним сможет только наше приложение.

Дабы не захламлять статью лишними деталями реализации подобной задачи, при которой необходимо обеспечить получение ключа и организации обновления файла БД, будем предполагать, что это есть, а ключ будет просто как переменная массив в программе и файл БД сами зашифруем, используя этот ключ.

В качестве платформы возьмём ОС Windows 10 x64, а в качестве инструментов реализации задачи будем использовать IDE Embarcadero RAD Studio (на данный момент была доступна версия 10.2 Tokyo — 30-дневная пробная), поскольку она содержит удобные компоненты для работы с БД Firebird и применяется часто для разработки прикладных решений (пробную версию можно скачать по ссылке) и СУБД Firebird 3.0.2 x64, которую можно скачать по ссылке https://www.firebirdsql.org/en/firebird-3-0-2 для своей платформы. Так как нам в режиме разработки не нужен постоянно выполняемый сервер, да и перезапуск его может понадобиться неоднократно, то во время инсталляции выбираем способ запуска сервера как приложения.

Пароль для SYSDBA делаем по старинке masterkey и снимаем галочку отвечающую за запуск сервера после установки – пока не нужно.

После того, как был проинсталлирован пакет Firebird, необходимо произвести его настройку. Если же планируется использование Firebird в Embedded режиме, то такие же настройки нужно будет проделать в директории, где будет находиться используемая библиотека клиент fbclient.dll. Базовые файлы настроек находятся в корневой директории установленного Firebird — %ProgramW6432%FirebirdFirebird_3_0.

Пропишем псевдоним (алиас) БД в файле databases.conf, добавив строку с указанием полного пути к будущей БД:

TESTDB = C:TESTAPPDBTESTDB.FDB

Для того, чтобы Firebird знал, где ему искать ключи, необходимо определить параметр KeyHolderPlugin. Его можно указать или конкретно для выше описанного псевдонима или в файле firebird.conf. Добавляем или изменяем параметр конфигурации файла firebird.conf, определяя плагин хранитель секретного ключа:

KeyHolderPlugin = CiKeyHolder

Где искать плагины Firebird узнаёт также из конфигурационных файлов. Изменим файл plugins.conf, добавив следующие строки:

Plugin = CiKeyHolder {
# Прописываем путь к плагину хранителю
Module = $(dir_plugins)/CiKeyHolder
}

Plugin = CiDbCrypt {
# Прописываем путь к плагину шифрования
Module = $(dir_plugins)/CiDbCrypt
}

Директория под псевдонимом $(dir_plugins) подразумевает директорию plugins в корневом каталоге Firebird. Копируем туда ранее скачанные плагины CiKeyHolder.dll и CiDbCrypt.dll.

Поскольку мы предполагаем, что БД справочник у нас уже есть, создадим её средствами пакета Firebird, а именно, используя приложение isql. Жмём Win + R и выполняем следующую команду:

%ProgramW6432%FirebirdFirebird_3_0isql -q -user SYSDBA -password masterkey

Или же как-то по-другому запускаем isql, например, используя программу Far.

Следующие команды создадут БД TESTDB, псевдоним которой ранее был прописан в настройках и выведут информацию о созданной БД:

SQL> create database ‘TESTDB’;
SQL> create table t_1 (i1 int, s1 varchar(15), s2 varchar(15));
SQL> insert into t_1 values (1,’value 1-1′,’value 1-2′);
SQL> insert into t_1 values (2,’value 2-1′,’value 2-2′);
SQL> commit;
SQL> select * from t_1;

I1 S1 S2
============ =============== ===============
1 value 1-1 value 1-2
2 value 2-1 value 2-2

SQL> show db;
Database: TESTDB
Owner: SYSDBA
PAGE_SIZE 8192
Number of DB pages allocated = 196
Number of DB pages used = 184
Number of DB pages free = 12
Sweep interval = 20000
Forced Writes are ON
Transaction — oldest = 4
Transaction — oldest active = 5
Transaction — oldest snapshot = 5
Transaction — Next = 9
ODS = 12.0
Database not encrypted
Default Character set: NONE
SQL> quit;

Как видно из полученной информации по команде show db;, созданная БД в данный момент не зашифрована — Database not encrypted.

Теперь всё настроено и можно приступить к написанию приложения.

Создаём новый проект VCL Forms Application в IDE Embarcadero RAD Studio. Сохраняем проект, например, как testapp в директории C:TESTAPP.

Для начала мы можем просто связать наше приложение с БД, обеспечив подключение и отключение к ней. Наша тестовая БД имеет одну таблицу, данные которой можно вывести при подключении. Из визуального оформления достаточно пары кнопок, таблица и навигатор с управлением данными. Для доступа к БД Firebird воспользуемся компонентами FireDAC. Далее представлена таблица с компонентами и их настраиваемыми параметрами.

Компоненты, помещаемые на форму тестового приложения

Класс компонента
Наименование компонента
Параметр
Значение параметра

TFDPhysFBDriverLink
FDPhysFBDriverLink1

TFDConnection
FDConnection1
DriverName
FB

LoginPrompt
False

ParamsDatabase
TESTDB

ParamsUserName
SYSDBA

ParamsPassword
masterkey

ParamsProtocol
ipTCPIP

ParamsServer
localhost

TFDGUIxWaitCursor
FDGUIxWaitCursor1

TFDTransaction
FDTransaction1

TFDTable
FDTable1
TableName
T_1

TDataSource
DataSource1
DataSet
FDTable1

TDBGrid
DBGrid1
DataSource
DataSource1

TDBNavigator
DBNavigator1
DataSource
DataSource1

TButton
Button1
Caption
Connect

TButton
Button2
Caption
Disconnect

В итоге форма может выглядеть приблизительно так.

Пропишем обработку нажатия на кнопку “Connect”:

FDConnection1->Connected = true;
FDTable1->Active = true;

И для “Disconnect”:

FDTable1->Active = false;
FDConnection1->Connected = false;

В случае использования Delphi всё аналогично:

FDConnection1.Connected := True;
FDTable1.Active := True;

. . .

FDConnection1.Connected := False;
FDTable1.Active := False;

Прежде чем подключиться к БД, необходимо запустить сервер. Для этого необходимо вызвать командную строку администратора:

Win + X → Командная строка (администратор)

И выполнить команду:

“%ProgramW6432%FirebirdFirebird_3_0firebird” –a

В трее появится значок запущенного сервера. Из контекстного меню вызванном над этим значком можно завершить его работу или посмотреть свойства запущенного сервера.

Теперь можно собрать и запустить приложение для проверки возможности подключения к БД. После подключения к БД в таблице отобразятся данные.

Настало время подключить модуль активации ключа и управления шифрованием к нашему приложению. Для этого копируем файлы CiFbEnc_x86.dll и ICiFbEncActivator.h, CI.ICiFbEncActivator.pas в директорию C:TESTAPP. Стоит обратить внимание, что поскольку по умолчанию в среде разработки создаётся проект под 32-bit Windows и триал версия среды разработки не позволяет изменить целевую платформу, приложению необходим именно файл-модуль CiFbEnc_x86.dll.

Подключим заголовочный файл с описанием интерфейса модуля активации к файлу приложения:

#include "ICiFbEncActivator.h"

Или файл Ci.ICiFbEncActivator.pas в случае Delphi.

uses
. . . , CI.ICiFbEncActivator;

Добавим на форму ещё три кнопки: “Encrypt”, “Decrypt” и “Get State”.

Для начала рассмотрим обработчик кнопки “Get State”. Что следует из названия, это даст нам возможность получить текущее состояние БД.

Самое главное, — это получить доступ к интерфейсу модуля активации. Его можно получать как для каждой операции, так и глобально или агрегируя в некий объект. Чтобы не городить классы-обертки, будем предполагать, что получаем объект модуля активации при каждой операции. Ниже представлен один из возможных способов сделать это.

// C++ Builder

// Загружаем модуль
std::unique_ptr<HINSTANCE__, decltype(&::FreeLibrary)> mHandle(
::LoadLibraryEx(L"C:\TESTAPP\CiFbEnc_x86.dll",0,LOAD_WITH_ALTERED_SEARCH_PATH),
&::FreeLibrary);

if (!mHandle)
{
MessageBox(
NULL,
L"Модуль CiFbEnc_x86.dll не найден или рядом с ним нет fbclient.dll",
L"Загрузка модуля",
MB_OK|MB_ICONERROR);

return;
}

// Получаем активатор
typedef CALL_CONV int(__stdcall *CREATEFUNCPTR)(CI::ICiFbEncActivator**);
CREATEFUNCPTR GetActivator =
(CREATEFUNCPTR)::GetProcAddress(mHandle.get(), "createCiFBEncActivator");

if (!GetActivator)
{
MessageBox(
NULL,
L"Не удалось получить из модуля CiFbEnc_x86.dll процедуру"
"createCiFBEncActivator"
" — попробуйте посмотреть, что пишет tdump.",
L"Загрузка модуля",
MB_OK|MB_ICONERROR);

return;
}

CI::ICiFbEncActivator* pActivator = NULL;
GetActivator(&pActivator);
if (!pActivator) { ShowMessage("ERROR GetActivator!"); return; }

// . . .
//
// обращения к объекту модуля активации
//
// . . .

// Уничтожаем экземпляр активатора
pActivator->Destroy();
pActivator = NULL;

// Delphi

var
pActivator : ICiFbEncActivator;
res : Integer;
CreateActivator: TActivatorFunction;
mHandle : HINST;
. . .
begin
// Загружаем модуль
mHandle := LoadLibraryEx(
PChar(‘C:TESTAPPCiFbEnc_x86.dll’), 0, LOAD_WITH_ALTERED_SEARCH_PATH);

if mHandle = 0 then
begin
MessageBox(
Application.Handle,
‘Модуль CiFbEnc_x86.dll не найден или рядом с ним нет fbclient.dll’,
‘Загрузка модуля’,
MB_OK OR MB_ICONERROR);
Exit;
end;

// Получаем активатор
CreateActivator := GetProcAddress(mHandle, ‘createCiFBEncActivator’);
if not Assigned(CreateActivator) then
begin
MessageBox(
Application.Handle,
‘Не удалось получить из модуля CiFbEnc_x86.dll процедуру createCiFBEncActivator’
+
‘ — попробуйте посмотреть, что пишет tdump.’,
‘Загрузка модуля’,
MB_OK OR MB_ICONERROR);

Exit;
end;

pActivator := nil;
res := CreateActivator(pActivator);
if not Assigned(pActivator) then begin ShowMessage(‘ERROR CreateActivator!’); Exit; end;

// . . .
//
// обращения к объекту модуля активации
//
// . . .

// Уничтожаем экземпляр активатора
pActivator.Destroy;
pActivator := nil;

FreeLibrary(mHandle);
end;

Далее будем предполагать, что для каждого из обработчиков нажатия на кнопку будет добавлен вышеуказанный код.

Теперь можно перейти и к получению состояния БД.

// C++ Builder

. . .

// Устанавливаем параметры подключения к БД
pActivator->SetDBAccess("localhost:TESTDB", "SYSDBA", "masterkey");

// Посмотрим, что нам расскажет о БД сервис Firebird
char stat_buf[1024] = { 0 };
size_t bufsize = sizeof(stat_buf);

int res = pActivator->GetStateSVC(stat_buf, bufsize);
String sStatMsg = (String)stat_buf;
if (Err_OK == res)
{
MessageBox(NULL, sStatMsg.c_str(), L"Статус БД", MB_OK|MB_ICONINFORMATION);
}
else
{
String sErrMsg = L"ERROR GetStateSVC: " + sStatMsg;
MessageBox(
NULL,
sErrMsg.c_str(),
L"Статус БД",
MB_OK|MB_ICONERROR);

}

. . .

// Delphi

var
. . .
stat_buf : array[0..1023] of AnsiChar;
bufsize : NativeUInt;

. . .

// Устанавливаем параметры подключения к БД
res := pActivator.SetDBAccess(‘localhost:TESTDB’, ‘SYSDBA’, ‘masterkey’);

// Посмотрим, что нам расскажет о БД сервис Firebird
bufsize := SizeOf(stat_buf);
ZeroMemory(@stat_buf, bufsize);
res := pActivator.GetStateSVC(stat_buf, bufsize);

if Err_OK = res then
begin
MessageBox(Application.Handle, PChar(String(stat_buf)),
‘Статус БД’, MB_OK OR MB_ICONINFORMATION);
end
else
begin
MessageBox(Application.Handle, PChar(String(stat_buf)),
‘Ошибка’, MB_OK OR MB_ICONERROR);
end;

. . .

В случае, если удалось подключиться к БД и получить информацию, мы увидим сообщение с детальной информацией о текущем состоянии.

Как видно, БД всё ещё не зашифрована. Пора это исправить. Но для шифрования необходим ключ. И мы его определим как глобальную константу в рамках примера.

// C++ Builder

// От куда-то у нас есть ключ 192 бита
const uint8_t key[24] =
{
0x06,0xDE,0x81,0xA1,0x30,0x55,0x1A,0xC9,
0x9C,0xA3,0x42,0xA9,0xB6,0x0F,0x54,0xF0,
0xB6,0xF9,0x70,0x18,0x85,0x04,0x83,0xBF
};

// Delphi

const
key : array [0..23] of Byte =
(
$06,$DE,$81,$A1,$30,$55,$1A,$C9,
$9C,$A3,$42,$A9,$B6,$0F,$54,$F0,
$B6,$F9,$70,$18,$85,$04,$83,$BF
);

Кроме получения текущего состояния БД, все остальные операции, такие как зашифровать, расшифровать и получить доступ к зашифрованным данным, требуют активации секретного ключа. Для этого добавим после получения объекта модуля активации следующий код:

// C++ Builder

. . .

// Устанавливаем параметры подключения к БД
pActivator->SetDBAccess("localhost:TESTDB", "SYSDBA", "masterkey");

// Устанавливаем ключ активатору
int res = pActivator->SetKey(&key, sizeof(key));
if (Err_OK != res) { ShowMessage("ERROR SetKey!"); pActivator->Destroy(); return; }

// Активируем доступ к ключу
res = pActivator->Activate();
if (Err_OK != res)
{
// Обрабатываем ошибку
char errmsg[512] = {0};
size_t esize = sizeof(errmsg);
pActivator->GetFBStat(errmsg, esize);
String sErrMsg = "ERROR Activate: " + String(errmsg);
MessageBox(
NULL,
sErrMsg.w_str(),
L"Активация доступа к ключу",
MB_OK|MB_ICONERROR);

pActivator->Destroy();
return;
}

. . .

// Delphi

. . .

// Устанавливаем параметры подключения к БД
res := pActivator.SetDBAccess(‘localhost:TESTDB’, ‘SYSDBA’, ‘masterkey’);

// Устанавливаем ключ активатору
res := pActivator.SetKey(@key, Length(key));
if Err_OK <> res then begin ShowMessage(‘ERROR SetKey!’); pActivator.Destroy; Exit; end;

// Активируем доступ к ключу
res := pActivator.Activate;
if Err_OK <> res then
begin
bufsize := SizeOf(errmsg);
ZeroMemory(@errmsg, bufsize);
pActivator.GetFBStat(errmsg, bufsize);
MessageBox(
Application.Handle,
PChar(‘ERROR Activate: ‘ + String(errmsg)),
‘Активация доступа к ключу’,
MB_OK OR MB_ICONERROR);

pActivator.Destroy;
Exit;
end;

. . .

Как видно, происходит передача ключа активатору и вызов функции активации. При этом модуль регистрирует функцию обратного вызова для доступа к ключу средствами расширения хранителя ключа. И это не зависит от режима работы Firebird. А безопасность передачи ключа гарантируется шифрованием на сессионных ключах. В вышеизложенном коде после вызова функции активации показан пример обработки возникновения возможной ошибки на уровне Firebird библиотеки. Подобным образом можно считывать текущий статус выполненной задачи для любой из операций с обращением к функционалу Firebird.

Теперь можем написать обработчики кнопок “Encrypt” и “Decrypt”. Если опустить проверку статуса выполненной задачи и предварительную активацию ключа, то достаточно только добавить вызов у объекта активации одноимённой функции:

// C++ Builder

// зашифровать
res = pActivator->Encrypt();

. . .

// расшифровать
res = pActivator->Decrypt();

// Delphi

// зашифровать
res := pActivator.Encrypt;

. . .

// расшифровать
res := pActivator.Decrypt;

В итоге мы получим идентичные обработчики за исключением одного вызова.

Теперь мы можем зашифровать БД. А вызов её статуса расскажет нам о том, что она уже зашифрована.

После этого, попытка выполнить подключение к БД не меняя обработчик кнопки “Connect” приведёт к ошибке.

Последний шаг – это изменение обработчика кнопки подключения к БД. Туда нужно добавить получение объекта интерфейса модуля и вызов активации ключа. Ниже представлен полностью код обработки подключения к зашифрованной БД.

Для C++ Builder

// C++ Builder

void __fastcall TForm1::Button1Click(TObject *Sender)
{
// Загружаем модуль
std::unique_ptr<HINSTANCE__, decltype(&::FreeLibrary)> mHandle(
::LoadLibraryEx(L"C:\TESTAPP\CiFbEnc_x86.dll", 0,
LOAD_WITH_ALTERED_SEARCH_PATH),
&::FreeLibrary);

if (!mHandle)
{
MessageBox(
NULL,
L"Модуль CiFbEnc_x86.dll не найден или рядом с ним нет fbclient.dll",
L"Загрузка модуля",
MB_OK|MB_ICONERROR);
return;
}

// Получаем активатор
typedef CALL_CONV int(__stdcall *CREATEFUNCPTR)(CI::ICiFbEncActivator**);
CREATEFUNCPTR GetActivator =
(CREATEFUNCPTR)::GetProcAddress(mHandle.get(), "createCiFBEncActivator");

if (!GetActivator)
{
MessageBox(
NULL,
L"Не удалось получить из модуля CiFbEnc_x86.dll"
" процедуру createCiFBEncActivator"
" — попробуйте посмотреть, что пишет tdump.",
L"Загрузка модуля",
MB_OK|MB_ICONERROR);
return;
}

CI::ICiFbEncActivator* pActivator = NULL;
GetActivator(&pActivator);
if (!pActivator) { ShowMessage("ERROR GetActivator!"); return; }

// Устанавливаем параметры подключения к БД
pActivator->SetDBAccess("localhost:TESTDB", "SYSDBA", "masterkey");

// Устанавливаем ключ активатору
int res = pActivator->SetKey(&key, sizeof(key));
if (Err_OK != res) { ShowMessage("ERROR SetKey!"); pActivator->Destroy();return; }

// Активируем доступ к ключу
res = pActivator->Activate();
if (Err_OK != res)
{
// Обрабатываем ошибку
char errmsg[512] = {0};
size_t esize = sizeof(errmsg);
pActivator->GetFBStat(errmsg, esize);
String sErrMsg = "ERROR Activate: " + String(errmsg);
MessageBox(
NULL,
sErrMsg.w_str(),
L"Активация доступа к ключу",
MB_OK|MB_ICONERROR);
pActivator->Destroy();
return;
}

// Подключаемся к БД
try
{
FDConnection1->Connected = true;
FDTable1->Active = true;
}
catch(EFDDBEngineException &e)
{
String sErrMsg = L"Не удалось подключиться к БД. " + e.Message;
MessageBox(
NULL,
sErrMsg.w_str(),
L"Подключение к БД",
MB_OK|MB_ICONERROR);
}

// Уничтожаем экземпляр активатора
pActivator->Destroy();
pActivator = NULL;
}

Для Delphi

// Delphi

procedure TForm2.Button1Click(Sender: TObject);
var
pActivator : ICiFbEncActivator;
res : Integer;
CreateActivator: TActivatorFunction;
mHandle : HINST;
errmsg : array[0..511] of AnsiChar;
bufsize : NativeUInt;
begin
// Загружаем модуль
mHandle := LoadLibraryEx(PChar(‘C:TESTAPPCiFbEnc_x86.dll’), 0,
LOAD_WITH_ALTERED_SEARCH_PATH);

if mHandle = 0 then
begin
MessageBox(
Application.Handle,
‘Модуль CiFbEnc_x86.dll не найден или рядом с ним нет fbclient.dll’,
‘Загрузка модуля’,
MB_OK OR MB_ICONERROR);
Exit;
end;

// Получаем активатор
CreateActivator := GetProcAddress(mHandle, ‘createCiFBEncActivator’);
if not Assigned(CreateActivator) then
begin
MessageBox(
Application.Handle,
‘Не удалось получить из модуля CiFbEnc_x86.dll процедуру createCiFBEncActivator’
+
‘ — попробуйте посмотреть, что пишет tdump.’,
‘Загрузка модуля’,
MB_OK OR MB_ICONERROR);
Exit;
end;
pActivator := nil;
res := CreateActivator(pActivator);
if not Assigned(pActivator) then begin ShowMessage(‘ERROR CreateActivator!’);Exit; end;

// Устанавливаем параметры подключения к БД
res := pActivator.SetDBAccess(‘localhost:TESTDB’, ‘SYSDBA’, ‘masterkey’);

// Устанавливаем ключ активатору
res := pActivator.SetKey(@key, Length(key));
if Err_OK <> res then begin ShowMessage(‘ERROR SetKey!’); pActivator.Destroy;Exit; end;

// Активируем доступ к ключу
res := pActivator.Activate;
if Err_OK <> res then
begin
bufsize := SizeOf(errmsg);
ZeroMemory(@errmsg, bufsize);
pActivator.GetFBStat(errmsg, bufsize);
MessageBox(
Application.Handle,
PChar(‘ERROR Activate: ‘ + String(errmsg)),
‘Активация доступа к ключу’,
MB_OK OR MB_ICONERROR);

pActivator.Destroy;
Exit;
end;

// Подключаемся к БД
try
FDConnection1.Connected := True;
FDTable1.Active := True;
except
on E: EFDDBEngineException do begin
MessageBox(
Application.Handle,
PChar(‘Не удалось подключиться к БД. ‘ + String(E.Message)),
‘Подключение к БД’,
MB_OK OR MB_ICONERROR);
end;
end;

// Уничтожаем экземпляр активатора
pActivator.Destroy;
pActivator := nil;

FreeLibrary(mHandle);
end;

Вот так вот достаточно просто можно работать с зашифрованной БД.

Полностью проект можно взять тут.

Демо-пакет — тут.