Ta strona została przetłumaczona maszynowo. Przeczytaj oryginał angielski. English

Biblioteka IBSurgeon

Jak działa szyfrowanie bazy danych Firebird

(c) Alexey Kovyazin, IBSurgeon, Alex Peshkoff, Firebird Project, 2021

Artykuł oparty jest na materiałach z warsztatów „Szyfrowanie baz danych” podczas Firebird Conference 2019 w Berlinie w Niemczech. Opisuje, jak działa szyfrowanie baz danych Firebird, na poziomie serwera, po stronie klienta, jak skonfigurować szyfrowanie bazy danych, jak używać go z różnych typów aplikacji (Delphi, Java, .NET). Przykłady w artykule oparte są na IBSurgeon Firebird Encryption Framework (FEPF), ale można je dostosować do większości obecnie dostępnych implementacji wtyczek szyfrujących.

Spis treści:

  1. Dlaczego potrzebujemy szyfrowania baz danych (a kiedy nie)?
  2. Jak działa szyfrowanie baz danych Firebird po stronie serwera
  3. Która część bazy danych jest szyfrowana?
  4. Kiedy strony danych są szyfrowane?
  5. Jak chronić transfer kluczy?
  6. Jak działa szyfrowanie Firebird po stronie klienta
  7. Aplikacje natywne
  8. Aplikacje Java
  9. Aplikacje .NET
  10. Instalacja i konfiguracja
  11. Jak śledzić postęp szyfrowania
  12. Podsumowanie

1. Dlaczego potrzebujemy szyfrowania baz danych (a kiedy nie)?

Szyfrowanie baz danych Firebird zostało wprowadzone w wersji Firebird 3.0 (wraz z szyfrowaniem protokołu transferu, które często jest mylone z omawianym tematem) i znacznie zwiększyło możliwości ochrony danych przed nieautoryzowanym dostępem. Nie jest to jednak panaceum i konieczne jest zrozumienie jego mocnych i słabych stron, aby używać go właściwie.

W tym artykule rozważymy wewnętrzne mechanizmy szyfrowania baz danych na podstawowym poziomie, aby dać programistom aplikacji Firebird lepsze zrozumienie, jak działa szyfrowanie baz danych.

Dlaczego więc potrzebujemy szyfrowania baz danych?

  1. Aby chronić bazy danych z wrażliwymi/cennymi danymi przed „fizyczną” kradzieżą. Jeśli intruz ukradnie dysk z kopią zaszyfrowanej bazy danych lub w jakiś sposób uzyska kopię pliku bazy danych, nie będzie możliwe odczytanie z niej danych bez odpowiedniego klucza, ani też nie będzie możliwe użycie oprogramowania do odzyskiwania danych, takiego jak FirstAID, do wyodrębnienia danych. Oczywiście zależy to od algorytmu szyfrowania i mocy obliczeniowej, ale złamanie AES256 będzie wymagać zbyt długiego czasu lub zbyt kosztownych zasobów obliczeniowych.
  2. Aby chronić bazę danych przed dostępem z nieautoryzowanych aplikacji bez kluczy szyfrowania. Przykłady to:
    • bezpośredni dostęp za pomocą narzędzia deweloperskiego przez nieautoryzowaną osobę w celu zmiany wrażliwych informacji (np. transakcji pieniężnych),
    • zmiana lub kradzież logiki biznesowej (tekstów procedur składowanych i wyzwalaczy).
  3. Ochrona baz danych z wstępnie wypełnionymi danymi przed eksportem lub dostępem przez nieautoryzowane aplikacje.
  4. Rządy wprowadziły ostatnio przepisy dotyczące ochrony danych (GDPR/DSVGO w Europie, LGPD w Brazylii itp.), które wymagają między innymi wyższego poziomu ochrony danych osobowych i innych wrażliwych danych, a szyfrowanie jest wymieniane jako jedna z odpowiednich środków ochrony.

Kiedy szyfrowanie baz danych nie jest przydatne?

W niektórych przypadkach lepiej jest użyć możliwości zabezpieczeń i konfiguracji Firebird zamiast szyfrowania baz danych:

  • Aby chronić bazę danych przed fizycznym dostępem przez sieć, konieczne jest skonfigurowanie dostępu sieciowego: tj. zamknięcie współdzielonych folderów sieciowych, ponieważ Firebird nie wymaga współdzielonego dostępu sieciowego do plików bazy danych, oraz zaostrzenie uprawnień bezpieczeństwa (dla systemu Linux, na przykład, pliki bazy danych muszą mieć dostęp do odczytu i zapisu tylko dla użytkownika „firebird”).
  • Aby ograniczyć dostęp do konkretnej bazy danych dla konkretnego podzbioru użytkowników, łatwiejszym rozwiązaniem będzie skonfigurowanie osobnej bazy danych zabezpieczeń.
  • Aby ograniczyć dostęp do obiektów bazy danych (tabele, procedury składowane), konieczne jest użycie mechanizmów zabezpieczeń Firebird: użytkowników, ról itp.

Oczywiście obie powyższe listy są niekompletne, ale dają pewne wyobrażenie o tym, kiedy potrzebujesz lub nie potrzebujesz szyfrowania baz danych.

2. Jak działa szyfrowanie baz danych Firebird po stronie serwera

Przejdźmy przez wewnętrzne szczegóły szyfrowania baz danych Firebird i zacznijmy od części serwerowej.

2.1. Która część bazy danych jest szyfrowana?

Pierwszą rzeczą, którą musimy rozważyć, jest to, która część bazy danych jest szyfrowana? Jak zapewne wiesz, baza danych Firebird składa się z części o równej wielkości, zwanych „stronami bazy danych”. Istnieje kilka typów takich stron, a każdy typ służy określonemu celowi.

Poniżej możesz zobaczyć rysunek z głównymi typami danych:

Rysunek 1. Typy stron bazy danych

Niektóre strony są przeznaczone do przechowywania danych użytkowników, a inne są potrzebne do przechowywania informacji systemowych, takich jak transakcje i strony inwentaryzacji stron (więcej szczegółów na temat stron bazy danych jest dostępnych tutaj).

Gdy baza danych Firebird jest szyfrowana, szyfrowane są tylko strony z danymi użytkowników: strony danych, indeksy, generatory i BLOB-y:

Rysunek 2. Tylko strony bazy danych z danymi użytkowników są szyfrowane

Należy pamiętać, że metadane bazy danych (procedury składowane, tabele, widoki, wyzwalacze, nazwy generatorów itp.) nie różnią się od „danych użytkowników” w części silnika odpowiedzialnej za szyfrowanie i są szyfrowane.

Dlaczego strony systemowe nie są szyfrowane? Głównie ze względów wydajnościowych oraz ze względu na fakt, że nie zawierają wrażliwych danych wymagających ochrony.

Strona nagłówka bazy danych nie jest szyfrowana, ponieważ zawiera informacje potrzebne do szyfrowania (na przykład nazwę klucza).

2.2. Kiedy strony danych są szyfrowane?

Gdy użytkownik wykonuje SELECT z zaszyfrowanej bazy danych, dane są odczytywane z zaszyfrowanego pliku, ale docierają do siatki wyników aplikacji w formie niezaszyfrowanej.

Rozważmy szczegóły tego procesu:

Rysunek 3. Kiedy strony bazy danych są szyfrowane?

Zazwyczaj proces zaczyna się od serii odczytów stron bazy danych z pliku bazy danych, a strony te są buforowane w pamięci podręcznej plików systemu operacyjnego.

Firebird można również skonfigurować tak, aby pomijał pamięć podręczną plików i używał tylko własnej pamięci podręcznej, ale domyślnie używana jest pamięć podręczna plików.

Następnie Firebird odczytuje strony i umieszcza je w pamięci podręcznej stron Firebird (jest ona określana przez parametr DefaultDBCachePages w firebird.conf i/lub databases.conf lub na stronie nagłówka bazy danych).

Następnie strony z pamięci podręcznej są wybierane do zestawu wyników konkretnego polecenia SQL (SELECT w naszym przykładzie).

Poniższy rysunek pokazuje szczegóły:

Rysunek 4. Strony są szyfrowane między pamięcią podręczną Firebird a pamięcią podręczną plików systemu operacyjnego

Tak więc strony bazy danych są szyfrowane w pamięci podręcznej plików systemu operacyjnego, ale docierają do pamięci podręcznej stron Firebird w formie niezaszyfrowanej i odwrotnie.

Część oprogramowania Firebird odpowiedzialna za szyfrowanie/deszyfrowanie nazywana jest „wtyczką szyfrującą”. Ponieważ większość implementacji wtyczek (znanych autorom) nazywa się DbCrypt, będziemy się do niej odnosić jako DbCrypt.

Na poniższym rysunku możesz zobaczyć warianty dla Windows (DbCrypt.dll) i Linux (libDbCrypt.so):

Rysunek 5. Wtyczka szyfrująca (DbCrypt) wykonuje szyfrowanie/deszyfrowanie

Jeśli spojrzysz na ten obraz wystarczająco długo, następne pytanie pojawi się dość szybko: jak DbCrypt uzyskuje właściwy klucz do szyfrowania/deszyfrowania stron bazy danych?

Odpowiedź - istnieje kolejna wtyczka do zarządzania kluczami.

Typowa nazwa zarządzania kluczami to KeyHolder, który służy jako magazyn/zarządzanie kluczami dla wtyczki szyfrującej (DbCrypt). KeyHolder implementuje interfejs do zarządzania kluczami używany przez DbCrypt.

Rysunek 6. DbCrypt i KeyHolder

Co oznacza „zarządzanie kluczami”?

W najprostszym przypadku DbCrypt może odczytywać klucze z pliku na serwerze. Plik może być prostym plikiem tekstowym, który może być ukryty w „tajnym” miejscu lub na dysku USB, lub może być plikiem zaszyfrowanym (na przykład przy użyciu Windows Crypto API lub z wbudowanym kluczem wewnętrznym).

Plik kluczy może zawierać kilka kluczy, przechowywanych dla wygody jako nazwana lista, i może wyglądać tak (przykład poniżej pochodzi z frameworka wtyczek szyfrujących IBSurgeon):

Code
Key=Red 0xec,0xa1,0x52,0xf6,0x4d,0x27,0xda,0x93,0x53,0xe5,0x48,0x86,0xb9,0x7d,0xe2,0x8f,0x3b,0xfa,0xb7,0x91,0x22,0x5b,0x59,0x15,0x82,0x35,0xf5,0x30,0x1f,0x04,0xdc,0x75,
Key=Green 0xab,0xd7,0x34,0x63,0xae,0x19,0x52,0x00,0xb8,0x84,0xa3,0x44,0xbd,0x11,0x9f,0x72,0xe0,0x04,0x68,0x4f,0xc4,0x89,0x3b,0x20,0x8d,0x2a,0xa7,0x07,0x32,0x3b,0x5e,0x74,

Gdy baza danych jest szyfrowana, jej strony nagłówka pozostają niezaszyfrowane, aby przechowywać informacje o wtyczce szyfrującej i nazwie klucza, a te informacje można zobaczyć za pomocą polecenia „gstat -h nazwabazy”:

Code
Database header page information:
....
Creation date    Jan 11, 2017 15:12:20
Attributes       force write, encrypted, plugin DBCRYPT

Variable header data:
 Crypt checksum: MUB2NTJqchh9RshmP6xFAiIc2iI=
 Key hash:       ask88tfWbinvC6b1JvS9Mfuh47c=
 Encryption key name:    RED
 Sweep interval:         0
*END*

DbCrypt może przetwarzać strony dla kilku baz danych i kilku kluczy:

Rysunek 7. Wiele kluczy dla wielu baz danych na tym samym serwerze (tj. instancji Firebird)

Wybór właściwego klucza

Często programiści zadają pytanie: „Jak wtyczka rozpoznaje, który klucz jest dla której bazy danych?” Odpowiedź jest dość prosta: po nazwie klucza, która jest przechowywana na stronie nagłówka bazy danych.

Rzadziej, ale wciąż ważne pytanie - co jeśli nazwa klucza będzie taka, jak zapisana w nagłówku, ale wartość klucza będzie inna? Aby zapobiec błędom odczytu stron z powodu niewłaściwego klucza, wtyczka DbCrypt przechowuje zaszyfrowaną sekwencję testową (cyfry 0…F) w nagłówku, a następnie, gdy klucz jest aktywowany, wtyczka próbuje zaszyfrować dane próbne kluczem i porównać ich skrót z zapisanym wynikiem, aby upewnić się, że przekazana wartość klucza jest faktycznie poprawna dla tej konkretnej bazy danych.

Podejście, w którym wtyczka szyfrująca odczytuje klucze bezpośrednio, jest proste i korzystne do debugowania, testów wydajności itp., ponieważ implementuje szyfrowanie w sposób przejrzysty: tj. aplikacje klienckie i narzędzia deweloperskie nie wiedzą, że baza danych jest zaszyfrowana.

Jednak w rzeczywistości musimy ograniczyć dostęp aplikacji do bazy danych: tylko aplikacja kliencka, która ma klucz, powinna mieć możliwość połączenia się z zaszyfrowaną bazą danych.

W tym celu potrzebujemy wtyczki KeyHolder, aby uzyskać klucz od aplikacji klienckiej (która zwykle znajduje się na innym komputerze) przez protokół sieciowy Firebird.

2.3. Jak chronić transfer kluczy?

Możliwe, że chcemy chronić bazę danych w sytuacji, gdy klient zdecyduje się uzyskać bezpośredni dostęp do zaszyfrowanej bazy danych, z pominięciem autoryzowanych aplikacji (wielu dostawców chce ograniczyć dane przed bezpośrednim dostępem, zarówno tylko do odczytu, jak i do odczytu i zapisu).

Jest to równoznaczne z sytuacją, gdy intruz ma dostęp do serwera, ale nie ma kluczy.

Rozważmy następujące scenariusze ataków mające na celu przechwycenie kluczy po stronie serwera:

  1. Gdy intruz tworzy fałszywy plugin szyfrujący (DBCrypt.dll) i umieszcza go na serwerze, a gdy KeyHolder przekazuje klucz, fałszywy DbCrypt wykonuje zrzut klucza:

Rysunek 8. Atak z użyciem fałszywego DbCrypt.dll

  1. Gdy intruz tworzy fałszywy plik firebird.exe i wykonuje zrzut za jego pomocą:

Rysunek 9. Atak z użyciem fałszywego firebird.exe

Aby zabezpieczyć się przed takimi atakami, dobra implementacja pluginów szyfrowania i zarządzania kluczami musi chronić wymianę kluczy.

Wymiana kluczy może być chroniona za pomocą szyfrowania asymetrycznego z parą kluczy publiczny/prywatny.

Klucze te są generowane podczas procesu budowania i wbudowane dla konkretnej pary pluginów szyfrowania i zarządzania kluczami. Dla najlepszej ochrony konieczne jest użycie specjalnie zbudowanych par DbCrypt/KeyHolder.

Gdy DbCrypt i KeyHolder wymieniają klucze, używają następującego protokołu (jest on uproszczony, ale myślę, że idea jest jasna):

Code
DbCrypt → KeyHolder:
	Podaj mi klucz bazy danych z tym saltem
KeyHolder:
	Szyfruje DbKey kluczem publicznym używając saltu z DbCrypt
	Przekazuje zaszyfrowany DbKey do DbCrypt
DbCrypt:
	Odszyfrowuje DbKey kluczem prywatnym
	Weryfikuje poprawność saltu
	Gotowy do pracy

Mniej więcej ten sam protokół jest używany do wymiany kluczy między instancjami KeyHolder oraz do wymiany kluczy między aplikacją kliencką a KeyHolder.

Należy pamiętać, że szyfrowanie i poprawność przesyłanych kluczy zależą od implementacji pluginu; silnik Firebird zapewnia tylko podstawową usługę transferu niskiego poziomu „wyślij N bajtów z tej instancji pluginu do tamtej instancji pluginu”.

Podsumowanie części serwerowej szyfrowania

  • Szyfrowanie/deszyfrowanie jest wykonywane przez plugin szyfrowania bazy danych (DbCrypt), strona po stronie, podczas wymiany danych między pamięcią podręczną plików systemu operacyjnego a pamięcią podręczną stron Firebird
  • Zarządzanie kluczami może być zaimplementowane w prosty sposób, gdy DbCrypt czyta klucze bezpośrednio, ale zwykle odbywa się to za pomocą pluginu zarządzania kluczami (KeyHolder)

Teraz odkryjmy, jak aplikacje klienckie pracują z zaszyfrowanymi bazami danych.

3. Jak działa szyfrowanie Firebird po stronie klienta

3.1. Aplikacje natywne

Aby zrozumieć, co się dzieje, gdy aplikacja kliencka łączy się z zaszyfrowaną bazą danych, rozważmy zwykły proces łączenia z niezaszyfrowaną bazą danych dla aplikacji natywnych.

Uwaga: od tego miejsca „natywna” oznacza, że taka aplikacja nawiązuje połączenie sieciowe z serwerem za pomocą fbclient.dll; zwykle taka aplikacja jest zbudowana w Delphi, C++, PHP. W przeciwieństwie do aplikacji natywnych, Java i .NET implementują własną wersję protokołu; zostaną one omówione poniżej.

Proces łączenia:

  1. Aplikacja kliencka ładuje bibliotekę kliencką
  2. fbclient.dll - natywne aplikacje Windows
  3. libfbclient.so - natywne aplikacje Linux
  4. Aplikacja kliencka inicjuje połączenie, wysyłając
  5. Nazwę użytkownika, np. SYSDBA
  6. Hasło, np. masterkey
  7. Ścieżkę/alias bazy danych

W przypadku zaszyfrowanej bazy danych wymagany jest dodatkowy krok: konieczne jest przekazanie nazwy klucza szyfrowania i jego wartości.

Ważne jest, aby przekazanie klucza odbyło się przed zwykłym połączeniem, ze względu na fakt, że strony danych z metadanymi, w tym nazwa właściciela bazy danych, zestaw znaków itp., są zaszyfrowane.

Prowadzi nas to do następujących wniosków:

  1. Konieczny jest dodatkowy cykl sieciowy, aby przekazać klucz przed zwykłym połączeniem
  2. Przekazanie klucza z aplikacji klienckiej do Firebird wymaga kodowania z użyciem szyfrowania asymetrycznego i implementacji interfejsu wywołania zwrotnego; może to być dość skomplikowane. Aby uprościć to zadanie, dostawcy pluginów udostępniają przykład kodu do łączenia lub, jak w frameworku pluginów IBSurgeon, tworzą dodatkową bibliotekę fbcrypt.dll/libfbcrypt.so, która implementuje łatwy w użyciu odpowiedni interfejs do przekazywania kluczy z aplikacji klienckiej.

Aby połączyć natywną aplikację (używającą fbclient.dll) z zaszyfrowaną bazą danych, należy wykonać 3 wywołania. Poniżej znajduje się przykład w Delphi (uproszczony, bez obsługi błędów):

W procedurze obsługi zdarzenia BeforeConnect:

Code
fbcrypt_init(PAnsiChar(‘C:\Firebird30.bclient.dll’));
fbcrypt_key(‘RED’, ‘0xec,0xa1,0x52,0xf6,...’));
fbcrypt_callback(nil);

 // Następnie połącz się jak zwykle
Database1.Active:=True;

Na poniższym rysunku widać przegląd procesu łączenia z zaszyfrowaną bazą danych w aplikacji natywnej:

Rysunek 10. Proces łączenia z zaszyfrowaną bazą danych dla aplikacji natywnych

A co z bezpieczeństwem wątków w przypadku wielowątkowych aplikacji klienckich?

Przypomnę kilka ogólnych kwestii dotyczących implementacji wielowątkowych aplikacji klienckich.

Od Firebird 2.5 wiele wątków wewnątrz aplikacji może bezpiecznie używać pojedynczego przyłączenia do bazy danych, ponieważ cała niezbędna synchronizacja jest wykonywana wewnątrz fbclient.dll.

Jednak w tym przypadku wątki będą mogły pracować z przyłączeniem tylko jeden po drugim.

Jest to w porządku dla aplikacji, które nie wymagają wydajnej wymiany danych z bazą - jeśli nie stanowi problemu czekanie na wykonanie zapytania SQL z jednego wątku, gdy inny wątek wykonuje inne SQL, łatwiej jest użyć prostego modelu, w którym 1 przyłączenie jest współdzielone między kilkoma wątkami.

Jeśli aplikacja wymaga równoległego wykonywania zapytań SQL (tj. jest to pełnowymiarowa aplikacja kliencka), lepiej jest użyć osobnego wątku dla każdego połączenia.

Sytuacja z wymianą kluczy dla przyłączeń do zaszyfrowanych baz danych jest nieco bardziej złożona.

Przy każdym przyłączeniu biblioteka kliencka przekazuje klucze od klienta, ale nie jest to bezpośrednio związane z wątkami w aplikacji klienckiej; sytuacja zależy od używanego API.

Jak wiadomo, biblioteka kliencka Firebird od wersji 3.0 oferuje 2 typy API: nowe zorientowane obiektowo API, oparte na koncepcji dostawców, oraz starsze isc_ API, zaimplementowane jako obejście, w celu zachowania zgodności ze starymi sterownikami Firebird.

Jeśli używane jest nowe zorientowane obiektowo API klienckie, wystarczy utworzyć dostawcę, wyposażyć go w niezbędne klucze, a następnie używać go dla nowych przyłączeń.

Jeśli używane jest isc_ API klienckie, dla każdego przyłączenia biblioteka kliencka utworzy własnego tymczasowego dostawcę, który nie jest bezpośrednio widoczny ani dostępny dla użytkownika końcowego.

W tym przypadku klucz jest przekazywany dokładnie z wątku, w którym wywoływane jest isc_attach_database, a do przechowywania tego klucza używana jest pamięć lokalna wątku.

W praktyce, ponieważ prawie wszystkie biblioteki klienckie używają isc_ API (obecnie, wśród popularnych sterowników, tylko sterownik Python używa API OO), konieczne jest wywołanie fb_database_crypt_callback() w każdym wątku, który łączy się z zaszyfrowaną bazą danych.

Wywołania przekazania klucza (wywołania fbcrypt.dll w przykładzie FEPF) muszą być wykonane przed połączeniem, w tym samym wątku, w którym zostanie nawiązane połączenie.

Gdy pracujemy z wieloma bazami danych (na przykład serwer WWW SaaS z wieloma bazami klientów), ważne jest, aby pamiętać, że każde wywołanie fbcrypt_key() dodaje klucz do magazynu KeyHolder, powiązany z bieżącym połączeniem.

Wartości kluczy muszą być ustawione przed połączeniem; po przyłączeniu wartość klucza nie może zostać zmieniona.

W przypadku odłączenia klucze nie są zwalniane; będą przechowywane w pamięci aż do zwolnienia fbcrypt.dll.

3.2. Aplikacje Java

Sterownik Java (JayBird) ma własną implementację (w czystej Javie) protokołu połączenia Firebird. Jaybird 4 (oraz 3.0.4+) dodaje obsługę wywołań zwrotnych szyfrowania bazy danych Firebird 3 w czystej implementacji Java protokołu w wersji 13.

Z Readme Jaybird 4:

Obecna implementacja jest prosta i obsługuje tylko odpowiadanie statyczną wartością z właściwości połączenia. Należy pamiętać, że statyczna odpowiedź wartości dla szyfrowania bazy danych nie jest zbyt bezpieczna, ponieważ może łatwo prowadzić do ataków typu replay lub niezamierzonego ujawnienia klucza.

Przyszłe wersje Jaybird (prawdopodobnie 5) wprowadzą obsługę pluginów dla pluginów szyfrowania bazy danych, które wymagają bardziej złożonego wywołania zwrotnego.

W praktyce oznacza to, że musimy ustawić wartość dla wywołania zwrotnego szyfrowania (zwykle jest to nazwa klucza i para klucz-wartość) we właściwości połączenia dbCryptConfig.

Na przykład:

Code
 edConnectionString.setText("jdbc:firebirdsql://localhost/g:/Databases/ODS12/crypt.fdb?lc_ctype=utf8&dbCryptConfig=MyKey:0xec,0xa1,0x52,0xf6,0x4d,0x27,0xda,0x93,0x53,0xe5,0x48,0x86,0xb9,0x7d,0xe2,0x8f,0x3b,0xfa,0xb7,0x91,0x22,0x5b,0x59,0x15,0x82,0x35,0xf5,0x30,0x1f,0x04,0xdc,0x75,");

Możliwe jest również określenie ciągu w base64 - z readme:

Ciągi z prefiksem base64:: reszta ciągu jest dekodowana jako base64 do bajtów.

Znaki dopełnienia = są opcjonalne, ale jeśli są obecne, muszą być prawidłowe (to znaczy: jeśli używasz dopełnienia, musisz użyć prawidłowej liczby znaków dopełnienia dla długości).

Gdy wartość zakodowana w base64 zawiera +, musi być escapowana jako %2B w adresie URL JDBC. Dla zachowania wstecznej kompatybilności z Jaybird 3 nie możemy przełączyć się na wariant base64 bezpieczny dla URL.

W implementacji pluginu zarządzania kluczami IBSurgeon takie przekazanie klucza jest uważane za mniej lub bardziej niebezpieczne: jeśli szyfrowanie protokołu sieciowego nie jest włączone ( przy okazji, aby je włączyć, ustaw w firebird.conf WireCrypt=Required i nie używaj starszego uwierzytelniania), klucz może zostać łatwo wykryty za pomocą analizatora ruchu sieciowego, takiego jak WireShark, dlatego aby umożliwić przekazywanie kluczy w ten sposób, konieczne jest ustawienie UnsafeClient=true w KeyHolder.conf pluginu zarządzania kluczami IBSurgeon.

3.3 Aplikacje .NET

Dostawca Firebird.NET implementuje podobny schemat wymiany kluczy dla zaszyfrowanych baz danych i również wymaga ustawienia parametru UnsafeClient=true w KeyHolder.conf w FEPF.

Przykład .NET ciągu połączenia dla zaszyfrowanych baz danych:

Code
string connectionString =
                "User=SYSDBA;" +
                "Password=masterkey;" +
                "Database=G:\\Databases\\ODS12.RYPT.FDB;" +
                "DataSource=localhost;" +
                "Port=3053;" +
                "Dialect=3;" +
                "Charset=NONE;" +
                "Role=;" +
                "Connection lifetime=15;" +
                "Pooling=true;" +
                "MinPoolSize=0;" +
                "MaxPoolSize=50;" +
                "Packet Size=8192;" +
                "ServerType=0;" +
                 "cryptkey = TXlLZXk6MHhlYywweG…...;";

Można zauważyć, że klucz szyfrowania wygląda inaczej niż w przykładzie dla aplikacji natywnych i JayBird; dzieje się tak, ponieważ jest to wynik transformacji Base64, więc aby uzyskać klucz dla aplikacji .NET lub Java, konieczne jest obliczenie base64 z ciągu:

“MyKey:0xec,0xa1,0x52,0xf6,0x4d,0x27,0xda,0x93,0x53,0xe5,0x48,0x86,0xb9,0x7d,0xe2,0x8f,0x3b,0xfa,0xb7,0x91,0x22,0x5b,0x59,0x15,0x82,0x35,0xf5,0x30,0x1f,0x04,0xdc,0x75,”

i użycie go jako parametru “cryptkey=xxx;” z “;” na końcu ciągu połączenia.

4. Instalacja i konfiguracja

Aby włączyć szyfrowanie bazy danych i plugin zarządzania kluczami, konieczne jest określenie nazwy pluginu szyfrowania w pliku konfiguracyjnym Firebird firebird.conf:

Code
 KeyHolderPlugin = KeyHolder

Lub alternatywnie, w databases.conf, dla aliasu zaszyfrowanej bazy danych:

Code
myencrypted = C:\Temp\EMPLOYEE30.MPLOYEE30.FDB { KeyHolderPlugin = KeyHolder }

Następnie należy sprawdzić, czy wszystkie pliki potrzebne dla pluginu znajdują się na serwerze.

Poniższy przykład dotyczy FEPF od IBSurgeon, ale inne pluginy są mniej więcej podobne:

W %FirebirdFolder$\plugins

Code
    • DbCrypt.dll
    • DbCrypt.conf
    • KeyHolder.dll
    • KeyHolder.conf - tylko do trybu debugowania!

W %FirebirdFolder$

Code
    • fbcrypt.dll
    • libcrypto-1_1-x64.dll
    • libssl-1_1-x64.dll
    • firebird.msg

Następnie możemy przeprowadzić testowe szyfrowanie na serwerze. W tym celu w isql:

Code
isql.exe localhost:C:\Temp\EMPLOYEE30.MPLOYEE30.FDB -user SYSDBA -pass masterkey
SQL>alter database encrypt with dbcrypt key red;
SQL> show database;
Database: localhost:C:\Temp\EMPLOYEE30.MPLOYEE30.FDB
….
ODS = 12.0
Database encrypted
Default Character set: NONE

Jeśli pracujesz na Linuksie, pamiętaj, że wielkość liter ma znaczenie, więc polecenie będzie wyglądać następująco:

Code
alter database encrypt with "DbCrypt" key Red;

Teraz możemy przetestować dostęp klienta do zaszyfrowanej bazy danych. W tym celu usuniemy (lub zmienimy nazwę albo edytujemy) plik konfiguracyjny KeyHolder.conf i spróbujemy połączyć się z zaszyfrowaną bazą danych za pomocą prostej aplikacji testowej.

W tym celu musimy umieścić w folderze z aplikacją kliencką następujące pliki:

  • Aplikacja demo z FEPF - CryptTest.exe (32-bit)
  • Pliki obowiązkowe:
    • fbclient.dll
    • fbcrypt.dll
    • libcrypto-1.1.dll
    • libssl-1.1-x64.dll
  • Pliki opcjonalne:
    • firebird.conf
    • w plugins
    • ◦ KeyHolder.conf
    • ◦ keyhodler.dll

W niektórych implementacjach wtyczek do zarządzania kluczami możliwe jest ładowanie kluczy w bibliotece klienta (fbclient.dll), bez modyfikacji oprogramowania klienckiego.

Umożliwia to przejrzystą pracę narzędzi deweloperskich Firebird (takich jak Firebird SQL Studio, DatabaseWorkbench, IBExpert, FlameRobin, RedExpert itp.) oraz przejrzyste korzystanie z narzędzi wiersza poleceń Firebird (gfix.exe, nbackup.exe itp.).

5. Jak śledzić postęp szyfrowania

Firebird szyfruje bazę danych tylko wtedy, gdy ma aktywne połączenia. Proces szyfrowania działa w osobnym wątku równoległym, a w przypadku dużych baz danych pełne szyfrowanie może zająć znaczną ilość czasu.

Aby śledzić proces szyfrowania, uruchom zapytanie SQL z MON$:

Code
select mon$crypt_page * 100.0 / mon$pages as Percent from mon$database;
commit;

lub wykonaj narzędzie gstat ze specjalnym przełącznikiem:

Code
gstat -e dbname

Database "D:\ENCDB\TESTENCRYPT.FDB"
Gstat execution time Tue Mar 16 11:45:11 2021

Database header page information:
        Flags                   0
        Generation              10697
        System Change Number    3
        Page size               8192
        ODS version             12.0
        Oldest transaction      7053
        Oldest active           7054
        Oldest snapshot         7054
        Next transaction        7054
        Sequence number         0
        Next attachment ID      17834
        Implementation          HW=Intel/i386 little-endian OS=Windows CC=MSVC
        Shadow count            0
        Page buffers            0
        Next header page        0
        Database dialect        3
        Creation date           Oct 9, 2019 6:42:31
        Attributes              encrypted, plugin DBCRYPT

    Variable header data:
        Database backup GUID:   {866B4967-ED58-427E-A481-DB9206CEA2ED}
        Crypt checksum: MUB2NTJqchh9RshmP6xFAiIc2iI=
        Key hash:       ask88tfWbinvC6b1JvS9Mfuh47c=
        Encryption key name:    RED
        Database GUID:  {323FE494-1771-4608-E99D-C1B69C84578B}
        *END*

Data pages: total 105, encrypted 105, non-crypted 0
Index pages: total 95, encrypted 95, non-crypted 0
Blob pages: total 0, encrypted 0, non-crypted 0
Generator pages: total 1, encrypted 1, non-crypted 0
Gstat completion time Tue Mar 16 11:45:11 2021

Należy pamiętać, że wykonanie gstat może być długotrwałym procesem.

6. Podsumowanie

  1. Szyfrowanie baz danych Firebird to potężna funkcja chroniąca informacje w bazach danych przed nieautoryzowanym dostępem.
  2. Proces szyfrowania wymaga biblioteki dynamicznej po stronie serwera - wtyczki szyfrującej (zwykle nazywanej DbCrypt) oraz, w zdecydowanej większości przypadków, wtyczki do zarządzania kluczami (zwykle nazywanej KeyHolder).
  3. Bezpieczna i niezawodna implementacja wtyczek DbCrypt i KeyHolder powinna uwzględniać najpopularniejsze typy ataków.
  4. Aby pracować z zaszyfrowaną bazą danych, aplikacje klienckie muszą przekazać klucz szyfrowania.
  5. Instalacja i konfiguracja wtyczki szyfrującej po stronie serwera jest trywialna - wymaga 1 parametru w firebird.conf/databases.conf oraz kilku plików.
  6. Proces szyfrowania może być długotrwały, odbywa się w osobnym wątku w tle, a postęp można śledzić za pomocą wywołania MON$ lub gstat.

Co dalej?

Pracujemy nad szczegółowym testem wydajności szyfrowania baz danych Firebird. Ogólnie wydajność jest o 4-8% niższa, ale zależy to od sprzętu i ustawień Firebird. Bądź na bieżąco!

Skontaktuj się z nami:

Prosimy o przesyłanie sugestii, literówek, błędów itp. oraz wszelkich pytań na adres e-mail: [email protected]