Szyfrowanie zero-knowledge: jak Vaulted chroni prywatność twoich sekretów
Autor: Maxim Novak
Szyfrowanie zero-knowledge oznacza, że serwer nigdy nie widzi twoich danych w postaci jawnej ani twoich kluczy szyfrujących. Vaulted realizuje to, szyfrując każdy sekret w twojej przeglądarce za pomocą AES-256-GCM, zanim jakiekolwiek dane dotrą do serwera. Serwer przechowuje wyłącznie szyfrogram — nie ma dostępu do klucza deszyfrującego, który żyje wyłącznie we fragmencie URL.
Ten artykuł omawia dokładną implementację kryptograficzną: jak generowane są klucze, jak szyfrowane są dane, jak klucz trafia do odbiorcy i co dzieje się po dodaniu hasła. Każdy detal odzwierciedla rzeczywisty kod działający w twojej przeglądarce.
Ogólne omówienie tego, dlaczego szyfrowanie po stronie klienta ma znaczenie, znajdziesz w naszym wcześniejszym artykule o szyfrowaniu po stronie klienta. Ten artykuł zagłębia się w kryptograficzne elementy składowe.
Generowanie klucza
Za każdym razem, gdy tworzysz sekret, Vaulted generuje świeży klucz szyfrujący AES-256-GCM przy użyciu Web Crypto API:
crypto.subtle.generateKey({ name: 'AES-GCM', length: 256 }, true, [
'encrypt',
'decrypt',
]);
Kilka rzeczy wartych odnotowania:
- AES-GCM (Galois/Counter Mode) zapewnia zarówno poufność, jak i integralność — wykrywa manipulacje, nie tylko nieuprawniony odczyt.
- 256-bitowa długość klucza to najmocniejsza dostępna opcja dla AES, stosowana przez rządy i instytucje finansowe na całym świecie.
- Extractable:
true— klucz można wyeksportować jako surowe bajty, żeby umieścić go we fragmencie URL dla odbiorcy.
Web Crypto API deleguje zadania do natywnego silnika kryptograficznego przeglądarki (np. BoringSSL w Chromium, NSS w Firefox). Klucz jest bezpiecznie generowany w pamięci i nigdy nie trafia na dysk ani w sieć.
Szyfrowanie
Mając wygenerowany klucz, Vaulted szyfruje twój tekst jawny:
const iv = crypto.getRandomValues(new Uint8Array(12));
crypto.subtle.encrypt(
{ name: 'AES-GCM', iv },
key,
new TextEncoder().encode(plaintext),
);
Wektor inicjalizacyjny (IV): AES-GCM wymaga losowego IV o długości 12 bajtów (96 bitów) dla każdej operacji szyfrowania. IV nie musi być tajny — jest przechowywany razem z szyfrogramem — ale nigdy nie może być ponownie użyty z tym samym kluczem. Ponieważ Vaulted generuje nowy klucz dla każdego sekretu, ponowne użycie IV jest niemożliwe z założenia.
Kodowanie: Zarówno szyfrogram, jak i IV są kodowane jako ciągi base64url (standardowy base64, w którym + jest zastępowane przez -, / przez _, a końcowe = są usuwane). Dzięki temu są bezpieczne do użycia w URL-ach i ładunkach JSON.
Zaszyfrowany szyfrogram i IV są wysyłane do serwera w celu przechowywania. Tekst jawny nigdy nie opuszcza przeglądarki.
Dostarczanie klucza przez fragment URL
Po zaszyfrowaniu klucz jest eksportowany i umieszczany we fragmencie URL — części po #:
const rawKey = await crypto.subtle.exportKey('raw', key);
// rawKey → base64url-encoded string
Wynikowy zaszyfrowany link wygląda następująco:
https://vaulted.fyi/s/abc123#dGhpcyBpcyBhIGtleQ
^^^^^^^^^^^^^^^^^^^^^^^^
encryption key (base64url)
Separator # ma kluczowe znaczenie. Zgodnie z RFC 3986 identyfikator fragmentu jest przetwarzany wyłącznie przez klienta. Przeglądarki nigdy nie dołączają fragmentu do żądań HTTP — ani w URL, ani w nagłówku Referer, ani w żadnym miejscu widocznym dla serwera.
Gdy odbiorca otwiera link:
- Przeglądarka wysyła żądanie
/s/abc123do serwera (bez fragmentu) - Serwer zwraca szyfrogram i IV
- JavaScript odczytuje fragment z
window.location.hash - Klucz jest importowany z powrotem do Web Crypto API
- Szyfrogram jest deszyfrowany lokalnie
Serwer umożliwia przechowywanie i pobieranie zaszyfrowanego bloba. Nigdy nie uczestniczy w operacjach kryptograficznych.
Opcjonalna ochrona hasłem
Dla sekretów wymagających dodatkowej ochrony Vaulted obsługuje zawijanie klucza opartego na haśle. Dodaje to drugą warstwę: nawet jeśli ktoś przechwyci link, nie może odszyfrować sekretu bez hasła.
Derywacja klucza z PBKDF2
Gdy ustawiasz hasło, Vaulted wyprowadza klucz zawijający przy użyciu PBKDF2:
crypto.subtle.deriveKey(
{
name: 'PBKDF2',
salt, // 16 bytes, randomly generated
iterations: 600_000,
hash: 'SHA-256',
},
keyMaterial, // the passphrase, imported as raw key material
{ name: 'AES-KW', length: 256 },
false,
['wrapKey', 'unwrapKey'],
);
- 600 000 iteracji sprawia, że ataki brute-force są obliczeniowo kosztowne.
- 16-bajtowy losowy salt gwarantuje, że identyczne hasła generują różne klucze pochodne dla różnych sekretów.
- SHA-256 to funkcja skrótu używana w każdej iteracji PBKDF2.
- Wyprowadzony klucz jest kluczem AES-KW (AES Key Wrap, RFC 3394), a nie kluczem AES-GCM. Zawijanie klucza jest odrębną operacją od szyfrowania.
Zawijanie klucza szyfrującego
Wyprowadzony klucz AES-KW zawija oryginalny klucz szyfrujący:
crypto.subtle.wrapKey('raw', encryptionKey, wrappingKey, 'AES-KW');
Fragment URL zawiera teraz zarówno zawinięty klucz, jak i salt, oddzielone kropką:
#wrappedKeyBase64url.saltBase64url
Odwijanie przy odszyfrowaniu
Gdy odbiorca wprowadza hasło, Vaulted odwraca proces:
- Fragment jest dzielony po
., aby wyodrębnić zawinięty klucz i salt - Ten sam klucz zawijający AES-KW jest wyprowadzany z hasła i saltu przy użyciu PBKDF2
- Klucz szyfrujący jest odwijany przez
crypto.subtle.unwrapKey - Szyfrogram jest deszyfrowany odwiniętym kluczem AES-GCM
Jeśli hasło jest błędne, unwrapKey zgłasza wyjątek — nie ma częściowego odszyfrowania ani wycieku informacji.
Przepływ deszyfrowania
Bez hasła deszyfrowanie jest proste:
- Importuj klucz z fragmentu URL (
crypto.subtle.importKey) - Odszyfruj szyfrogram z IV (
crypto.subtle.decrypt) - Dekoduj tekst jawny z bajtów UTF-8
Z hasłem:
- Podziel fragment, aby wyodrębnić zawinięty klucz i salt
- Wyprowadź klucz zawijający z hasła i saltu (PBKDF2)
- Odwiń klucz szyfrujący (AES-KW)
- Odszyfruj szyfrogram (AES-GCM)
- Dekoduj tekst jawny
Wszystko to dzieje się w przeglądarce. Serwer zwraca zaszyfrowany blob i metadane (licznik wyświetleń, czas wygaśnięcia) — i nic więcej.
Model zagrożeń
Żadne narzędzie bezpieczeństwa nie chroni przed wszystkim. Oto przed czym architektura Vaulted jest zaprojektowana, a co leży poza jej zakresem.
Przed czym Vaulted chroni
- Kompromitacja serwera — Serwer przechowuje wyłącznie zaszyfrowany szyfrogram. Bez klucza (który nigdy nie jest wysyłany do serwera) dane są nieczytelne. Pełny zrzut bazy danych dostarcza jedynie zaszyfrowanych blobów.
- Naruszenie bazy danych — Jak wyżej. Szyfrogram bez kluczy jest obliczeniowo bezużyteczny przy AES-256-GCM.
- Atak man-in-the-middle po stronie serwera — Klucz szyfrujący żyje we fragmencie URL, który nigdy nie jest przesyłany w żądaniach HTTP. TLS chroni szyfrogram podczas przesyłania.
- Operatorzy usługi czytający sekrety — Zero-knowledge oznacza dokładnie to. Nie możemy czytać twoich sekretów nawet pod przymusem, ponieważ nie mamy kluczy.
Przed czym Vaulted NIE chroni
- Skompromitowana przeglądarka lub urządzenie — Jeśli urządzenie odbiorcy zawiera złośliwe oprogramowanie lub keylogger, odszyfrowany tekst jawny może zostać przechwycony po odszyfrowaniu.
- Złośliwe rozszerzenia przeglądarki — Rozszerzenia z dostępem do strony mogą odczytać DOM po odszyfrowaniu, w tym ujawniony sekret.
- Przechwycenie linku — URL zawiera klucz szyfrujący we fragmencie. Każdy, kto zdobędzie pełny URL (łącznie z fragmentem), może odszyfrować sekret. Udostępniaj linki przez bezpieczne kanały.
- Podglądanie przez ramię — Gdy sekret jest wyświetlony na ekranie, każdy obserwujący może go zobaczyć.
- Zachowanie odbiorcy — Po odszyfrowaniu odbiorca może zrobić zrzut ekranu, skopiować lub udostępnić tekst jawny. Vaulted nie może temu zapobiec.
Funkcja hasła ogranicza ryzyko przechwycenia linku: nawet dysponując pełnym URL, atakujący nadal potrzebuje hasła. Przekaż hasło innym kanałem (rozmowa telefoniczna, oddzielna wiadomość), aby uzyskać obronę warstwową.
Podsumowanie
Szyfrowanie zero-knowledge w Vaulted oznacza:
- Dla każdego sekretu generowany jest świeży klucz AES-256-GCM
- Szyfrowanie odbywa się w całości w twojej przeglądarce
- Serwer przechowuje wyłącznie zaszyfrowany szyfrogram
- Klucz deszyfrujący podróżuje wyłącznie we fragmencie URL, który przeglądarki nigdy nie wysyłają do serwerów
- Opcjonalne zawijanie hasłem dodaje drugą warstwę przez PBKDF2 i AES-KW
Wynik: nawet gdyby wszystkie serwery, bazy danych i ścieżki sieciowe zostały jednocześnie skompromitowane, twoje sekrety pozostają zaszyfrowane. Klucze istnieją tylko w linkach, które udostępniasz.
Gotowy, żeby bezpiecznie udostępnić sekret? Utwórz sekret w Vaulted — zajmuje to 10 sekund.
Powiązane
- Bezpieczeństwo w Vaulted — podsumowanie naszego modelu bezpieczeństwa
- Dlaczego szyfrowanie po stronie klienta ma znaczenie — argumenty za szyfrowaniem w przeglądarce
- Plac zabaw z szyfrowaniem — obserwuj szyfrowanie AES-256-GCM krok po kroku