Ciąg dalszy tematu haseł. Tym razem kilka słów o tym, dlaczego szyfrowanie haseł w plikach konfiguracyjnych (szyfrowanie plików konfiguracyjnych) często w praktyce nie zwiększa poziomu bezpieczeństwa.
Szyfrowanie czy "zaciemnianie" czyli hasła w konfiguracji
Załóżmy, że istnieje program, który łączy się z serwerem i pobiera z niego pewne dane. Do pobrania tych danych potrzebne jest uwierzytelnienie, czyli podanie nazwy użytkownika i hasła. Program powinien działać automatycznie, więc dane potrzebne do uwierzytelnienia musi pobierać z konfiguracji (rejestr, plik konfiguracyjny). Z uwagi na bezpieczeństwo, dane uwierzytelniające nie mogą być przechowywane w formie jawnej.
Pierwszym rozwiązaniem (stosunkowo często stosowanym) jest "własny algorytm szyfrowania", który w praktyce jest własnym algorytmem "zaciemniania", oczywiście odwracalnym (bo program musi użyć hasła w formie jawnej). Jest to klasyczny przykład security by obscurity. Nikogo chyba nie trzeba przekonywać, że to tego typu rozwiązanie problemu jest złe. Przykład? Proszę bardzo: Microsoft ActiveSync 4.x Weak Password Obfuscation.
Zamiast tworzenia własnej kryptografii, można oczywiście wykorzystać znane i sprawdzone algorytmy kryptograficzne. Pojawia się tylko jeden mały problem - klucz. Do szyfrowania/rozszyfrowania danych potrzebny jest klucz, który w jakiś sposób musi być dostępny dla programu. Może on być po prostu osadzony w programie, może być też generowany automatycznie przy pierwszym uruchomieniu programu. W obu przypadkach wykorzystanie najlepszego nawet algorytmu szyfrowania jest niwelowane przez dostępność klucza. W praktyce oznacza to, że zwiększenie poziomu bezpieczeństwa wynikające z zaszyfrowania hasła, jest czysto iluzoryczne. W zasadzie można odwołać się do przykładu z ActiveSync, nawet zastępując XOR przez AES hasło nadal byłoby łatwo dostępne, bo łatwo dostępny byłby klucz użyty do zaszyfrowania tych danych.
Sensownym rozwiązaniem może być wpisywanie "hasła głównego" przy starcie programu, z którego to hasła generowany jest klucz, za którego pomocą hasło właściwe jest odszyfrowywane. Tylko czy jest to sensowne w przypadku tego konkretnego scenariusza? Przecież w zasadzie sprowadza się to do tego, że do poznania (jednego) hasła zapisanego w pliku konfiguracyjnym, potrzebne jest wpisanie innego hasła. W zasadzie równie dobrze zamiast zapisywania hasła w pliku konfiguracyjnym, można podawać je przy starcie programu. Oczywiście sensowność tego rozwiązania rośnie, gdy chroniony ma być większy zakres informacji, niż tylko jedno hasło, lub więcej niż jedno hasło. Polecam tutaj (ponownie) lekturę Password Minder Internals, czy też informacji zamieszczonych na stronie projektu KeePass.
Jak do tej pory sens szyfrowania hasła jest co najmniej dyskusyjny. W zasadzie nie ma istotnej różnicy między szyfrowaniem hasła a jego "zaciemnianiem" (choćby przy pomocy rot13). Różnica między hasłem podanym w formie jawnej i w formie niejawnej jest również niewielka (w praktyce żadna, jeśli program dostępny jest masowo - na pewno znajdzie się ktoś, komu będzie chciało się sprawdzić, jak to zaszyfrowane/zakodowane hasło odzyskać).
Można popatrzeć na sprawę jednak w inny sposób. W trakcie różnych testów udawało mi się pozyskać pliki konfiguracyjne, w których było dostępne różne (użyteczne) hasła, na przykład do bazy danych. W przypadku środowiska Windows (aplikacje w ASP.NET) dane te mogły być łatwo zaszyfrowane za pomocą mechanizmu DPAPI (How To: Encrypt Configuration Sections in ASP.NET 2.0 Using DPAPI). Gdybym mógł uruchomić własny kod na badanym serwerze, tego typu zabezpieczenie byłoby trywialne do obejścia. Jeśli jednak dysponuję wyłącznie plikiem konfiguracyjnym, nie mam klucza potrzebnego do odszyfrowania tych danych.
Wracając do przykładu z ActiveSync, hasło do urządzenia przenośnego mogłoby być chronione przy pomocy DPAPI (funkcje CryptProtectData i CryptUnprotectData), z użyciem klucza użytkownika. Klucz użytkownika chroniony jest jego hasłem (w zasadzie to pewne uproszczenie) i "odblokowywany" jest przy jego uwierzytelnieniu. W przypadku ataku offline w celu odzyskania hasła konieczne byłoby złamanie hasła użytkownika (atak na bazę SAM), co przy odpowiednio mocnym haśle użytkownika nie jest zadaniem trywialnym. Oczywiście, gdyby atak był "online", dowolny kod uruchomiony w kontekście tego użytkownika mógłby wywołać funkcję CryptUnprotectData i hasło to odzyskać.
W ramach podsumowania:
- kodowanie/zaciemnianie danych jest ZŁE (czytaj - mało skuteczne),
- szyfrowanie danych w sytuacji gdy klucz jest (łatwo) dostępny, to w praktyce kodowanie/zaciemnianie (choć bardziej eleganckie),
Trzeba więc zadbać o to, by klucz nie był łatwo dostępny (powszechnie znany). Można do tego wykorzystać na przykład mechanizm DPAPI, można generować unikalny klucz przy pierwszym uruchomieniu programu, można wreszcie wymagać, by użytkownik wpisał "hasło główne" przy uruchamianiu aplikacji. W przypadku DPAPI (i szyfrowania kluczem maszyny, bo w przypadku wykorzystania klucza użytkownika trzeba złamać najpierw jego hasło) lub generowanego unikalnego klucza nadal możliwe są ataki offline (pobranie kluczy DPAPI, odczytanie wygenerowanego klucza), w przypadku wpisywania hasła sytuacja jest oczywiście lepsza, ale tylko do czasu. Jeśli "atakujący" może uruchomić swój kod na atakowanej maszynie (w kontekście tego użytkownika LUB z prawami administratora/systemu), chronione hasła mogą być na przykład odczytane z pamięci procesów.
Osobiście zamiast wspomnianych dziesiątków narzędzi do pamiętania haseł stosuję po prostu zaszyfrowany arkusz kalkulacyjny (do wszystkich haseł) oraz PwdHash (do serwisów online). W odróżnieniu od password-managerów ma tę zaletę że działa wszędzie gdzie jest JavaScript.
A jeśli chodzi o PwdHash to moja paranoja jest najwyraźniej bardziej zaawansowana. Z zasady nie korzystam z niezaufanych komputerów, a na zaufanych (czytaj - moich) mam swojego password managera