Bezpieczne przechowywanie haseł, nawet w systemie Windows

Ostatnie dwa miesiące zajmowałem się rozwojem gałęzi dotyczącej szyfrowania haseł w Pidginie – w końcu jest gotowa do pokazania publiczności. Ponadto, jej dokończenie zależało również od innego zadania związanego z bezpieczeństwem: poprawieniu wersji dla systemu Windows – to też jest również prawie gotowe. Dzisiaj chciałbym zaprezentować aktualne wyniki mojej pracy.

Tuż przed rozpoczęciem pracy nad tą gałęzią Pidgina, była ona opisywana jako prawie gotowa, lub nawet gotowa. Jak się okazało, jej funkcje działały dobrze tylko dla małej liczby przypadków, w których użytkownik miał wszystko ustawione zgodnie z założeniami programisty oraz nigdy się nie mylił przy wpisywaniu głównego hasła. Zabawne jest również to, że funkcja wspomniana w nazwie tej gałęzi (szyfrowanie haseł wewnątrz programu z użyciem hasła głównego) nigdy nie była nawet w części wykonana.

Muszę przyznać, że część pracy wykonanej przez studenta, który się tym zajmował w trakcie GSoC’08 nie poszła na marne. Można powiedzieć, że to był dobry proof-of-concept. Ja zająłem się docelową implementacją i przybliżyłem ten kod to stanu, w którym będzie się nadawał do udostępnienia użytkownikom. Obsłużenie wszystkich możliwych przypadków użycia (nie tylko tego jednego, idealnego) wymagało prawie drugie tyle napisanego kodu, niż ten który powstał w ramach SoC. Ponadto, tylko mała część kodu nadawała się do pozostawienia. Wykres poniżej przedstawia pewne przybliżenie sytuacji, dla uproszczenia został wykonany tylko na podstawie statystyk wziętych z nowo dodanych plików (keyring.c i implementacje poszczególnych sejfów).

Kod poprawiony w gałęzi szyfrowania haseł

Wykonanie tych funkcji zgodnie ze sztuką wymagało wielu poprawek w głównej gałęzi rozwojowej: poprawy API szyfrowania, naprawy wersji dla Windowsa (jest również specjalny sejf dla tego systemu), naprawy komponentów takich jak Request API oraz kilka mniej znaczących błędów wersji 3.0.0. Niektóre z nich nie były zbyt zajmujące (np. API szyfrowania zajęło mnie tylko na kilka dni), a niektóre były na tyle duże, że można je spokojnie uznać za całkiem osobne zadania (wersja dla Windowsa wymagała poświęcenia 1/4 całkowitego spędzonego czasu).

Wersja dla systemu Windows

Gałąź 3.0.0 nie działała na Windowsie od roku lub dwóch, więc musiałem naprawić to, abym mógł przygotować sejf na hasła również dla tego systemu. Ponadto, to był całkowicie osobny problem związany z bezpieczeństwem, ponieważ biblioteki wykorzystywane w wersji na ten system były pobierane ze źródeł, które nie były regularnie aktualizowane.

Udało mi się dostosować Pidgina do korzystania z bibliotek dostarczanych przez (całkiem wiarygodny) openSUSE Build Service. Prawdopodobnie część z tych bibliotek i tak była by ciężko dostępna z innych źródeł. Ten dostawca udowodnił swoją odpowiedzialność za dostarczane pakiety, przez naprawdę szybkie poprawianie błędów bezpieczeństwa (w kilka godzin po moich uwagach). Błędy nie związane bezpośrednio z bezpieczeństwem również były poprawiane w rozsądnym czasie. Chciałbym tutaj złożyć specjalne podziękowania dla jednej z osób opiekujących się tym repozytorium – Fridrich Strba, który przygotował je do potrzeb komunikatora Pidgin. Bez jego pomocy, wykonanie w miarę stabilnego wydania dla systemu Windows było by co najmniej dwukrotnie trudniejsze.

Obsługiwane sejfy na hasła

Sejfy (ang keyring) przechowują hasła dla kont skonfigurowanych w komunikatorze Pidgin (lub innym, wykorzystującym bibliotekę libpurple). Zaimplementowano kilka z nich, więc użytkownik może sam wybrać ten, który najlepiej pasuje do jego potrzeb. Jeżeli żaden się nie nadaje – zawsze może napisać własny.

Wewnętrzny sejf bez szyfrowania pozwala na kompatybilność ze starszymi wersjami. Hasła tutaj są przechowywane tak, jak do tej pory – bez szyfrowania. To nie musi być koniecznie niebezpieczne – wszystko zależy od zabezpieczenia pliku accounts.xml.

Wewnętrzny sejf z szyfrowaniem również przechowuje hasła w pliku accounts.xml, jednak w postaci zaszyfrowanej. Klucz szyfrowania jest otrzymywany z hasła głównego, wpisywanego przy każdym uruchomieniu komunikatora.

GNOME Keyring oraz KWallet przenoszą odpowiedzialność za hasła do usługi zarządzanej przez system operacyjny. Hasła są tutaj szyfrowane hasłem głównym, które musi być (w zależności od konfiguracji) wprowadzane po każdym uruchomieniu systemu operacyjnego.

Secret Service (biblioteka libsecret) jest kolejnym sejfem zarządzanym przez system. Niestety, jakość wtyczki go obsługującej jest na tyle słaba, że go tymczasowo wyłączyłem.

Menedżer poświadczeń jest usługą systemu Windows, szyfrującą hasła z użyciem danych powiązanych z kontem użytkownika tego systemu. Jego bezpieczeństwo zależy od konfiguracji konta – szyfrowanie niewiele da, jeżeli użytkownik nie zabezpieczył go hasłem.

KeePass jest wieloplatformową aplikacją, która mogła by być dobrym wyborem dla osób, które używają jednej konfiguracji komunikatora dla wielu systemów. Niestety, wygląda na to, że KeePass nie mógłby działać z Pidginem od razu po uruchomieniu – wymagane są osobne wtyczki dla tej aplikacji, aby mogła współpracować z komunikatorem. W tej chwili nie ma implementacji dla tego sejfu.

Schemat szyfrowania haseł (dla wewnętrznego sejfu)

Implementacja tej funkcji jest czułym punktem, więc musi być wykonana bardzo ostrożnie. To ważne, aby nie próbować własnych, wymyślnych rozwiązań kryptograficznych, a skupić się na tych sprawdzonych. Nawet wtedy bardzo łatwo coś popsuć.

Wybranie dobrych klocków, z których złoży się ta funkcjonalność jest krytyczna, ale kiedyś może się okazać, że mimo to wybrane rozwiązanie jest słabe. Dla takich sytuacji wprowadziłem możliwość konfigurowania metody szyfrowania, ale zaimplementowałem tylko jedną (użytkownik nie ma możliwości wyboru). Wybrałem dwa algorytmy: AES-256 do szyfrowania oraz PBKDF2-SHA256 do otrzymywania klucza z hasła głównego.

Algorytm PBKDF2 posiada konfigurowalną liczbę iteracji (domyślnie 10000), więc użytkownik może ją zwiększyć, jeżeli potrzebuje paranoicznego poziomu bezpieczeństwa, lub zmniejszyć (jednak nie mniej, niż 1000), jeżeli jego maszyna jest zbyt wolna dla domyślnej wartości.

To ważne, aby używać szyfrowania AES rozsądnie, aby uniknąć niektórych potencjalnych ataków. Użyłem następującego schematu szyfrowania:
ciphertext := [IV] ++ AES( [plaintext] ++ [min length padding] ++ [control string] ++ [pkcs7 padding] )
gdzie:

  • ciphertext jest zakodowany z użyciem metody base64, aby można było zapisać go w pliku xml.
  • IV jest losowym, 128-bitowym wektorem, dzięki któremu każdy szyfrogram jest inny, nawet dla jednakowych tekstów jawnych. Jest przechowywany razem z szyfrogramem, aby ułatwić zarządzanie nim.
  • AES jest używany z kluczem 256-bitowym w trybie CBC
  • plaintext, czyli tekst jawny, jest zapisany bez kończącego znaku NUL – jego długość jest określona przez jedno z wyrównań (w zależności od długości).
  • min length padding wydłuża tekst jawny do co najmniej 50 znaków, poprzez dodawanie znaków NUL za jego końcem. Dzięki temu ukryta jest informacja, czy hasło jest długie, czy krótkie.
  • control string jest stałym tekstem, który pozwala sprawdzić, czy odszyfrowywanie przebiegło pomyślnie.
  • pkcs7 padding jest wyrównaniem pozwalającym określić długość ciągu [plaintext] ++ [min length padding] ++ [control string]

Wersje testowe

Kod źródłowy jest dostępny bezpośrednio z repozytorium, ale przygotowałem kilka paczek dla osób, które chcą po prostu rzucić okiem:

Proszę o komentarze i informacje o błędach. Osoby dobrze posługujące się językiem angielskim proszę również o uwagi na temat błędów językowych – można przeglądać wszystkie teksty wyszukując w pliku pidgin.pot frazy /keyring.c oraz /keyrings/.

3 myśli nt. „Bezpieczne przechowywanie haseł, nawet w systemie Windows

    • Możesz spróbować uruchomić ją z przełącznikiem -d i określić, w którym momencie się zawiesza? Zauważyłem, że przy pierwszym uruchomieniu na windowsie potrafi przywiesić, nawet na 2-3 minuty – czy to o to chodzi?

    • Tak, właśnie o to mi chodziło. Jednak było trochę poczekać. Dzięki :)

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *