background image

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

background image

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 – RecoveryKeyMasterKey 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

background image

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

�����

�����������

�������������

�����������

������������
���������

������������

����

����������������

�����������������

�����������

��������������

���������

��������

������

�����������

����

������

����������

����

����������

background image

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

));