58
Inżynieria
oprogramowania
www.sdjournal.org
Software Developer’s Journal 9/2006
Data Protection API
i .NET Framework 2.0
B
rzmi groźnie? Bo jest groźnie kiedy firmy two-
rzą oprogramowanie przechowujące pouf-
ne informacje o znaczeniu krytycznym w nie-
zaszyfrowanej postaci, klucze i hasła jawnym tekstem
w pamięci, ba również w rejestrze systemowym i pli-
kach konfiguracyjnych ! Bardziej świadomi włączają
w funkcjonalność swoich aplikacji mechanizmy szy-
frujące – owszem. Cóż jednak z tego jeśli po głębszej
analizie okazuje się iż klucze użyte podczas szyfrowa-
nia odnaleźć możemy w przestrzeni adresowej działa-
jącego procesu bądź po prostu w kodzie aplikacji? Dla
średnio rozgarniętego crakera wystarczającym narzę-
dziem będzie debugger, zrzut pamięci procesu (ang.
memory dump) i nieco czasu aby ustalić algorytm szy-
frowania, dokonać ekstrakcji klucza szyfrującego a na-
stępnie rozszyfrować informację. Poziom bezpieczeń-
stwa wzrasta gdy aplikacja do wygenerowania klucza
szyfrującego używa hasła wprowadzanego każdorazo-
wo przez użytkownika. Takie rozwiązanie nie zawsze
jest efektywne. Poza tym powstaje możliwość ataku
mającego na celu przejęcie wprowadzanego hasła –
za pomocą tych samych technik o których wspomnia-
no wcześniej. Jeśli atak taki się powiedzie a użytkownik
stosował dane hasło również do ochrony innych zaso-
bów – skutki mogą być smutniejsze niż osiołek z „Przy-
gód Kubusia Puchatka”.
Mocy przybywaj!
Moc w postaci Data Protection API przybyła dość
dawno temu bo już razem z systemem operacyjnym
Windows 2000. DPAPI rozszerzone zostało w kolej-
nych wersjach Windows lecz dostęp z poziomu plat-
formy .NET możliwy był jedynie poprzez międzyplat-
formowe (PInvoke) wywołania kodu niezarządzane-
go. Sytuację odmieniło nadejście nowej wersji .NET
Framework 2.0 posiadającej już wsparcie dla me-
chanizmu DPAPI. W przestrzeni nazw
System.Secu-
rity.Cryptography
pojawiły się dwie nowe klasy:
Pro-
tectedData
oraz
ProtectedMemory
i stała się światłość.
Ale co to jest…
Zaczynajmy zatem od początku. DPAPI jest interfej-
sem programistycznym umożliwiającym bezpiecz-
ne szyfrowanie oraz odszyfrowanie danych w oparciu
o algorytm szyfrujący wykorzystujący klucz symetrycz-
ny. Użycie takiego algorytmu oznacza iż do zaszyfro-
wania i odszyfrowania danego bloku danych służy ten
sam klucz. To z kolei oznacza iż zaszyfrowana informa-
cja będzie bezpieczna dopóki klucz nie zostanie pozna-
ny przez osoby niepowołane. Tak więc warunkiem za-
chowania bezpieczeństwa jest dobre zabezpieczenie
kluczy – i głównie to właśnie robi za nas DPAPI. Klu-
cze zabezpieczane są poprzez ich zaszyfrowanie algo-
rytmem opartym o hasło użytkownika. Domyślnie wy-
korzystywane jest w tym celu hasło użytkownika sys-
temu Windows z czego jasno wynika że DPAPI służy
do zabezpieczania lokalnych danych użytkownika da-
nej stacji roboczej, nie zaś jakiejkolwiek komunikacji po-
między użytkownikami czy komputerami w sieci. DPA-
PI nie odpowiada również za składowanie i przechowy-
wanie zaszyfrowanych informacji – zadanie to spoczy-
wa już na barkach programisty. W pewnych sytuacjach
możliwość odszyfrowania bloku danych przez inny pro-
ces tego samego użytkownika jest niepożądana, dlate-
go też aplikacja podczas szyfrowania może użyć do-
datkowego sekretnego ciągu bajtów zwanego „opcjo-
nalną entropią”.
Interakcja aplikacji użytkowej z DPAPI w maksy-
malnym skrócie wygląda następująco:
• aplikacja
„A”
uwierzytelniona w systemie jako
użytkownik
„U”
wysyła nie zaszyfrowany blok da-
nych do DPAPI;
• DPAPI szyfruje blok danych kluczem użytkowni-
ka
„U”
;
• DPAPI zwraca aplikacji
„A”
zaszyfrowany blok
który odszyfrować będą mogły tylko i wyłącznie
procesy uwierzytelnione jako użytkownik
„U”
.
DPAPI zapewnia również cykliczną wymianę kluczy na
nowe – co w przypadku sytuacji zdobycia jednego z klu-
czy przez osoby niepowołane zminimalizuje ryzyko od-
szyfrowania wszystkich poufnych danych. Standardowo
nowy klucz dla danego użytkownika generowany jest
co trzy miesiące, następnie jest zabezpieczany i skła-
Tomasz Leszczyński
Autor jest programistą z siedmioletnim doświadczeniem
zawodowym, współtwórcą grupy deweloperskiej T3
(http://www.t3.com.pl) projektującej systemy informatycz-
ne w oparciu o technologie i narzędzia firmy Microsoft.
Kontakt z autorem: leszczynski@t3.com.pl
Rysunek 1.
Schemat działania DPAPI
warstwa publiczna
warstwa prywatna
Dane do
zaszyfrowania
Dane
zaszyfrowane
CryptoAPI
APLIKACJA
DAPAPI
Local Security
Authority (LSA)
DAPAPI
DPAPI i .NET Framework 2.0
Software Developer’s Journal 9/2006
dowany w profilu użytkownika. Poza ochroną, wymianą i składo-
waniem kluczy DPAPI gwarantuje iż proces szyfrujący dane i za-
rządzający kluczami będzie maksymalnie bezpieczny i odseparo-
wany od pozostałych procesów działających w systemie. Idea ta
zrealizowana została poprzez wykorzystanie usługi Local Secu-
rity Authority (LSA). Usługa LSA jest uruchamiana podczas star-
tu systemu operacyjnego i działa przez cały czas jego pracy pod
szczególną ochroną. LSA odpowiada między innymi za uwierzy-
telnianie użytkowników, zarządzanie zabezpieczeniami systemo-
wymi i generowanie kluczy. W budowie DPAPI możemy zatem
wydzielić dwie warstwy logiczne:
• publiczną – udostępniającą interfejs dla programistów i do-
stępną poprzez CryptoAPI (
crypt32.dll
);
• prywatną – niedostępną bezpośrednio dla programistów,
realizującą właściwe zadania kryptograficzne w obrębie
chronionej usługi LSA.
Komunikacja pomiędzy warstwą publiczną a prywatną od-
bywa się poprzez wywołania RPC (Remote Procedure Call).
RPC jest techniką komunikacji międzyprocesowej (IPC – in-
terprocess communication) uwzględniającą mechanizmy bez-
pieczeństwa (uwierzytelnianie, autoryzacja).
Jam jest klucznik
Mechanizm DPAPI opiera się o wykorzystanie trzech typów klu-
czy: głównego – MasterKey, sesyjnego – SessionKey oraz odzy-
skiwania – RecoveryKey. MasterKey jest głównym i najważniej-
szym kluczem generowanym przez DPAPI. Stanowi on najważ-
niejszą informację niezbędną do odszyfrowania bloku danych.
Klucz ten nie jest jednak jawnie wykorzystywany przez funk-
cje szyfrujące. Do bezpośredniego szyfrowania bloku danych
używany jest tzw. klucz sesyjny (ang. SessionKey) generowany
z połączenia MasterKey z losową porcją danych. Klucze Master-
Key są najwrażliwszą częścią mechanizmu dlatego też za ich
szyfrowanie, składowanie i wymianę odpowiada DPAPI.
Klucz SessionKey w przeciwieństwie do MasterKey nie jest
nigdzie zapisywany – jest wykorzystywany jednorazowo pod-
czas szyfrowania porcji danych a następnie usuwany z pamię-
ci. SessionKey powstaje poprzez policzenie skrótu SHA-1 z połą-
czenia MasterKey i 16 bajtowej losowej porcji danych. Do zaszy-
frowanego ciągu bajtów (kryptogramu) dodawana jest owa loso-
wa porcja danych która posłużyła do utworzenia klucza Session-
Key – umożliwia ona później odtworzenie klucza sesyjnego i od-
szyfrowanie informacji. Mechanizm ten zapewnia iż każdorazo-
we zaszyfrowanie tych samych danych, tym samym kluczem
MasterKey spowoduje wygenerowanie innego kryptogramu.
Trzecim z kluczy jest asymetryczny klucz odzyskiwania
– RecoveryKey. Klucz ten wykorzystywany jest do tworzenia
tzw. dysku „resetowania hasła”. W przypadku gdy użytkowniko-
wi zdarzy się zapomnieć własnego hasła może za pomocą te-
goż dysku ustanowić nowe hasło. Podczas tworzenia dysku ge-
nerowana jest para 2048-bitowych kluczy RSA: publiczny oraz
prywatny. Za pomocą klucza publicznego szyfrowane jest aktu-
alne hasło użytkownika a następnie zapisywane w profilu użyt-
kownika. Klucz prywatny natomiast jest zapisywany tylko na
dyskietce którą użytkownik powinien następnie fizycznie gdzieś
ukryć i zabezpieczyć. W momencie wprowadzenia błędnego
hasła użytkownik może wybrać opcję „resetuj”. System doko-
na próby odszyfrowania hasła zapisanego w profilu za pomocą
klucza prywatnego znajdującego się na dyskietce. Jeśli opera-
cja się powiedzie – użytkownik może ustanowić nowe hasło.
Listing 1.
Użycie klasy ProtectedData
// Dane do zaszyfrowania
byte
[]
userData
=
{
1, 2, 3, 4, 5, 6
}
;
// Dodatkowy ciąg bajtów
// (niezbędny później przy odszyfrowywaniu)
byte
[]
optionalEntropy
=
{
7, 3, 2, 9, 1
}
;
Console
.
WriteLine
(
"Przed zaszyfrowaniem: "
);
Console
.
WriteLine
(
System
.
Text
.
ASCIIEncoding
.
ASCII
.
GetChars
(
userData
));
// Szyfrujemy !
byte
[]
protData
=
ProtectedData
.
Protect
(
userData
,
optionalEntropy
,
DataProtectionScope
.
CurrentUser
);
Console
.
WriteLine
(
"Po zaszyfrowaniu: "
);
Console
.
WriteLine
(
System
.
Text
.
ASCIIEncoding
.
ASCII
.
GetChars
(
protData
));
// Odszyfrowujemy!
byte
[]
unprotData
=
ProtectedData
.
Unprotect
(
protData
,
optionalEntropy
,
DataProtectionScope
.
CurrentUser
);
Console
.
WriteLine
(
"Po odszyfrowaniu: "
);
Console
.
WriteLine
(
System
.
Text
.
ASCIIEncoding
.
ASCII
.
GetChars
(
unprotData
));
R
E
K
L
A
M
A
60
Inżynieria
oprogramowania
www.sdjournal.org
Software Developer’s Journal 9/2006
Przepis na danie główne: MasterKey
Podstawą MasterKey jest generowany losowo ciąg 512 bitów (64
bajty) – jak już wspomniano jest on najwrażliwszą częścią me-
chanizmu ponieważ służy do generowania klucza SessionKey
którym bezpośrednio szyfrowane (i odszyfrowywane) są dane.
Jak zabezpieczany zatem jest sam MasterKey? Otóż wygenero-
wany ciąg 512 bitów szyfrowany jest algorytmem PBKDF2 opi-
sywanym w dokumencie PCKS#5 opublikowanym przez firmę
RSA. Dokument ten opisuje sposób szyfrowania danych za po-
mocą hasła oraz ochrony integralności tychże danych. W skrócie
szyfrowanie przebiega następująco: za pomocą algorytmu SHA-
1 liczony jest skrót (ang. hash) z hasła użytkownika. Następnie
wywoływana jest funkcja szyfrująca PBKDF2 do której przeka-
zywany jest skrót hasła, 16 losowo wygenerowanych bajtów sta-
nowiących tzw. modyfikator klucza (ang. salt) oraz liczba n okre-
ślająca ilość iteracji szyfrowania (domyślna wartość to 4000).
Funkcja PBKDF2 z podanych danych liczy rekurencyjnie skrót
SHA-1 przy czym ilość iteracji wynosi podane wcześniej n. Wie-
lokrotne rekurencyjne wyznaczenie skrótu znacznie utrudnia po-
tencjalny atak metodą siłową (ang. Brute-force). Wyznaczony tak
skrót jest tzw. kluczem bazującym na haśle użytkownika. W celu
zapewnienia integralności klucza MasterKey liczony jest dla nie-
go tzw. kod HMAC (Keyed-Hash Message Authentication Code).
Kod ten wyznaczany jest poprzez obliczenie skrótu SHA-1 z Ma-
sterKey w połączeniu z hasłem użytkownika. Pozwala on na póź-
niejsze weryfikowanie integralności odszyfrowanego klucza Ma-
sterKey czyli po prostu sprawdzenie czy jest on poprawny. Na-
stępnie za pomocą algorytmu Triple-DES wykorzystującego wy-
znaczony wcześniej klucz bazujący na haśle użytkownika szyfro-
wany jest MasterKey oraz wyliczony dla niego kod HMAC. Za-
szyfrowany MasterKey oraz kod HMAC, modyfikator klucza ba-
zującego na haśle oraz ilość iteracji użytych do wygenerowania
tego klucza zapisywane są do pliku i umieszczane w profilu użyt-
kownika. Te cztery dane w połączeniu z hasłem użytkownika po-
zwalają później na odszyfrowanie klucza MasterKey oraz spraw-
dzenie jego integralności.
Magazynier
Jak już wiemy, DPAPI przechowuje zaszyfrowane klucze Ma-
sterKey w profilu danego użytkownika. Wiemy również iż każ-
dy MasterKey wygasa po pewnym okresie czasu po czym ge-
nerowany jest nowy klucz. Pojawia się więc pytanie w jaki spo-
sób DPAPI odszyfruje dane zaszyfrowane kluczem MasterKey
który w międzyczasie wygasł i został zastąpiony nowym. Moż-
liwe jest to dzięki temu iż DPAPI nie kasuje starych kluczy Ma-
sterKey lecz zapisuje wszystkie w profilu użytkownika. Ponadto
każdemu kluczowi przypisywany jest unikalny identyfikator GU-
ID (Globally Unique Identifier). Ten sam numer GUID dołącza-
ny jest do kryptogramu dzięki czemu DPAPI wie którego klucza
MasterKey należy użyć dla danego kryptogramu.
Rysunek 2.
MasterKey
�����
�����������
�������������
�����������
������������
���������
������������
����
����������������
�����������������
�����������
��������������
�
���������
��������
������
�����������
����
������
����������
����
����������
DPAPI i .NET Framework 2.0
61
www.sdjournal.org
Software Developer’s Journal 9/2006
Nasuwa się również pytanie: jeśli użytkownik zmieni hasło
to w jaki sposób DPAPI odszyfruje przechowywany MasterKey
(bieżący bądź jeden z poprzednich) który zaszyfrowany został
starym hasłem ? DPAPI rozwiązuje ten problem poprzez pró-
bę wychwycenia momentu modyfikacji hasła. Następuje wtedy
odszyfrowanie wszystkich MasterKey a następnie zaszyfrowa-
nie ich ponownie już nowym hasłem. Ponadto DPAPI zapisuje
w profilu użytkownika historię haseł użytkownika co pozwala na
ewentualne odszyfrowanie MasterKey zaszyfrowanego jednym
z poprzednich haseł.
Dla użytkowników komputerów będących członkami do-
meny DPAPI oferuje dodatkowy mechanizm wykonywania
kopii bezpieczeństwa dla generowanych MasterKey. Mia-
nowicie – po wygenerowaniu każdego MasterKey DPAPI
łączy się z kontrolerem domeny. Po nawiązaniu połączenia
otrzymuje klucz publiczny służący do szyfrowania Master-
Key. Następnie zapisuje w profilu użytkownika dwie wer-
sje zaszyfrowanego MasterKey: pierwszą – zaszyfrowaną
sposobem standardowym za pomocą hasła użytkownika
i drugą – zaszyfrowaną kluczem publicznym otrzymanym
od kontrolera domeny. Kiedy dojdzie do sytuacji w któ-
rej DPAPI nie będzie w stanie odszyfrować danego Ma-
sterKey połączy się z kontrolerem domeny. Po połącze-
niu przesłana zostanie kopia MasterKey zaszyfrowana klu-
czem publicznym. Kontroler za pomocą klucza prywatne-
go odszyfruje MasterKey i prześle go z powrotem do DPA-
PI. Wszystkie połączenia pomiędzy DPAPI a kontrolerem
domeny realizowane są za pomocą chronionych wywołań
RPC.
DPAPI w .NET Framework 2.0
Wraz z nadejściem .NET Framework 2.0 programiści uzyska-
li dostęp do DPAPI za pośrednictwem dwóch klas:
ProtectedDa-
ta
oraz
ProtectedMemory
. Obydwie klasy posiadają bardzo proste
interfejsy. Ich wykorzystanie sprowadza się w zasadzie do wy-
woływania dwóch statycznych metod
Protect
(szyfrowanie da-
nych) oraz
Unprotect
(odszyfrowywanie danych). Klasa
Protec-
tedData
przeznaczona jest do szyfrowania danych które będą
później gdzieś zapisywane / składowane, natomiast
Protected-
Memory
służy do szyfrowania tymczasowych danych przetrzymy-
wanych w pamięci. Różnica ta wynika m.in. ze sposobu genero-
wania klucza SessionKey. W przypadku klasy
ProtectedData
do
kryptogramu dołączane są również dodatkowe dane pozwalają-
ce odtworzyć SessionKey i odszyfrować dane. Do kryptogramu
tworzonego przez
ProtectedMemory
nie są dołączane żadne do-
datkowe dane. Dane przed zaszyfrowaniem i po zaszyfrowaniu
posiadają więc taki sam rozmiar. Dodatkowe informacje potrzeb-
ne do odtworzenia SessionKey generowane są podczas startu
systemu operacyjnego i przechowywane w pamięci przez DPA-
PI lecz nie są nigdzie zapisywane. Gdybyśmy więc zapisali kryp-
togram utworzony przez
ProtectedMemory
np. w pliku, po ponow-
nym starcie systemu operacyjnego jego odszyfrowanie byłoby
niemożliwe.
W Listingu 1 zaprezentowano bardzo prosty przykład uży-
cia klasy
ProtectedData
(należy pamiętać o wcześniejszym
dodaniu w projekcie referencji do pakietu
System.Security.dll
):
Dwa pierwsze parametry metod
Protect
i
Unprotect
nie wymagają wyjaśnienia, natomiast trzeci parametr typu
DataProtectionScope
pozwala programiście dodatkowo okre-
ślić poziom ochrony danych: dostępne tylko dla proce-
sów działających w kontekście tego samego użytkownika
(CurrentUser) lub dla wszystkich procesów działających na
danej maszynie (LocalMachine). Kolejny przykład to użycie
klasy
ProtectedMemory
:
Należy podkreślić że wielkość bloku danych który za-
mierzamy zaszyfrować powinna być wielokrotnością warto-
ści 16. Drugi parametr metody
Protect
i
Unprotect
pozwala
na określenie jednego z trzech poziomów ochrony danych:
dostępne dla wszystkich procesów (CrossProcess), dostęp-
ne tylko dla procesów działających w kontekście tego sa-
mego użytkownika (SameLogon) oraz dostępne tylko dla
procesu który zaszyfrował dane (SameProcess).
Do dzieła!
DPAPI daje nam do ręki skuteczne narzędzie do zabezpiecza-
nia poufnych informacji o znaczeniu krytycznym dla bezpieczeń-
stwa. Wraz z .NET Framework 2.0 programiści zyskali możli-
wość łatwego i bezproblemowego korzystania z tego narzędzia.
Fakt ten dodatkowo przemawia za tym aby poznać DPAPI i nie-
wielkim kosztem dokonać wielkich postępów w zakresie zwięk-
szania bezpieczeństwa tworzonych aplikacji. Należy również po-
wiedzieć otwarcie, że DPAPI nie jest złotym środkiem i reme-
dium na wszystkie problemy dotyczące bezpieczeństwa szyfro-
wanych informacji. n
W Sieci
Cennym źródłem informacji o DPAPI jest na pewno Microsoftowy
MSDN. Poniżej kilka przykładowych linków:
• http://msdn.microsoft.com/msdnmag/issues/05/01/
SecurityBriefs/
• http://msdn.microsoft.com/library/default.asp?url=/library/
enus/dnpag2/html/PAGHT000005.asp
• http://msdn.microsoft.com/library/default.asp?url=/library/
enus/dnsecure/html/windataprotection-dpapi.asp
• http://msdn.microsoft.com/msdnmag/issues/03/11/
protectyourdata/
Listing 2.
Użycie klasy ProtectedMemory
// Dane do zaszyfrowania
byte
[]
userData
=
{
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
13, 14, 15, 16
}
;
Console
.
WriteLine
(
"Przed zaszyfrowaniem: "
);
Console
.
WriteLine
(
System
.
Text
.
ASCIIEncoding
.
ASCII
.
GetChars
(
userData
));
// Szyfrujemy !
ProtectedMemory
.
Protect
(
userData
,
MemoryProtectionScope
.
SameLogon
);
Console
.
WriteLine
(
"Po zaszyfrowaniu: "
);
Console
.
WriteLine
(
System
.
Text
.
ASCIIEncoding
.
ASCII
.
GetChars
(
userData
));
// Odszyfrowujemy !
ProtectedMemory
.
Unprotect
(
userData
,
MemoryProtectionScope
.
SameLogon
);
Console
.
WriteLine
(
"Po odszyfrowaniu: "
);
Console
.
WriteLine
(
System
.
Text
.
ASCIIEncoding
.
ASCII
.
GetChars
(
userData
));