Korzystanie z szyfrowania w bazach danych SQL

Adobe AIR 1.5 i starsze wersje

Wszystkie aplikacje Adobe AIR współużytkują ten sam lokalny mechanizm bazy danych. Dlatego każda aplikacja AIR może połączyć się z niezaszyfrowanym plikiem bazy danych oraz odczytywać i zapisywać w nim dane. Począwszy od wersji 1.5 środowisko Adobe AIR oferuje możliwość tworzenia zaszyfrowanych plików baz danych i łączenia się z takimi plikami. Aby połączyć się z zaszyfrowaną bazą danych, aplikacja musi podać prawidłowy klucz szyfrowania. Jeśli nie zostanie podany prawidłowy klucz szyfrowania (lub nie zostanie podany żaden klucz), aplikacja nie będzie mogła połączyć się z bazą. W takiej sytuacji aplikacja nie będzie mogła odczytywać danych z bazy ani zapisywać bądź zmieniać w niej danych.

Aby korzystać z szyfrowanej bazy danych, należy utworzyć bazę jako zaszyfrowaną. Gdy zaszyfrowana baza danych będzie już istniała, możliwe będzie nawiązanie z nią połączenia. Można również zmienić klucz szyfrowania zaszyfrowanej bazy danych. Techniki pracy z zaszyfrowanymi bazami danych — z wyjątkiem operacji tworzenia takich baz i łączenia się z nimi — są takie same, jak w przypadku baz niezaszyfrowanych. W szczególności instrukcje SQL wykonywane są tak samo niezależnie od tego, czy baza danych jest zaszyfrowana.

Zastosowania zaszyfrowanej bazy danych

Szyfrowanie jest przydatne w sytuacjach, gdy chcemy ograniczyć dostęp do informacji przechowywanych w bazie danych. Funkcja szyfrowania baz danych oferowana przez środowisko Adobe AIR może być używana w kilku różnych celach. Poniżej przedstawiono przykłady sytuacji, w których celowe mogłoby okazać się użycie zaszyfrowanej bazy danych.

  • Przeznaczona tylko do odczytu pamięć podręczna na prywatne dane aplikacji pobrane z serwera.

  • Lokalny magazyn aplikacji przeznaczony na dane prywatne i synchronizowane z serwerem (dane są wysyłane do serwera i z niego ładowane).

  • Pliki zaszyfrowane jako format dokumentów tworzonych i edytowanych przez aplikację. Pliki mogą być prywatnymi plikami jednego użytkownika lub współużytkowane przez wszystkich użytkowników aplikacji.

  • Wszelkie inne zastosowania lokalnego magazynu danych, takie jak opisane w sekcji Zastosowania lokalnych baz danych SQL , gdy dane muszą być niedostępne dla osób mających dostęp do komputera lub plików bazy danych.

Prawidłowe zdefiniowanie powodu, dla którego chcemy korzystać z zaszyfrowanej bazy danych, pomaga w podjęciu decyzji co do architektury aplikacji. W szczególności może mieć wpływ na sposób tworzenia, uzyskiwania i przechowywania klucza szyfrowania dla bazy danych. Więcej informacji na temat tych zagadnień zawiera sekcja Uwarunkowania szyfrowania bazy danych .

Alternatywnym dla zaszyfrowanej bazy danych mechanizmem zapewnienia poufności danych jest użycie zaszyfrowanego magazynu lokalnego . Zaszyfrowany magazyn lokalny umożliwia przechowywanie pojedynczej wartości ByteArray przy użyciu klucza typu String. Dostęp do takiej wartości ma tylko ta aplikacja AIR, która ją zapisała, i tylko na komputerze, na którym wartość jest przechowywana. W przypadku zaszyfrowanego magazynu lokalnego nie ma potrzeby tworzenia własnego klucza szyfrowania. Z tego względu zaszyfrowany magazyn lokalny jest najprostszym mechanizmem przechowywania pojedynczej wartości lub zestawu wartości, które można w prosty sposób zakodować w obiekcie ByteArray. Zaszyfrowana baza danych najlepiej nadaje się do przechowywania większych zestawów danych, gdy pożądane jest przechowywanie danych w postaci strukturyzowanej i możliwość kierowania zapytań do bazy danych. Więcej informacji na temat korzystania z zaszyfrowanego magazynu lokalnego zawiera sekcja Zaszyfrowany magazyn lokalny .

Tworzenie zaszyfrowanej bazy danych

Aby skorzystać z szyfrowania bazy danych, plik bazy danych należy zaszyfrować już na etapie jego tworzenia. Bazy danych utworzonej jako niezaszyfrowanej nie można później zaszyfrować. Podobnie, zaszyfrowanej bazy danych nie można później przekształcić w niezaszyfrowaną. W razie potrzeby można zmienić klucz szyfrowania zaszyfrowanej bazy danych. Szczegółowe informacje zawiera sekcja Zmiana klucza szyfrowania bazy danych . Jeśli istnieje już niezaszyfrowana baza danych, a chcemy skorzystać z szyfrowania bazy danych, można utworzyć nową zaszyfrowaną bazę danych i kopiować istniejącą strukturę tabel oraz dane do nowej bazy danych.

Tworzenie zaszyfrowanej bazy danych odbywa się niemal identycznie jak tworzenie niezaszyfrowanej bazy danych, co opisano w sekcji Tworzenie bazy danych . Najpierw tworzymy instancję klasy SQLConnection reprezentującą połączenie z bazą danych. Bazę danych można utworzyć, wywołując metodę open() lub metodę openAsync() obiektu SQLConnection, jako lokalizację bazy danych wskazując plik, który jeszcze nie istnieje. Jedyna różnica występująca przy tworzeniu zaszyfrowanej bazy danych polega na konieczności podania wartości parametru encryptionKey (piątego parametru metody open() i szóstego parametru metody openAsync() ).

Poprawną wartością parametru encryptionKey jest obiekt ByteArray zawierający dokładnie 16 bajtów.

Poniższe przykłady ilustrują tworzenie zaszyfrowanej bazy danych. Dla uproszczenia przykładów klucz szyfrowania został zapisany na stałe w kodzie aplikacji. Jednak w rzeczywistych zastosowaniach nie zaleca się stosowania tego rozwiązania, ponieważ nie jest ono bezpieczne.

var conn:SQLConnection = new SQLConnection(); 
     
var encryptionKey:ByteArray = new ByteArray(); 
encryptionKey.writeUTFBytes("Some16ByteString"); // This technique is not secure! 
     
// Create an encrypted database in asynchronous mode 
conn.openAsync(dbFile, SQLMode.CREATE, null, false, 1024, encryptionKey); 
     
// Create an encrypted database in synchronous mode 
conn.open(dbFile, SQLMode.CREATE, false, 1024, encryptionKey);

Przykład demonstrujący zalecany sposób generowania klucza szyfrowania zawiera sekcja Przykład: Generowanie i używanie klucza szyfrowania .

Nawiązywanie połączenia z zaszyfrowaną bazą danych

Podobnie jak w przypadku tworzenia zaszyfrowanej bazy danych, procedura otwierania połączenia z zaszyfrowaną bazą danych jest podobna do otwierania połączenia z bazą niezaszyfrowaną. Procedurę tę opisano bardziej szczegółowo w sekcji Połączenie z bazą danych . Należy użyć metody open() http://help.adobe.com/pl_PL/Flash/CS5/AS3LR/flash/data/SQLConnection.html#open() w celu otwarcia połączenia w trybie wykonywania synchronicznego lub metody openAsync() w celu otwarcia połączenia w trybie wykonywania asynchronicznego . Jedyna różnica polega na tym, że aby utworzyć zaszyfrowaną bazę danych, należy podać prawidłową wartość parametru encryptionKey (piątego parametru metody open() i szóstego parametru metody openAsync() ).

Jeśli podany klucz szyfrowania nie jest prawidłowy, zgłaszany jest błąd. W przypadku metody open() generowany jest wyjątek SQLError . W przypadku metody openAsync() obiekt SQLConnection wywołuje zdarzenie SQLErrorEvent , którego właściwość error zawiera obiekt SQLError . W obu przypadkach obiekt SQLError wygenerowany przez wyjątek ma właściwość errorID równą 3138. Ten identyfikator błędu odpowiada komunikatowi „Otwarty plik nie jest plikiem bazy danych”.

Poniższy przykład ilustruje otwieranie zaszyfrowanej bazy danych w trybie wykonywania asynchronicznego. W celu uproszczenia przykładu klucz szyfrowania został zapisany na stałe w kodzie aplikacji. Jednak w rzeczywistych zastosowaniach nie zaleca się stosowania tego rozwiązania, ponieważ nie jest ono bezpieczne.

import flash.data.SQLConnection; 
import flash.data.SQLMode; 
import flash.events.SQLErrorEvent; 
import flash.events.SQLEvent; 
import flash.filesystem.File; 
     
var conn:SQLConnection = new SQLConnection(); 
conn.addEventListener(SQLEvent.OPEN, openHandler); 
conn.addEventListener(SQLErrorEvent.ERROR, errorHandler); 
var dbFile:File = File.applicationStorageDirectory.resolvePath("DBSample.db"); 
     
var encryptionKey:ByteArray = new ByteArray(); 
encryptionKey.writeUTFBytes("Some16ByteString"); // This technique is not secure! 
     
conn.openAsync(dbFile, SQLMode.UPDATE, null, false, 1024, encryptionKey); 
     
function openHandler(event:SQLEvent):void 
{ 
    trace("the database opened successfully"); 
} 
     
function errorHandler(event:SQLErrorEvent):void 
{ 
    if (event.error.errorID == 3138) 
    { 
        trace("Incorrect encryption key"); 
    } 
    else 
    { 
        trace("Error message:", event.error.message); 
        trace("Details:", event.error.details); 
    } 
} 

Poniższy przykład ilustruje otwieranie zaszyfrowanej bazy danych w trybie wykonywania synchronicznego. W celu uproszczenia przykładu klucz szyfrowania został zapisany na stałe w kodzie aplikacji. Jednak w rzeczywistych zastosowaniach nie zaleca się stosowania tego rozwiązania, ponieważ nie jest ono bezpieczne.

import flash.data.SQLConnection; 
import flash.data.SQLMode; 
import flash.filesystem.File; 
     
var conn:SQLConnection = new SQLConnection(); 
var dbFile:File = File.applicationStorageDirectory.resolvePath("DBSample.db"); 
     
var encryptionKey:ByteArray = new ByteArray(); 
encryptionKey.writeUTFBytes("Some16ByteString"); // This technique is not secure! 
     
try 
{ 
    conn.open(dbFile, SQLMode.UPDATE, false, 1024, encryptionKey); 
    trace("the database was created successfully"); 
} 
catch (error:SQLError) 
{ 
    if (error.errorID == 3138) 
    { 
        trace("Incorrect encryption key"); 
    } 
    else 
    { 
        trace("Error message:", error.message); 
        trace("Details:", error.details); 
    } 
} 

Przykład demonstrujący zalecany sposób generowania klucza szyfrowania zawiera sekcja Przykład: Generowanie i używanie klucza szyfrowania .

Zmiana klucza szyfrowania bazy danych

Klucz zaszyfrowanej bazy danych można zmieniać po jej utworzeniu. Aby zmienić klucz szyfrowania bazy danych, należy najpierw otworzyć połączenie z bazą danych, tworząc instancję klasy SQLConnection i wywołując jej metodę open() lub openAsync() . Po nawiązaniu połączenia z bazą danych należy wywołać metodę reencrypt() , przekazując jako jej argument nowy klucz szyfrowania.

Podobnie jak w przypadku większości operacji na bazie danych, działanie metody reencrypt() jest różne w zależności od tego, czy połączenie z bazą danych jest nawiązane w trybie wykonywania synchronicznego, czy asynchronicznego. Jeśli do połączenia z bazą danych użyto metody open() , operacja reencrypt() działa asynchronicznie. Gdy operacja zostanie zakończona, następuje wznowienie wykonywania kodu od kolejnego wiersza:

var newKey:ByteArray = new ByteArray(); 
// ... generate the new key and store it in newKey 
conn.reencrypt(newKey);

Natomiast jeśli połączenie z bazą danych zostało otwarte przy użyciu metody openAsync() , operacja reencrypt() jest asynchroniczna. Wywołanie metody reencrypt() rozpoczyna proces ponownego szyfrowania. Po zakończeniu operacji obiekt SQLConnection wywołuje zdarzenie reencrypt . W celu wykrycia zakończenia operacji ponownego szyfrowania należy skorzystać z detektora zdarzeń:

var newKey:ByteArray = new ByteArray(); 
// ... generate the new key and store it in newKey 
     
conn.addEventListener(SQLEvent.REENCRYPT, reencryptHandler); 
     
conn.reencrypt(newKey); 
     
function reencryptHandler(event:SQLEvent):void 
{ 
    // save the fact that the key changed 
}

Operacja reencrypt() działa w ramach swojej własnej transakcji. Jeśli zostanie przerwana lub zakończy się niepowodzeniem (na przykład aplikacja zostanie zamknięta przed ukończeniem operacji), transakcja jest wycofywana. W takim wypadku w bazie danych nadal obowiązuje dotychczasowy klucz szyfrowania.

Metoda reencrypt() nie umożliwia przekształcenie bazy danych w niezaszyfrowaną. Przekazanie do metody reencrypt() klucza szyfrowania o wartości null lub niebędącego 16-bajtowym obiektem ByteArray spowoduje błąd.

Uwarunkowania szyfrowania bazy danych

W sekcji Zastosowania zaszyfrowanej bazy danych przedstawiono kilka przypadków, w których celowe byłoby użycie zaszyfrowanej bazy danych. Oczywiste jest, że w różnych zastosowaniach (przedstawionych oraz innych) obowiązują różne wymagania w zakresie poufności danych. Sposób włączenia mechanizmu szyfrowania do struktury aplikacji istotnie wpływa na skuteczność ochrony poufności danych w bazie. Na przykład, jeśli zaszyfrowana baza danych ma zapewniać poufność danych osobistych, nawet między różnymi użytkownikami tego samego komputera, to baza danych każdego użytkownika powinna mieć inny klucz szyfrowania. Aby uzyskać najwyższy poziom bezpieczeństwa, aplikacja może generować klucze na podstawie haseł wprowadzanych przez użytkowników. Powiązanie klucza szyfrowania z hasłem gwarantuje, że nawet jeśli inna osoba zdoła podszyć się pod konto użytkownika na komputerze, nadal nie będzie mieć dostępu do danych. Przeciwstawny skrajny przypadek to sytuacja, w której plik bazy danych powinien być czytelny dla wszystkich użytkowników naszej aplikacji, ale nie dla innych aplikacji. W takim przypadku każda zainstalowana kopia aplikacji musi mieć dostęp do współużytkowanego klucza szyfrowania.

Budowę aplikacji, a w szczególności technikę generowania kluczy szyfrowania, można dopasować do wymaganego poziomu poufności danych aplikacji. Na poniższej liście przedstawiono propozycje rozwiązań odpowiadających różnym poziomom poufności danych:

  • Aby baza danych była dostępna dla każdego użytkownika mającego dostęp do aplikacji na dowolnym komputerze, należy użyć jednego klucza dostępnego dla wszystkich instancji aplikacji. Na przykład przy pierwszym uruchomieniu aplikacja może pobrać współużytkowany klucz szyfrowania z serwera, korzystając z bezpiecznego protokołu, np. SSL. Następnie aplikacja może zapisać klucz w szyfrowanym magazynie lokalnym i korzystać z niego w przyszłości. Alternatywnym rozwiązaniem jest szyfrowanie danych poszczególnych użytkowników na komputerze i synchronizowanie danych ze zdalnym magazynem danych, na przykład serwerem, w celu zapewnienia przenośności danych między komputerami.

  • Aby baza danych była dostępna dla jednego konkretnego użytkownika na dowolnym komputerze, należy wygenerować klucz szyfrowania na podstawie informacji znanej tylko użytkownikowi (np. hasła). W szczególności do generowania klucza nie należy używać wartości powiązanej z konkretnym komputerem (np. przechowywanej w szyfrowanym magazynie lokalnym). Alternatywnym rozwiązaniem jest szyfrowanie danych poszczególnych użytkowników na komputerze i synchronizowanie danych ze zdalnym magazynem danych, np. serwerem, w celu zapewnienia przenośności danych między komputerami.

  • Aby baza danych była dostępna tylko dla jednej konkretnej osoby na jednym konkretnym komputerze, należy wygenerować klucz na podstawie hasła i wygenerowanego modyfikatora. Zastosowanie tej techniki ilustruje Przykład: Generowanie i używanie klucza szyfrowania .

Poniżej przedstawiono dodatkowe uwarunkowania dotyczące bezpieczeństwa, o których należy pamiętać, projektując aplikację korzystającą z zaszyfrowanej bazy danych.

  • System jest tylko tak bezpieczny, jak jego najsłabsze ogniwo. Jeśli klucz szyfrowania jest generowany na podstawie hasła wprowadzanego przez użytkownika, wskazane jest narzucenie minimalnej długości i złożoności hasła. Krótkie hasła składające się tylko z podstawowych znaków można szybko odgadnąć.

  • Kod źródłowy aplikacji AIR jest przechowywany na komputerze użytkownika w postaci zwykłego tekstu (w przypadku treści HTML) lub w formacie binarnym, który można jednak łatwo zdekompilować (w przypadku treści SWF). Ponieważ kod źródłowy jest dostępny, należy pamiętać o dwóch kwestiach:

    • Nigdy nie należy wpisywać klucza szyfrowania na stałe w kodzie aplikacji.

    • Należy zawsze zakładać, że osoba nieuprawniona będzie w stanie odtworzyć technikę użytą do wygenerowania klucza szyfrowania (np. generator liczb pseudolosowych lub konkretny algorytm mieszający).

  • W środowisku AIR do szyfrowania baz danych używany jest algorytm Advanced Encryption Standard (AES) z licznikiem w trybie CBC-MAC (CCM). Aby ten szyfr był bezpieczny, klucz wprowadzony przez użytkownika musi być połączony z wartością modyfikatora. Zastosowanie tej techniki ilustruje Przykład: Generowanie i używanie klucza szyfrowania .

  • Zaszyfrowanie bazy danych powoduje zaszyfrowanie wszystkich plików dyskowych używanych przez mechanizm serwera bazy danych w związku z daną bazą. Jednak mechanizm serwera bazy danych tymczasowo przechowuje pewne dane w pamięci podręcznej, aby przyspieszyć odczytywanie i zapisywanie danych w transakcjach. Wszystkie dane przechowywane w pamięci są niezaszyfrowane. Jeśli osoba nieuprawniona zdoła uzyskać dostęp do pamięci wykorzystywanej przez aplikację AIR, np. za pomocą debugera, to uzyska również dostęp do niezaszyfrowanych danych pochodzących z otwartej bazy.

Przykład: Generowanie i używanie klucza szyfrowania

Ta przykładowa aplikacja ilustruje jedną z technik generowania klucza szyfrowania. Aplikacja została zaprojektowana w taki sposób, aby zapewniała najwyższy poziom poufności i bezpieczeństwa danych użytkowników. Jednym z ważnych aspektów ochrony danych poufnych jest wymuszenie na użytkowniku wprowadzenia hasła za każdym razem, gdy aplikacja łączy się z bazą danych. Dlatego, co ilustruje omawiany przykład, aplikacja wymagająca tak wysokiego poziomu bezpieczeństwa nie powinna nigdy bezpośrednio przechowywać klucza szyfrowania bazy danych.

Aplikacja składa się z dwóch części: klasy ActionScript generującej klucz szyfrowania (klasa EncryptionKeyGenerator) oraz prostego interfejsu użytkownika, który ilustruje zastosowanie tej klasy. Pełny kod źródłowy zawiera sekcja Kompletny przykładowy kod ilustrujący generowanie i używanie klucza szyfrowania .

Użycie klasy EncryptionKeyGenerator w celu uzyskania bezpiecznego klucza szyfrowania

Aby używać klasy EncryptionKeyGenerator we własnych aplikacjach, nie jest konieczna znajomość zasad jej wewnętrznego działania. Czytelnicy zainteresowani szczegółowym algorytmem generowania klucza szyfrowania dla bazy danych znajdą odpowiednie informacje w sekcji Omówienie klasy EncryptionKeyGenerator .

Aby użyć klasy EncryptionKeyGenerator we własnej aplikacji, należy postępować według poniższej procedury:

  1. Klasę EncryptionKeyGenerator można pobrać jako kod źródłowy lub skomplikowany plik SWC. Klasa EncryptionKeyGenerator wchodzi w skład biblioteki podstawowej języka ActionScript 3.0 (as3corelib). Jest to projekt open source. Pakiet as3corelib, wraz z kodem źródłowym i dokumentacją jest dostępny do pobrania. Ze strony projektu można również pobrać pliki SWC lub pliki z kodem źródłowym.

  2. Umieść kod źródłowy klasy EncryptionKeyGenerator (lub plik SWC biblioteki as3corelib) w miejscu dostępnym i znanym w kodzie aplikacji.

  3. W kodzie aplikacji dodaj instrukcję import w celu zaimportowania klasy EncryptionKeyGenerator.

    import com.adobe.air.crypto.EncryptionKeyGenerator;
  4. Przed punktem kodu, w którym tworzona jest baza danych lub otwierane jest połączenie z bazą danych, dodaj kod tworzący instancję EncryptionKeyGenerator poprzez wywołanie konstruktora EncryptionKeyGenerator() .

    var keyGenerator:EncryptionKeyGenerator = new EncryptionKeyGenerator();
  5. Uzyskaj hasło od użytkownika:

    var password:String = passwordInput.text; 
     
    if (!keyGenerator.validateStrongPassword(password)) 
    { 
        // display an error message 
        return; 
    }

    Instancja klasy EncryptionKeyGenerator wykorzysta hasło jako podstawę dla klucza szyfrowania (co ilustruje następny krok). Instancja klasy EncryptionKeyGenerator testuje hasło pod kątem pewnych wymogów stawianych silnym hasłom. Jeśli test ten wypadnie niepomyślnie, zgłaszany jest błąd. W kodzie przykładowym widzimy, że możliwe jest sprawdzenie hasła zanim wystąpi ewentualny błąd; w tym celu należy wywołać metodę validateStrongPassword() obiektu EncryptionKeyGenerator. Ten sposób pozwala określić, czy hasło spełnia minimalne wymagania stawiane hasłom silnym, i uniknąć błędu.

  6. Wygeneruj klucz szyfrowania na podstawie hasła:

    var encryptionKey:ByteArray = keyGenerator.getEncryptionKey(password);

    Metoda getEncryptionKey() generuje i zwraca klucz szyfrowania (16-bajtowy obiekt ByteArray). Uzyskany klucz może posłużyć do utworzenia nowej zaszyfrowanej bazy danych lub do otwarcia istniejącej bazy.

    Metoda getEncryptionKey() ma jeden parametr wymagany, którym jest hasło uzyskane w kroku 5.

    Uwaga: Aby utrzymać najwyższy poziom bezpieczeństwa i poufności danych, aplikacja musi wymagać od użytkownika wprowadzenia hasła za każdym razem, gdy nawiązuje połączenie z bazą danych. Nie należy bezpośrednio przechowywać hasła użytkownika lub klucza szyfrowania bazy danych. Stwarzałoby to zagrożenie dla poufności danych. Zamiast tego, tak jak ilustruje to omawiany przykład, aplikacja powinna generować klucz szyfrowania na podstawie hasła zarówno przy tworzeniu bazy danych, jak i każdorazowo przy nawiązywaniu połączenia z bazą.

    Do metody getEncryptionKey() można również przekazać drugi (opcjonalny) parametr overrideSaltELSKey . Klasa EncryptionKeyGenerator generuje wartość losową (tzw. modyfikator ) wykorzystywaną jako część klucza szyfrowania. Aby możliwe było odtworzenie klucza szyfrowania, wartość modyfikatora jest przechowywana w zaszyfrowanym magazynie lokalnym aplikacji AIR. Domyślnie klasa EncryptionKeyGenerator używa konkretnego ciągu znaków jako klucza magazynu lokalnego. Choć jest to mało prawdopodobne, istnieje możliwość konfliktu między tym kluczem a innym kluczem używanym w aplikacji. Zamiast używać klucza domyślnego można zatem podać własny klucz zaszyfrowanego magazynu lokalnego. W takim wypadku własny klucz należy przekazać jako drugi parametr metody getEncryptionKey() , co zilustrowano poniżej:

    var customKey:String = "My custom ELS salt key"; 
    var encryptionKey:ByteArray = keyGenerator.getEncryptionKey(password, customKey);
  7. Utwórz lub otwórz bazę danych

    Mając do dyspozycji klucz zwrócony przez metodę getEncryptionKey() , kod może utworzyć nową zaszyfrowaną bazę danych lub podjąć próbę otwarcia istniejącej zaszyfrowanej bazy danych. W obu przypadkach używa się metody open() lub openAsync() klasy SQLConnection, tak jak opisano to w sekcjach Tworzenie zaszyfrowanej bazy danych i Nawiązywanie połączenia z zaszyfrowaną bazą danych .

    Przykładowa aplikacja otwiera bazę danych w trybie wykonywania asynchronicznego. Kod konfiguruje odpowiednie detektory zdarzeń i wywołuje metodę openAsync() , przekazując klucz szyfrowania jako ostatni argument:

    conn.addEventListener(SQLEvent.OPEN, openHandler); 
    conn.addEventListener(SQLErrorEvent.ERROR, openError); 
     
    conn.openAsync(dbFile, SQLMode.CREATE, null, false, 1024, encryptionKey);

    Metody detektorów zdarzeń wyrejestrowują detektory. Następnie wyświetlany jest komunikat o stanie informujący o tym, czy baza danych została utworzona, otwarta, czy też wystąpił błąd. W kodzie detektorów zdarzeń szczególną uwagę zwraca treść metody openError() . Instrukcja if w tej metodzie sprawdza, czy baza danych istnieje (co oznacza, że ma miejsce próba połączenia z istniejącą bazą danych) i czy identyfikator błędu jest równy stałej EncryptionKeyGenerator.ENCRYPTED_DB_PASSWORD_ERROR_ID . Jeśli oba te warunki są spełnione, prawdopodobnie hasło podane przez użytkownika jest nieprawidłowe. (Jednak spełnienie obu warunków może też oznaczać, że podany plik nie jest plikiem bazy danych). Oto kod sprawdzający identyfikator błędu:

    if (!createNewDB && event.error.errorID == EncryptionKeyGenerator.ENCRYPTED_DB_PASSWORD_ERROR_ID) 
    { 
        statusMsg.text = "Incorrect password!"; 
    } 
    else 
    { 
        statusMsg.text = "Error creating or opening database."; 
    }

    Pełny kod przykładowych detektorów zdarzeń zawiera sekcja Kompletny przykładowy kod ilustrujący generowanie i używanie klucza szyfrowania .

Kompletny przykładowy kod ilustrujący generowanie i używanie klucza szyfrowania

Poniżej przedstawiono kompletny kod aplikacji przykładowej „Generowanie i używanie klucza szyfrowania”. Kod składa się z dwóch części.

W przykładzie zastosowano klasę EncryptionKeyGenerator do tworzenia klucza szyfrowania na podstawie hasła. Klasa EncryptionKeyGenerator wchodzi w skład biblioteki podstawowej języka ActionScript 3.0 (as3corelib). Jest to projekt open source. Pakiet as3corelib, wraz z kodem źródłowym i dokumentacją jest dostępny do pobrania. Ze strony projektu można również pobrać pliki SWC lub pliki z kodem źródłowym.

Przykład w środowisku Flex

Plik MXML aplikacji zawiera kod źródłowy prostej aplikacji, która tworzy lub otwiera połączenie z zaszyfrowaną bazą danych.

<?xml version="1.0" encoding="utf-8"?> 
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" creationComplete="init();"> 
    <mx:Script> 
        <![CDATA[ 
            import com.adobe.air.crypto.EncryptionKeyGenerator; 
             
            private const dbFileName:String = "encryptedDatabase.db"; 
             
            private var dbFile:File; 
            private var createNewDB:Boolean = true; 
            private var conn:SQLConnection; 
             
            // ------- Event handling ------- 
             
            private function init():void 
            { 
                conn = new SQLConnection(); 
                dbFile = File.applicationStorageDirectory.resolvePath(dbFileName); 
                if (dbFile.exists) 
                { 
                    createNewDB = false; 
                    instructions.text = "Enter your database password to open the encrypted database."; 
                    openButton.label = "Open Database"; 
                } 
            } 
             
            private function openConnection():void 
            { 
                var password:String = passwordInput.text; 
                 
                var keyGenerator:EncryptionKeyGenerator = new EncryptionKeyGenerator(); 
                 
                if (password == null || password.length <= 0) 
                { 
                    statusMsg.text = "Please specify a password."; 
                    return; 
                } 
                 
                if (!keyGenerator.validateStrongPassword(password)) 
                { 
                    statusMsg.text = "The password must be 8-32 characters long. It must contain at least one lowercase letter, at least one uppercase letter, and at least one number or symbol."; 
                    return; 
                } 
                 
                passwordInput.text = ""; 
                passwordInput.enabled = false; 
                openButton.enabled = false; 
                 
                var encryptionKey:ByteArray = keyGenerator.getEncryptionKey(password); 
                 
                conn.addEventListener(SQLEvent.OPEN, openHandler); 
                conn.addEventListener(SQLErrorEvent.ERROR, openError); 
                  
                conn.openAsync(dbFile, SQLMode.CREATE, null, false, 1024, encryptionKey); 
            } 
             
            private function openHandler(event:SQLEvent):void 
            { 
                conn.removeEventListener(SQLEvent.OPEN, openHandler); 
                conn.removeEventListener(SQLErrorEvent.ERROR, openError); 
                  
                statusMsg.setStyle("color", 0x009900); 
                if (createNewDB) 
                { 
                    statusMsg.text = "The encrypted database was created successfully."; 
                } 
                else 
                { 
                    statusMsg.text = "The encrypted database was opened successfully."; 
                } 
            } 
              
            private function openError(event:SQLErrorEvent):void 
            { 
                conn.removeEventListener(SQLEvent.OPEN, openHandler); 
                conn.removeEventListener(SQLErrorEvent.ERROR, openError); 
     
                if (!createNewDB && event.error.errorID == EncryptionKeyGenerator.ENCRYPTED_DB_PASSWORD_ERROR_ID) 
                { 
                    statusMsg.text = "Incorrect password!"; 
                } 
                else 
                { 
                    statusMsg.text = "Error creating or opening database."; 
                } 
            } 
        ]]> 
    </mx:Script> 
    <mx:Text id="instructions" text="Enter a password to create an encrypted database. The next time you open the application, you will need to re-enter the password to open the database again." width="75%" height="65"/> 
    <mx:HBox> 
        <mx:TextInput id="passwordInput" displayAsPassword="true"/> 
        <mx:Button id="openButton" label="Create Database" click="openConnection();"/> 
    </mx:HBox> 
    <mx:Text id="statusMsg" color="#990000" width="75%"/> 
</mx:WindowedApplication>

Przykład dla programu Flash Professional

Plik FLA aplikacji zawiera kod źródłowy prostej aplikacji, która tworzy lub otwiera połączenie z zaszyfrowaną bazą danych. Plik FLA zawiera cztery składniki umieszczone na stole montażowym:

Nazwa instancji

Typ składnika

Opis

instructions

Etykieta

Zawiera instrukcje dla użytkownika.

passwordInput

TextInput

Pole wejściowe, w którym użytkownik wprowadza hasło.

openButton

Przycisk

Przycisk, który użytkownik klika po wprowadzeniu hasła.

statusMsg

Label

Wyświetlane komunikaty o stanie (powodzenie lub niepowodzenie).

Kod aplikacji jest zdefiniowany w klatce kluczowej będącej klatką nr 1 na głównej osi czasu. Kod aplikacji przedstawiono poniżej:

import com.adobe.air.crypto.EncryptionKeyGenerator; 
     
const dbFileName:String = "encryptedDatabase.db"; 
     
var dbFile:File; 
var createNewDB:Boolean = true; 
var conn:SQLConnection; 
     
init(); 
     
// ------- Event handling ------- 
     
function init():void 
{ 
    passwordInput.displayAsPassword = true; 
    openButton.addEventListener(MouseEvent.CLICK, openConnection); 
    statusMsg.setStyle("textFormat", new TextFormat(null, null, 0x990000)); 
     
    conn = new SQLConnection(); 
    dbFile = File.applicationStorageDirectory.resolvePath(dbFileName); 
     
    if (dbFile.exists) 
    { 
        createNewDB = false; 
        instructions.text = "Enter your database password to open the encrypted database."; 
        openButton.label = "Open Database"; 
    } 
    else 
    { 
        instructions.text = "Enter a password to create an encrypted database. The next time you open the application, you will need to re-enter the password to open the database again."; 
        openButton.label = "Create Database"; 
    } 
} 
     
function openConnection(event:MouseEvent):void 
{ 
    var keyGenerator:EncryptionKeyGenerator = new EncryptionKeyGenerator(); 
     
    var password:String = passwordInput.text; 
     
    if (password == null || password.length <= 0) 
    { 
        statusMsg.text = "Please specify a password."; 
        return; 
    } 
     
    if (!keyGenerator.validateStrongPassword(password)) 
    { 
        statusMsg.text = "The password must be 8-32 characters long. It must contain at least one lowercase letter, at least one uppercase letter, and at least one number or symbol."; 
        return; 
    } 
     
    passwordInput.text = ""; 
    passwordInput.enabled = false; 
    openButton.enabled = false; 
     
    var encryptionKey:ByteArray = keyGenerator.getEncryptionKey(password); 
     
    conn.addEventListener(SQLEvent.OPEN, openHandler); 
    conn.addEventListener(SQLErrorEvent.ERROR, openError); 
     
    conn.openAsync(dbFile, SQLMode.CREATE, null, false, 1024, encryptionKey); 
} 
     
function openHandler(event:SQLEvent):void 
{ 
    conn.removeEventListener(SQLEvent.OPEN, openHandler); 
    conn.removeEventListener(SQLErrorEvent.ERROR, openError); 
     
    statusMsg.setStyle("textFormat", new TextFormat(null, null, 0x009900)); 
    if (createNewDB) 
    { 
        statusMsg.text = "The encrypted database was created successfully."; 
    } 
    else 
    { 
        statusMsg.text = "The encrypted database was opened successfully."; 
    } 
} 
 
function openError(event:SQLErrorEvent):void 
{ 
    conn.removeEventListener(SQLEvent.OPEN, openHandler); 
    conn.removeEventListener(SQLErrorEvent.ERROR, openError); 
     
    if (!createNewDB && event.error.errorID == EncryptionKeyGenerator.ENCRYPTED_DB_PASSWORD_ERROR_ID) 
    { 
        statusMsg.text = "Incorrect password!"; 
    } 
    else 
    { 
        statusMsg.text = "Error creating or opening database."; 
    } 
}

Omówienie klasy EncryptionKeyGenerator

Znajomość wewnętrznego mechanizmu działania klasy EncryptionKeyGenerator nie jest niezbędna do wykorzystania tej klasy w celu utworzenia bezpiecznego klucza szyfrowania bazy danych. Procedurę użycia klasy wyjaśniono w sekcji Użycie klasy EncryptionKeyGenerator w celu uzyskania bezpiecznego klucza szyfrowania . Jednak dla wielu czytelników przydatne może okazać się zrozumienie technik zastosowanych w tej klasie. Na przykład możliwe jest zaadaptowanie klasy do nietypowych potrzeb lub wykorzystanie niektórych z zastosowanych w niej technik w sytuacjach, w których wymagany jest inny poziom poufności danych.

Klasa EncryptionKeyGenerator wchodzi w skład biblioteki podstawowej języka ActionScript 3.0 (as3corelib). Jest to projekt open source. Pakiet as3corelib wraz z kodem źródłowym i dokumentacją jest dostępny do pobrania. Możliwe jest także przeglądanie kodu źródłowego na stronie projektu lub pobranie go w celu przeanalizowania z pomocą niniejszych wyjaśnień.

Gdy kod tworzy instancję klasy EncryptionKeyGenerator i wywołuje jej metodę getEncryptionKey() , podejmowany jest szereg działań zmierzających do zapewnienia dostępu do danych wyłącznie uprawnionemu użytkownikowi. Proces przebiega tak samo niezależnie od tego, czy klucz jest generowany na podstawie hasła przed utworzeniem bazy danych, czy też jest odtwarzany w celu otwarcia bazy danych.

Uzyskanie i sprawdzenie poprawności silnego hasła

W wywołaniu metody getEncryptionKey() kod aplikacji przekazuje hasło jako parametr. Hasło stanowi podstawę dla klucza szyfrowania. Wykorzystanie w kluczu informacji znanej tylko użytkownikowi gwarantuje, że dostęp do danych w bazie będzie miała wyłącznie osoba znająca hasło. Nawet jeśli inna osoba uzyska dostęp do konta użytkownika na komputerze, nie dostanie się do bazy danych, jeśli nie zna hasła. Aby uzyskać maksymalny poziom bezpieczeństwa, aplikacja nigdy nie przechowuje hasła.

Kod aplikacji tworzy instancję klasy EncryptionKeyGenerator i wywołuje jej metodę getEncryptionKey() , przekazując jako argument hasło wprowadzone przez użytkownika (w tym przykładzie w zmiennej password ):

var keyGenerator:EncryptionKeyGenerator = new EncryptionKeyGenerator(); 
var encryptionKey:ByteArray = keyGenerator.getEncryptionKey(password);

Pierwszym krokiem wykonywanym w klasie EncryptionKeyGenerator po wywołaniu metody getEncryptionKey() jest sprawdzenie, czy hasło wprowadzone przez użytkownika spełnia wymagania stawiane hasłom silnym. Klasa EncryptionKeyGenerator wymaga hasła o długości od 8 do 32 znaków. Hasło musi zawierać kombinację wielkich i małych liter oraz co najmniej jedną cyfrę lub symbol.

Wyrażenie regularne sprawdzające zgodność z tymi wymogami jest zdefiniowane jako stała o nazwie STRONG_PASSWORD_PATTERN :

private static const STRONG_PASSWORD_PATTERN:RegExp = /(?=^.{8,32}$)((?=.*\d)|(?=.*\W+))(?![.\n])(?=.*[A-Z])(?=.*[a-z]).*$/;

Kod sprawdzający hasło znajduje się w metodzie validateStrongPassword() klasy EncryptionKeyGenerator. Oto wspomniany kod:

public function vaidateStrongPassword(password:String):Boolean 
{ 
    if (password == null || password.length <= 0) 
    { 
        return false; 
    } 
     
    return STRONG_PASSWORD_PATTERN.test(password)) 
}

Metoda getEncryptionKey() wywołuje wewnętrznie metodę validateStrongPassword() klasy EncryptionKeyGenerator, a jeśli hasło nie spełnia wymogów, generuje wyjątek. Metoda validateStrongPassword() jest metodą publiczną, a zatem istnieje możliwość sprawdzenia hasła bez wywoływania metody getEncryptionKey() i uniknięcia w ten sposób zgłoszenia błędu.

Rozwijanie hasła do 256 bitów

Na dalszych etapach procesu hasło musi mieć długość 256 bitów. Kod jest napisany w taki sposób, że użytkownik nie musi wprowadzać hasła o długości dokładnie 256 bitów (32 znaków); w razie potrzeby dłuższe hasło jest tworzone przez powtórzenie znaków hasła użytkownika.

Metoda getEncryptionKey() wywołuje metodę concatenatePassword() w celu uzyskania hasło o wymaganej długości.

var concatenatedPassword:String = concatenatePassword(password);

Oto kod metody concatenatePassword() :

private function concatenatePassword(pwd:String):String 
{ 
    var len:int = pwd.length; 
    var targetLength:int = 32; 
     
    if (len == targetLength) 
    { 
        return pwd; 
    } 
     
    var repetitions:int = Math.floor(targetLength / len); 
    var excess:int = targetLength % len; 
     
    var result:String = ""; 
     
    for (var i:uint = 0; i < repetitions; i++) 
    { 
        result += pwd; 
    } 
     
    result += pwd.substr(0, excess); 
     
    return result; 
}

Jeśli hasło jest krótsze niż 256 bitów, jest konkatenowane samo ze sobą w celu uzyskania długości 256 bitów. Jeśli 256 nie jest dokładną wielokrotnością długości hasła, ostatnie powtórzenie jest skracane w celu uzyskania dokładnie tej liczby bitów.

Generowanie lub pobieranie 256-bitowego modyfikatora

Następnym krokiem jest uzyskanie 256-bitowej wartości modyfikatora, która na późniejszym etapie zostanie połączona z hasłem. Modyfikator (ang. salt, dosłownie „sól”, czyli przyprawa) to wartość losowa dodawana do wartości wprowadzonej przez użytkownika lub w inny sposób z nią łączona. Połączenie modyfikatora z hasłem gwarantuje, że nawet jeśli użytkownik w charakterze hasła poda zwykłe słowo występujące w słownikach, kombinacja hasło+modyfikator będzie wartością losową. Ta losowość chroni przed tzw. atakami słownikowymi, w których osoba atakująca próbuje odgadnąć hasło, próbując użyć kolejnych słów z listy. Ponadto wygenerowanie modyfikatora i zapisanie go w szyfrowanym magazynie lokalnym wiąże jego wartość z kontem użytkownika na komputerze, na którym znajduje się plik bazy danych.

Jeśli aplikacja wywołuje metodę getEncryptionKey() po raz pierwszy, tworzona jest losowa 256-bitowa wartość modyfikatora. W przeciwnym razie modyfikator jest ładowany z szyfrowanego magazynu lokalnego.

Modyfikator jest przechowywany w zmiennej o nazwie salt . W celu sprawdzenia, czy modyfikator został już utworzony, podejmowana jest próba załadowania go z szyfrowanego magazynu lokalnego:

var salt:ByteArray = EncryptedLocalStore.getItem(saltKey); 
if (salt == null) 
{ 
    salt = makeSalt(); 
    EncryptedLocalStore.setItem(saltKey, salt); 
}

W przypadku tworzenia nowego modyfikatora metoda makeSalt() generuje 256-bitową wartość losową. Ponieważ docelowo wartość ta ma być zapisana w szyfrowanym magazynie lokalnym, jest generowana jako obiekt ByteArray. Metoda makeSalt() używa metody Math.random() do wygenerowania wartości losowej. Metoda Math.random() nie umożliwia wygenerowania 256 bitów naraz. Dlatego zastosowano pętlę, w której metoda Math.random() wywoływana jest osiem razy. Za każdym razem generowana jest wartość losowa z przedziału od 0 do 4294967295 (maksymalna wartość typu uint). Typu uint użyto jako najdogodniejszego, ponieważ wartości tego typu mają dokładnie po 32 bity. Zapisanie ośmiu wartości uint w obiekcie ByteArray prowadzi do wygenerowania wartości 256-bitowej. Oto kod metody makeSalt() :

private function makeSalt():ByteArray 
{ 
    var result:ByteArray = new ByteArray; 
     
    for (var i:uint = 0; i < 8; i++) 
    { 
        result.writeUnsignedInt(Math.round(Math.random() * uint.MAX_VALUE)); 
    } 
     
    return result; 
}

Do zapisania modyfikatora w szyfrowanym magazynie lokalnym lub pobrania go z magazynu potrzebny jest klucz (ciąg znaków), pod którym modyfikator jest zapisany. Bez znajomości klucza nie można odczytać wartości modyfikatora. W tej sytuacji klucza szyfrowania nie dałoby się odtworzyć każdorazowo przy otwieraniu bazy danych. Domyślnie klasa EncryptionKeyGenerator używa wstępnie zdefiniowanego klucza magazynu, który jest zdefiniowany w stałej SALT_ELS_KEY . Zamiast używać klucza domyślnego, aplikacja może w wywołaniu metody getEncryptionKey() podać własny klucz magazynu. Domyślny lub podany przez aplikację klucz magazynu identyfikujący modyfikator jest przechowywany w zmiennej saltKey . Zmienna ta jest używana w wywołaniach metod EncryptedLocalStore.setItem() i EncryptedLocalStore.getItem() , tak jak przedstawiono to powyżej.

Łączenie 256-bitowego hasła i modyfikatora przy użyciu operatora XOR

Teraz dla kodu jest już dostępne i 256-bitowe hasło, i 256-bitowy modyfikator. Następnie wykonywana jest operacja XOR w celu połączenia modyfikatora ze skonkatenowanym hasłem w pojedynczą wartość. Wynikiem zastosowania tej techniki jest 256-bitowe hasło zawierające znaki z całego dostępnego zestawu. Znaki wynikowego hasła będą pochodzić z całego zestawu znaków, nawet jeśli hasło wprowadzone przez użytkownika zawierało w większości znaki alfanumeryczne. Ten dodatkowy czynnik losowy skutecznie powiększa zbiór możliwych haseł (utrudniając złamanie hasła), a jednocześnie eliminuje konieczność ręcznego wprowadzania długich i skomplikowanych ciągów znaków.

Wynik operacji XOR jest zapisywany w zmiennej unhashedKey . Właściwa operacja bitowej różnicy symetrycznej (XOR) na dwóch wartościach wykonywana jest w metodzie xorBytes() :

var unhashedKey:ByteArray = xorBytes(concatenatedPassword, salt);

Bitowy operator XOR ( ^ ) działa na dwóch wartościach typu uint i zwraca wartość typu uint. (Wartości typu uint są 32-bitowe). Argumenty przekazywane do metody xorBytes() należą do typu String (hasło) i ByteArray (modyfikator). Dlatego w kodzie zastosowano pętlę, która w każdym przebiegu wyodrębnia po 32 bity z każdego z argumentów przeznaczonych do wykonania operacji XOR.

private function xorBytes(passwordString:String, salt:ByteArray):ByteArray 
{ 
    var result:ByteArray = new ByteArray(); 
     
    for (var i:uint = 0; i < 32; i += 4) 
    { 
        // ... 
    } 
     
    return result; 
}

Wewnątrz pętli wyodrębniane są pierwsze 32 bity (4 bajty) parametru passwordString . Te bity są wyodrębniane i konwertowane na wartość typu uint ( o1 ) w dwóch etapach. Najpierw metoda charCodeAt() pobiera wartość liczbową każdego ze znaków. Następnie wartość znaku jest przesuwana na odpowiednią pozycję w ramach wartości uint przy użyciu operatora przesunięcia bitowego w lewo << ), a przesunięta wartość jest dodawana do o1 . Na przykład pierwszy znak ( i ) staje się pierwszymi 8 bitami po zastosowaniu operatora przesunięcia bitowego w lewo ( << ) o 24 bity i przypisaniu uzyskanej wartości do zmiennej o1 . Drugi znak (i + 1 ) staje się drugą grupą 8 bitów po przesunięciu jego wartości w lewo o 16 bitów i dodaniu wyniku do o1 . Wartości trzeciego i czwartego znaku są dodawane w ten sam sposób.

        // ... 
         
        // Extract 4 bytes from the password string and convert to a uint 
        var o1:uint = passwordString.charCodeAt(i) << 24; 
        o1 += passwordString.charCodeAt(i + 1) << 16; 
        o1 += passwordString.charCodeAt(i + 2) << 8; 
        o1 += passwordString.charCodeAt(i + 3); 
         
        // ...

Zmienna o1 zawiera teraz 32 bity z parametru passwordString . Następnie z parametru salt wyodrębniane są 32 bity poprzez wywołanie metody readUnsignedInt() tego parametru. Wyodrębnione 32 bity są zapisywane w zmiennej o2 typu uint.

        // ... 
         
        salt.position = i; 
        var o2:uint = salt.readUnsignedInt(); 
         
        // ...

Na koniec dwie wartości 32-bitowe (uint) są łączone za pomocą operatora XOR, a wynik jest zapisywany w obiekcie ByteArray o nazwie result .

        // ... 
         
        var xor:uint = o1 ^ o2; 
        result.writeUnsignedInt(xor); 
        // ...

Po zakończeniu pętli zwracany jest obiekt ByteArray zawierający wynik operacji XOR.

        // ... 
    } 
     
    return result; 
}

Wymieszanie klucza

Następnym krokiem po połączeniu skonkatenowanego hasła i modyfikatora jest dodatkowe zabezpieczenie wyniku poprzez wymieszanie go przy użyciu algorytmu mieszającego SHA-256. Wymieszanie wartości utrudnia potencjalnemu agresorowi odtworzenie sposobu, w jaki została utworzona.

Na tym etapie w kodzie jest dostępny obiekt ByteArray o nazwie unhashedKey zawierający skonkatenowane hasło połączone z modyfikatorem. Projekt biblioteki podstawowej ActionScript 3.0 (as3corelib) obejmuje klasę SHA256 wchodząca w skład pakietu com.adobe.crypto. Metoda SHA256.hashBytes() wykonuje mieszanie na obiekcie ByteArray zgodnie z algorytmem SHA-256 i zwraca obiekt String zawierający 256-bitowy wynik mieszania w formie liczby szesnastkowej. W klasie EncryptionKeyGenerator klasa SHA256 jest używana do mieszania klucza:

var hashedKey:String = SHA256.hashBytes(unhashedKey);

Wyodrębnienie klucza szyfrowania z wyniku mieszania

Klucz szyfrowania musi być obiektem ByteArray o długości dokładnie 16 bajtów (128 bitów). Wynik działania algorytmu mieszającego SHA-256 ma zawsze długość 256 bitów. Dlatego ostatnim etapem będzie wybranie z wyniku mieszania 128 bitów, które posłużą za faktyczny klucz szyfrowania.

W klasie EncryptionKeyGenerator klucz jest skracany do 128 bitów w wyniku wywołania metody generateEncryptionKey() . Wynik tej metod jest zwracany jako wynik metody getEncryptionKey() :

var encryptionKey:ByteArray = generateEncryptionKey(hashedKey); 
return encryptionKey;

Kluczem szyfrowania nie musi być pierwsze 128 bitów. Równie dobrze można wybrać ciąg bitów rozpoczynający się w arbitralnie określonym punkcie, co drugi bit lub sekwencję bitów określoną w inny sposób. Ważne jest jedynie, aby wybrać 128 odrębnych bitów i za każdym razem używać tych samych 128 bitów.

W prezentowanym przypadku metoda generateEncryptionKey() jako klucz szyfrowania wybiera ciąg bitów rozpoczynający się od 18. bajtu. Jak już wcześniej wspomniano, klasa SHA256 zwraca obiekt String zawierający 256-bitowy wynik mieszania zapisany w postaci liczby szesnastkowej. Pojedynczy blok 128 bitów składa się ze zbyt wielu bajtów, aby można go było dodać do obiektu ByteArray w jednym kroku. Dlatego zastosowano pętlę for wyodrębniającą znaki z ciągu cyfr szesnastkowych, konwertującą je na wartości liczbowe i dodającą do obiektu ByteArray. Ciąg znaków będący wynikiem działania algorytmu SHA-256 ma długość 64 znaków. Ciąg 128 bitów odpowiada 32 znakom ciągu, a każdy znak reprezentuje 4 bity. Najmniejszą jednostką danych, jaką można dodać do obiektu ByteArray, jest jeden bajt (8 bitów), co odpowiada dwóm znakom w obiekcie String o nazwie hash . Dlatego pętla odlicza od 0 do 31 (32 znaki) z przyrostem 2 znaków.

Wewnątrz pętli najpierw określana jest początkowa pozycja bieżącej pary znaków. Ponieważ wybrany zakres zaczyna się od znaku o indeksie 17 (18. bajtu), zmiennej position jest przypisywana bieżąca wartość iteratora ( i ) plus 17. Do wyodrębnienia dwóch znaków z bieżącej pozycji używana jest metoda substr() . Znaki te są przechowywane w zmiennej hex . Następnie za pomocą metody parseInt() ciąg znaków hex jest konwertowany na dziesiętną wartość całkowitą. Wartość ta jest zapisywana w zmiennej byte typu int. Na koniec wartość byte jest dodawana do obiektu ByteArray o nazwie result przy użyciu metody writeByte() tego obiektu. Po zakończeniu pętli obiekt ByteArray result zawiera 16 batów i jest gotowy do użycia w charakterze klucza szyfrowania bazy danych.

private function generateEncryptionKey(hash:String):ByteArray 
{ 
    var result:ByteArray = new ByteArray(); 
     
    for (var i:uint = 0; i < 32; i += 2) 
    { 
        var position:uint = i + 17; 
        var hex:String = hash.substr(position, 2); 
        var byte:int = parseInt(hex, 16); 
        result.writeByte(byte); 
    } 
     
    return result; 
}