Confidex
Sekcje

W jaki sposób serwer może przechowywać i synchronizować nasze sekrety bez trzymania kluczy w celu ich odczytania? Będziemy śledzić jak Confidex tworzy swoje klucze, zabezpiecza je w zamku oraz wykorzystuje do szyfrowania danych i weryfikacji zmian.

Zamek

Tylko Wybraniec może otworzyć drzwi

Matrix ponownie załadowany

Wyobraź sobie, że budujemy wirtualny vault do przechowywania tajnych danych. Musi przechowywać te dane na laptopie po zamknięciu aplikacji i pozwolić nam otworzyć je na innym urządzeniu. Skarbiec będzie potrzebował własnych kluczy. Musimy je przechowywać w taki sposób, aby kopiowanie plików nie dało osobom z zewnątrz możliwości odczytania ani zmiany ich zawartości.

A lock chroni klucze: mały zaszyfrowany plik. Otwarcie wymaga hasła głównego i tajnego kodu, secret_key; nie ma ich także w samym pliku. Moglibyśmy wydrukować cały zamek w porannej gazecie. Czytelnicy otrzymają kopię, ale jej opublikowanie nie ujawni kluczy prywatnych.

Załóżmy, że osoba z zewnątrz otrzymuje ten plik i nawet poznaje hasło główne. Mogą teraz powtórzyć każde obliczenie, które wymaga tylko hasła, ale te obliczenia nie dadzą klucza otwierającego zamek: secret_key jest również danymi wejściowymi. Plik nie zawiera oddzielnej kontroli samego hasła. Otwarcie go nadal wymaga nieznanego tajnego kodu.

Kod ten pochodzi z kryptograficznie losowych danych; człowiek tego nie wymyśla. Dopóki secret_key nie jest znane, nawet poprawnie odgadnięte hasło główne nie otwiera zamka: atakującemu nadal brakuje losowego sekretu. Dlatego samo słabe hasło nie powoduje, że kopia serwera staje się praktycznym atakiem słownikowym.

Jeśli atakujący uzyska również secret_key, może przetestować odgadywanie hasła głównego w trybie offline. Jest to kosztowne, ale ostateczna trudność zależy od siły hasła.

Klucze

Panowie, do czego służą klucze? Klucze… odblokowują rzeczy?

Piraci z Karaibów – Skrzynia umarlaka

Aplikacja pobiera 32 losowe bajty z generatora kryptograficznego systemu operacyjnego i przygotowuje je do wykorzystania jako klucz. Te przygotowane bajty to prywatny klucz główny, master_key.

Aplikacja zawsze wyprowadza te same klucze z master_key: powtarzanie tych samych obliczeń na tych samych bajtach daje ten sam wynik. Laptop i telefon mogą zatem odzyskać identyczne klucze. Aby uzyskać master_key na nowym urządzeniu, aplikacja musi najpierw otworzyć zamek za pomocą hasła głównego i secret_key.

Pierwsza para: szyfrowanie

Jestem klucznikiem.

Pogromcy Duchów

Aby przechowywać zaszyfrowane dane, aplikacja wylicza klucz publiczny z master_key: secret_key. Może szyfrować dane do skarbca, a odszyfrowanie wymaga master_key.

„Publiczny” opisuje rolę tego klucza w algorytmie, a nie obowiązek przekazania go każdemu. W Confidex pozostaje przy użytkowniku. Jest to także drugi sekret potrzebny do otwarcia zamka, więc pozostaje lokalny i nie jest przesyłany przez sieć. Samo w sobie secret_key pozwala komuś zaszyfrować dane w skarbcu, ale nie może ich odszyfrować ani podpisywać zmian w jego imieniu.

X25519 to algorytm, który pobiera master_key i oblicza secret_key. Odzyskanie master_key z secret_key jest praktycznie niewykonalne. Klasyczny atak na wyimaginowany superkomputer wykonujący miliard miliardów operacji na krzywych na sekundę zająłby średnio około 2,3 biliona lat (szacunki SafeCurves).

public bytes Random bytes32 bytes master_key secret_key public bytes Random bytes32 bytes master_key secret_key
Od losowych bajtów po klucze szyfrujące.

Druga para: podpisy

Jedyną rzeczą oryginalną jest podpis

Złap mnie, jeśli potrafisz

Pierwsza para pomaga ukryć zawartość. Potrzebujemy teraz sposobu na potwierdzenie, że właściciel skarbca autoryzował zmianę. Serwer też musi mieć możliwość sprawdzania podpisów, dlatego musimy wysłać mu publiczny klucz weryfikacyjny. Część publiczna pierwszej pary to secret_key, której serwer nie może odebrać. Dlatego tworzymy drugą, stałą parę kluczy do podpisów.

Zamiast przechowywać kolejny niezależny sekret, uzyskujemy prywatny klucz podpisujący z master_key i nazywamy go signing_key. Aplikacja wykorzystuje go do podpisywania danych i blokady. Inne urządzenia i serwer wykorzystują jego część publiczną do sprawdzania podpisów bez czytania zawartości.

Możemy odzyskać tę parę z tego samego master_key. Aplikacja wyprowadza z niego wartość początkową: 32 bajty, które jednoznacznie określają klucz podpisu.

Aby uzyskać różne klucze dla różnych zadań, w obliczeniach uwzględniamy stałą etykietę: tutaj confidex/vault/ed25519/v1. Ten sam master_key i ta sama etykieta zawsze dają ten sam klucz.

Hasło główne nie zostało uwzględnione w tych obliczeniach. Obie pary zależą od losowych bajtów master_key, a nie od ciągu wymyślonego przez osobę. To rozróżnienie będzie miało znaczenie później: hasło będzie chronić dostęp do kluczy, które już istnieją, dzięki czemu będziemy mogli je zmienić bez zmiany samych kluczy.

Wyprowadzenie wykorzystuje funkcję wyprowadzania klucza opartą na haszu: HKDF, HMAC-based Extract-and-Expand Key Derivation Function. Confidex używa go z SHA-256, stąd nazwa HKDF-SHA256. Wynikowe 32 bajty to seed dla pary Ed25519. Publiczna część signing_key nie ujawnia secret_key, więc możemy wysłać ją na serwer w celu weryfikacji podpisu.

public bytes HKDF-SHA256keymaster_keyinfo"confidex/vault/ed25519/v1" signing_key Signature verification public bytes HKDF-SHA256keymaster_keyinfo"confidex/vault/ed25519/v1" signing_key Signature verification
Oddzielny klucz podpisujący, wywodzący się z tego samego klucza głównego.

Co to jest plik blokady i do czego służy?

Otwórz drzwi przedziału na kapsuły, HAL.
Przykro mi, Dave. Obawiam się, że nie mogę tego zrobić.

Odyseja kosmiczna

Zamek przechowuje zaszyfrowany master_key. Otwarcie go wymaga hasła głównego i secret_key. Nagłówek publiczny zawiera wersję formatu, identyfikator skarbca, część publiczną signing_key, sól i parametry przetwarzania hasła. Wersja w formacie wewnętrznym jest szyfrowana wraz z master_key.

Podczas tworzenia blokady aplikacja ma już master_key, więc przed szyfrowaniem oblicza secret_key. Otwierając go, użytkownik podaje osobno secret_key: nie ma go w publicznej kopii zamka.

Po odszyfrowaniu aplikacja oblicza publiczną część master_key przy użyciu X25519 i wymaga dopasowania do secret_key. Następnie odzyskuje signing_key i porównuje swoją część publiczną z kluczem w nagłówku. W ten sposób zamek sam się sprawdza.

Aby spowolnić odgadywanie hasła głównego, każda próba musi kosztować czas i pamięć. Confidex używa Argon2id z 64 MiB pamięci i trzema przebiegami. Przetwarzanie haseł również wykorzystuje losową sól. Sól nie jest tajemnicą: sprawia, że ​​to samo hasło daje różne wyniki w różnych zamkach.

Jeśli secret_key jest już znane, długość hasła radykalnie zmienia skalę ataku. Istnieje około 12 milionów kombinacji 5 losowych małych liter łacińskich, około 8 miliardów 7 i około 95 biliardów 12. Przejście z 7 do 12 liter zwiększa przestrzeń wyszukiwania prawie 12 milionów razy. Jeśli przeszukiwanie 7 liter na danej maszynie zajmowało średnio jeden dzień, to 5 zajęłoby około dwóch minut, a 12 około 33 000 lat.

Aby uzyskać jeden klucz z wyniku Argon2id i secret_key, używamy wprowadzonej wcześniej funkcji HKDF-SHA256, z etykietą confidex/lock/wrap/v1. Rezultatem jest wrapping_key, klucz szyfrujący zamek.

Musimy ukryć master_key i chronić go przed manipulacją. XChaCha20-Poly1305 szyfruje go za pomocą wrapping_key i dodaje tag uwierzytelniający. Znacznik wiąże szyfrogram z publicznym nagłówkiem: błędne hasło lub zmiana chronionych bajtów powoduje, że otwarcie nie powiedzie się. Znacznik i losowa wartość jednorazowego użytku, nonce, są przechowywane wraz z zaszyfrowanym tekstem.

Za każdym razem, gdy blokada jest tworzona lub ponownie szyfrowana, aplikacja uzyskuje nową sól i nonce. Zaszyfrowana zawartość obejmuje jeden bajt wersji i 32 bajty master_key; obok nich przechowywany jest 24-bajtowy znacznik nonce i 16-bajtowy. Dokumentacja algorytmu opisuje konstrukcję.

Niezgodność między odzyskanymi kluczami może wynikać z celowo zniekształconego zamka lub błędu w programie, który go utworzył. Losowe uszkodzenie bajtu jest zwykle wykrywane wcześniej, podczas sprawdzania znacznika.

Serwer nie posiada sekretów potrzebnych do sprawdzenia tego tagu. Aby przenieść blokadę, aplikacja dodaje podpis wykonany za pomocą signing_key. Obejmuje cały gotowy plik, łącznie z publicznym nagłówkiem i zaszyfrowaną zawartością. Serwer sprawdza podpis i przy pierwszym przesłaniu przypina pełną publiczną część signing_key i wewnętrzny identyfikator skarbca. Sprawdza kolejne zmiany względem tego klucza. Zamek i zaszyfrowane dane są podpisane tym samym signing_key, odzyskanym z master_key.

Skarbiec

Możemy teraz opublikować blokadę. Tylko ktoś, kto zna secret_key i master password, może przeczytać master_key, podczas gdy każdy może pobrać publiczny klucz podpisujący z pliku blokady i sprawdzić podpisane dane, nie widząc zawartości kłódki ani danych.

Rozważ hasło, SuperPa55worD. Na razie chcemy go tylko zapisać: po zamknięciu aplikacji powinniśmy móc wysłać plik blokady i zaszyfrowane hasło w dowolne miejsce tak, aby nikt nie widział SuperPa55worD, jednocześnie pozwalając im zweryfikować nasze autorstwo. Szyfrowanie musi być odwracalne, gdy mamy secret_key i master password. Szyfr symetryczny może szyfrować i deszyfrować za pomocą tego samego klucza. Ponieważ nie przechowujemy tego klucza, musimy mieć możliwość odzyskania go za pomocą zamka. Nazwiemy to message_key.

Dla każdego nowego szyfrowania uzyskamy osobny message_key. Nadal musimy zorganizować jego odzyskanie: pozostawienie klucza otwartego obok zaszyfrowanego tekstu pozwoli każdemu, kto ma kopię, przeczytać wszystko.

Ze świeżych losowych bajtów aplikacja tworzy tymczasową parę X25519 tylko dla tego szyfrowania: record_key. Następnie pobiera prywatną część record_key i stały klucz publiczny skarbca, secret_key, i oblicza wspólny sekret. Z tego sekretu wynika osobna funkcja message_key; ten klucz szyfruje nasze hasło.

Podczas czytania aplikacja podąża inną drogą. Używa master_key i publicznej części record_key. Te dwa klucze tworzą ten sam wspólny sekret. Matematycznie obie trasy mnożą ten sam początkowy punkt na krzywej przez dwa numery prywatne, w innej kolejności. Kolejność nie zmienia wyniku. Możemy zatem odzyskać klucz deszyfrujący bez zachowywania prywatnej części record_key.

Uzyskanie tego sekretu z dwóch kluczy publicznych jest praktycznie niemożliwe: każda trasa wymaga odpowiedniego klucza prywatnego. Punkty publiczne nie pozwalają skutecznie odzyskać numerów prywatnych, które je wygenerowały. Ta matematyczna sztuczka stanowi podstawę bezpieczeństwa skarbca. Nie powstrzymuje to jednak kogoś przed udostępnieniem nam innego skarbca z przymocowanym do niego zamkiem.

Możemy teraz przechowywać zaszyfrowany tekst obok publicznej części record_key. Prywatna część record_key i używana message_key nie są już potrzebne do przyszłego czytania. Później aplikacja odzyskuje klucz deszyfrujący z master_key i zapisaną publiczną część record_key. W ten sposób jeden stały sekret skarbca może odszyfrować wiele oddzielnie zaszyfrowanych wartości, z których każda ma swój własny message_key odzyskany w razie potrzeby.

Ukrycie tekstu nie wystarczy: musimy także wykryć manipulację podczas jego czytania. Szyfrowanie dodaje zatem znacznik uwierzytelniający, krótką wartość sprawdzaną razem z zaszyfrowanymi danymi. Jeśli sprawdzenie się nie powiedzie, aplikacja odrzuci wynik. Hasło nie może wyświetlać losowego lub zmienionego tekstu jako pomyślnie odszyfrowanego hasła.

public bytes Fresh random bytes record_key secret_key Shared secret Derive message key message_key Encrypted passwordpacket public bytes Fresh random bytes record_key secret_key Shared secret Derive message key message_key Encrypted passwordpacket
Świeża para kluczy do każdego szyfrowania.

Opisaliśmy konstrukcję, w której para asymetryczna pomaga ustalić wspólny sekret, a message_key szyfruje zawartość. Nazywa się to Hybrid Public Key Encryption lub HPKE i jest określone w RFC 9180. Confidex używa X25519 do uzgodnienia sekretu, HKDF-SHA256 do uzyskania kluczy i ChaCha20-Poly1305 do szyfrowania ze sprawdzaniem integralności. Tutaj HKDF pobiera wspólny sekret uzgodniony za pomocą pary record_key i master_key; jest to zadanie odrębne od wyprowadzania Ed25519 seed. RFC 5869 opisuje HKDF.

Do kolejnego szyfrowania aplikacja tworzy nowy kontekst record_key i nowy kontekst HPKE. Ponowne użycie kombinacji message_key i nonce łamie ochronę szyfru. W Confidex klucz i nonce są wyprowadzane z wnętrza HPKE i używane w jednej wiadomości, a następnie kontekst jest odrzucany.

Sam ChaCha20 używa 256-bitowego message_key. Aby wyobrazić sobie bezpośredni atak, wyobraźmy sobie fikcyjny superkomputer testujący miliard miliardów kandydatów na sekundę. Przeszukanie 2²⁵⁶ możliwych kluczy zajęłoby około 1,8 × 10⁵¹ lat. RFC 8439 opisuje algorytm i jego analizę.

Rekord

Jesteśmy szczęśliwi?

Pulp Fiction

Zapisaliśmy hasło, ale logowanie do serwisu zwykle wymaga również podania nazwy użytkownika i adresu. Czasami potrzebujemy notatki obok nich. Jeśli przechowujemy te wartości osobno, aplikacja musi w jakiś sposób pamiętać, że należą one do siebie. Włóżmy te „papiery” do koperty. Łączymy pola w jedną kolekcję i nazywamy ją record.

Rekord może zawierać dowolną kombinację pól. Aplikacja składa je w jeden pakiet, kompresuje i szyfruje całość, korzystając z konstrukcji, którą właśnie opisaliśmy. Wartości pól, nazwy, typy i znaczniki są chronione. Zmiana adresu strony internetowej, nawet przy zachowaniu tego samego hasła, powoduje utworzenie nowej wersji całego rekordu, zaszyfrowanej nowym record_key.

Łańcuch mieszający

CZY TWÓJ PROJEKT CZY KORZYSTAĆ Z BLOCKCHAINA? → NIE

XKCD 2267

Ktoś, kto zna secret_key, może przygotować zaszyfrowany rekord dla naszego skarbca. Aby uniknąć uznania go za nasz, aplikacja podpisuje każdą wersję rekordu znakiem signing_key. Bez tego klucza osoba z zewnątrz nie może stworzyć wersji z podpisem, który zaakceptuje serwer i inne urządzenia.

Zaszyfrowana wersja wymaga dodatkowych informacji: do jakiego skarbca i zapisu należy, kiedy została zmieniona i które wersje ją poprzedzają. Tworzy to nagłówek publiczny. Nie zawiera hasła, ale nadal wymaga ochrony: w przeciwnym razie ktoś mógłby zmienić własność obiektów i relacje bez dotykania tekstu zaszyfrowanego. Aplikacja podpisuje dokładne bajty nagłówka wraz z tekstem zaszyfrowanym. Zmiana podpisanych danych powoduje unieważnienie podpisu.

Serwer przypomina magazyn zapieczętowanych przesyłek. Widzi nagłówek, zaszyfrowaną treść i podpis, ale nie zawiera hasła i wie, że odbiorca istnieje w mieście. Sprawdza podpis względem przypiętej publicznej części signing_key i decyduje, czy zaakceptować nową wersję. Nie potrzebuje hasła głównego, ani secret_key, ani żadnego klucza prywatnego. Podczas odczytu klient sprawdza ten sam podpis, a następnie ponownie sprawdza powiązanie między zawartością a nagłówkiem podczas deszyfrowania, używając znacznika uwierzytelniającego HPKE.

Podpis chroni także powiązania pomiędzy wersjami. Hash SHA-256 zawierający dokładny nagłówek i bajty tekstu zaszyfrowanego staje się identyfikatorem wersji. Nagłówek kolejnej wersji zawiera identyfikator wersji poprzedniej. Gdy zmienimy hasło do serwisu, aplikacja tworzy i podpisuje tym linkiem nowy obiekt. Stary obiekt i jego sygnatura pozostają niezmienione. Zastąpienie poprzednika przy jednoczesnym zachowaniu sygnatury nowej wersji zakończy się więc niepowodzeniem. Jeśli dwa urządzenia dokonają niezależnych zmian, obie gałęzie przetrwają; wersja scalona może odnosić się do obu.

Pamiętasz blockchaina? E tam :-).

Nagłówek publiczny pozwala nam sprawdzić linki bez odszyfrowywania, ale niektóre informacje pozostają widoczne. Serwer zna identyfikatory, linki do wersji, czas zmiany dostarczony przez klienta, liczbę pól i zakodowany rozmiar każdej wartości pola. Szyfrowanie ukrywa zawartość rekordu, a nie informacje strukturalne.

Header andparent IDs Encrypted recordpacket Sign revision signing_key Signed revision Header andparent IDs Encrypted recordpacket Sign revision signing_key Signed revision
Nagłówek i tekst zaszyfrowany są podpisane razem.

Synchronizacja

„To nie wyciek danych, tylko bezpłatna rozproszona kopia zapasowa”.

Internetowy mem.

Aby pracować na kilku urządzeniach, musimy przenieść blokadę i zapisać historię między nimi. Format obiektu nie ulega zmianie: przesyłamy te same zaszyfrowane bajty i podpisy, które są przechowywane lokalnie.

W przypadku ręcznego przesyłania aplikacja tworzy archiwum ZIP zawierające blokadę, wszystkie lokalnie przechowywane zaszyfrowane wersje rekordów oraz listę podpisanych plików. Ani hasło, ani secret_key nie są uwzględnione. Na innym urządzeniu aplikacja najpierw otwiera kłódkę, następnie sprawdza podpisy, odszyfrowuje zapisy i przywraca lokalną kopię skarbca. Import jest przeznaczony dla urządzenia, na którym ten skarbiec jeszcze nie istnieje; nie łączy historii z istniejącą kopią.

Serwer synchronizacji wymienia zmiany między kopiami roboczymi. Przy pierwszym przesyłaniu otrzymuje podpisaną blokadę, sprawdza podpis i oblicza adres skarbca publicznego na podstawie publicznej części signing_key. Następnie zapamiętuje ten klucz i identyfikator skarbca z nagłówka: powoduje to zarejestrowanie skarbca na serwerze. Kolejne zmiany muszą odpowiadać temu identyfikatorowi i posiadać podpis z tego samego klucza.

Nowe urządzenie potrzebuje adresu skarbca i secret_key, więc aplikacja łączy je w kod przelewu z sumą kontrolną. Użytkownik przesyła ten kod do urządzenia i osobno wprowadza hasło główne; do serwera wysyłany jest tylko adres publiczny.

Po otwarciu pobranej blokady aplikacja porównuje publiczne części odzyskanych kluczy z secret_key i nagłówkiem oraz sprawdza obliczony adres z kodem przelewu. Następnie zapisuje zweryfikowaną publiczną część signing_key w celu sprawdzenia rekordów.

Podczas synchronizacji aplikacja pobiera brakujące wersje i przesyła zmiany. Serwer sprawdza podpisy i linki do poprzednich wersji, przechowuje zaakceptowane obiekty i przekazuje je innym urządzeniom. Sprawdzanie otrzymanych danych, odszyfrowywanie ich i rozwiązywanie konfliktów odbywa się na urządzeniach użytkownika.

Rejestracja nie wymaga podania imienia, adresu e-mail ani osobnego hasła. Serwer widzi adres publiczny, klucz weryfikacji podpisu, nagłówki publiczne, rozmiary obiektów i łącza do wersji. Widzi także adresy IP i czasy żądań. Hasło główne, secret_key, klucze prywatne i zawartość rekordu w postaci zwykłego tekstu pozostają na urządzeniach użytkownika. Każdy, kto zna adres publiczny, może pobrać zaszyfrowane obiekty, ale ich odczytanie wymaga podania sekretów użytkownika.

Kontrole te wykrywają sfałszowane obiekty, ale nie dowodzą, że serwer pokazał pełną historię i jej najnowszy stan. Może ukryć gałąź, której klient nigdy nie widział, lub zwrócić spójny starszy zestaw danych. Podpis potwierdza autentyczność pojedynczego obiektu, a nie kompletność odpowiedzi serwera. Użytkownik może zauważyć brakujące zmiany na innym urządzeniu, ale spójna stara historia przejdzie weryfikację hash-chain.

Zmiana hasła głównego

Czy Wasza Świątobliwość zechciałaby zmienić swoje hasło?

Hakerzy (1995)

Zmiana hasła głównego powoduje ponowne zaszyfrowanie tylko zamka; master_key wewnątrz pozostaje takie samo. Akta, ich podpisy, secret_key i adres skarbca pozostają zatem niezmienione. Nowy zamek jest podpisany tym samym signing_key.

Stara kopia zamka nadal otwiera się ze starym hasłem i tym samym secret_key; zmiana hasła nie unieważnia kluczy, które zostały już ujawnione.

Pełny obraz

Zamek chroni master_key, message_key szyfruje rekordy, a signing_key tworzy podpisy. Archiwa ZIP i serwer otrzymują zaszyfrowane obiekty z podpisami.

public bytes public bytes public bytes master_key Fresh randombytes secret_key record_key Master password wrapping_key signing_key message_key lock Encrypted records Signed lockand records Signatureverification ZIP archive /sync server public bytes public bytes public bytes master_key Fresh randombytes secret_key record_key Master password wrapping_key signing_key message_key lock Encrypted records Signed lockand records Signatureverification ZIP archive /sync server
Klucze, co chronią i czym można się dzielić.