Zaktualizowano

Szyfrowanie zero-knowledge: jak Vaulted chroni prywatność twoich sekretów

Autor:

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:

  1. Przeglądarka wysyła żądanie /s/abc123 do serwera (bez fragmentu)
  2. Serwer zwraca szyfrogram i IV
  3. JavaScript odczytuje fragment z window.location.hash
  4. Klucz jest importowany z powrotem do Web Crypto API
  5. 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:

  1. Fragment jest dzielony po ., aby wyodrębnić zawinięty klucz i salt
  2. Ten sam klucz zawijający AES-KW jest wyprowadzany z hasła i saltu przy użyciu PBKDF2
  3. Klucz szyfrujący jest odwijany przez crypto.subtle.unwrapKey
  4. 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:

  1. Importuj klucz z fragmentu URL (crypto.subtle.importKey)
  2. Odszyfruj szyfrogram z IV (crypto.subtle.decrypt)
  3. Dekoduj tekst jawny z bajtów UTF-8

Z hasłem:

  1. Podziel fragment, aby wyodrębnić zawinięty klucz i salt
  2. Wyprowadź klucz zawijający z hasła i saltu (PBKDF2)
  3. Odwiń klucz szyfrujący (AES-KW)
  4. Odszyfruj szyfrogram (AES-GCM)
  5. 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:

  1. Dla każdego sekretu generowany jest świeży klucz AES-256-GCM
  2. Szyfrowanie odbywa się w całości w twojej przeglądarce
  3. Serwer przechowuje wyłącznie zaszyfrowany szyfrogram
  4. Klucz deszyfrujący podróżuje wyłącznie we fragmencie URL, który przeglądarki nigdy nie wysyłają do serwerów
  5. 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