Dokumenty techniczne

Szczegółowa dokumentacja techniczna kryptograficznych

Opublikowano: 16 kwietnia 2025 Ostatnia aktualizacja: 16 kwietnia 2026 Wersja: 2.0

Indeks dokumentów technicznych

  1. Transfer plików z szyfrowaniem end-to-end
  2. Kryptograficzne podpisywanie dokumentów stawiające prywatność na pierwszym miejscu

Transfer plików z szyfrowaniem end-to-end

Dokument techniczny PDF Pro WP-001 — architektura i bezpieczeństwo

1.1 Streszczenie

Niniejszy dokument techniczny opisuje architekturę systemu Bezpiecznego transferu PDF Pro, mechanizmu transferu plików z szyfrowaniem end-to-end, w którym pliki są szyfrowane po stronie klienta za pomocą AES-256-GCM przed przesłaniem. Serwer przechowuje tylko nieprzezroczysty zaszyfrowany tekst i nie ma możliwości odszyfrowania, sprawdzenia ani odczytania zawartości pliku w żadnym momencie. System obsługuje dwa tryby kluczy: automatycznie wygenerowany losowy klucz (przesyłany przez fragment URL, który nigdy nie jest wysyłany na serwer) lub klucz wyprowadzony z hasła za pomocą PBKDF2. W obu trybach klucz szyfrowania istnieje tylko w przeglądarkach nadawcy i odbiorcy. Niniejszy dokument szczegółowo opisuje prymitywy kryptograficzne, przepływ danych, model zagrożeń i uczciwe ograniczenia systemu. Uwaga dotycząca terminologii: ten system nie używa dowodów wiedzy zerowej (ZKP). Gdy mówimy, że serwer ma "zerową wiedzę" o Twoich plikach, mamy na myśli, że — w wyniku szyfrowania po stronie klienta — serwer zawsze przechowuje tylko zaszyfrowany tekst, którego nie może odszyfrować.

1.2 Przegląd architektury

System Bezpiecznego transferu stosuje ścisłą separację klient-serwer, w której wszystkie operacje kryptograficzne odbywają się wyłącznie w przeglądarce:

Serwer jest celowo zaprojektowany jako "głupia rura" dla zaszyfrowanych danych. Nie ma wiedzy o kluczu szyfrowania, haśle ani zawartości pliku. Jest to egzekwowane architektonicznie, a nie tylko przez politykę.

Zasada projektowa: Serwer powinien być możliwy do skompromitowania bez kompromitowania danych użytkownika. Nawet z pełnym dostępem do bazy danych i wykonywaniem kodu po stronie serwera, atakujący nie może odszyfrować przesłanych plików.

1.3 Szyfrowanie: AES-256-GCM przez Web Crypto API

Całe szyfrowanie plików wykorzystuje algorytm uwierzytelnionego szyfrowania AES-256-GCM, dostępny przez natywne Web Crypto API przeglądarki. Zapewnia to zarówno poufność (dane nie mogą być odczytane), jak i integralność (dane nie mogą zostać zmodyfikowane bez wykrycia).

1.3.1 Parametry algorytmu

ParametrWartośćUzasadnienie
AlgorytmAES-256-GCMZatwierdzony przez NIST, uwierzytelnione szyfrowanie z powiązanymi danymi (AEAD)
Rozmiar klucza256 bitówMaksymalna długość klucza AES; zapewnia 128-bitowe bezpieczeństwo przed atakiem brute force
Rozmiar IV96 bitów (12 bajtów)Długość IV zalecana przez NIST dla trybu GCM
Rozmiar tagu128 bitów (16 bajtów)Pełna długość tagu uwierzytelniania dla maksymalnej ochrony integralności
Generowanie IVcrypto.getRandomValues()Kryptograficznie bezpieczny generator liczb losowych

1.3.2 Implementacja szyfrowania

// Uproszczony przepływ szyfrowania (Web Crypto API)

const iv = crypto.getRandomValues(new Uint8Array(12));
const salt = crypto.getRandomValues(new Uint8Array(16));

// Wyprowadź klucz z hasła (zobacz sekcję 1.4)
const key = await deriveKey(passphrase, salt);

// Zaszyfruj plik
const ciphertext = await crypto.subtle.encrypt(
  { name: "AES-GCM", iv: iv },
  key,
  fileArrayBuffer
);

// Pakiet: [salt (16B)] + [iv (12B)] + [zaszyfrowany tekst + tag]
const blob = concatenate(salt, iv, ciphertext);

Końcowy zaszyfrowany blob jest konkatenacją trzech komponentów: 16-bajtowej soli (używanej do wyprowadzania klucza), 12-bajtowego IV (używanego dla AES-GCM) oraz zaszyfrowanego tekstu z dołączonym 16-bajtowym tagiem uwierzytelniania GCM. Ten blob jest tym, co serwer otrzymuje i przechowuje.

1.4 Generowanie i transport kluczy

Bezpieczny transfer obsługuje dwa wzajemnie wykluczające się tryby kluczy. Tryb jest wybierany przez nadawcę w momencie tworzenia transferu.

1.4.1 Tryb A — automatycznie wygenerowany klucz (domyślny)

W trybie domyślnym hasło nie jest zaangażowane. Przeglądarka generuje kryptograficznie losowy 256-bitowy klucz AES bezpośrednio:

1.4.2 Tryb B — transfer chroniony hasłem

Gdy nadawca zdecyduje się ustawić hasło, klucz jest wyprowadzany z tego hasła za pomocą PBKDF2:

1.4.3 Parametry PBKDF2 (tryb B)

ParametrWartośćUzasadnienie
AlgorytmPBKDF2Zalecany przez NIST SP 800-132; szeroko audytowany
Funkcja haszującaSHA-256Standardowy hash kryptograficzny; 256-bitowe wyjście
Iteracje600 000Spełnia zalecenie OWASP 2023 dla PBKDF2-SHA256
Rozmiar soli128 bitów (16 bajtów)Unikalna na transfer; zapobiega atakom rainbow table
Długość klucza wyjściowego256 bitówPasuje do wymogu klucza AES-256

1.4.4 Implementacja wyprowadzania klucza (tryb B)

async function deriveKey(passphrase, salt) {
  // Importuj hasło jako surowy materiał klucza
  const keyMaterial = await crypto.subtle.importKey(
    "raw",
    new TextEncoder().encode(passphrase),
    { name: "PBKDF2" },
    false,
    ["deriveKey"]
  );

  // Wyprowadź klucz AES-256-GCM
  return crypto.subtle.deriveKey(
    {
      name: "PBKDF2",
      salt: salt,
      iterations: 600000,
      hash: "SHA-256"
    },
    keyMaterial,
    { name: "AES-GCM", length: 256 },
    false,
    ["encrypt", "decrypt"]
  );
}

Uwaga dotycząca bezpieczeństwa: Bezpieczeństwo zależy od entropii hasła w trybie B. Zalecamy hasła o długości co najmniej 12 znaków łączące wielkie litery, małe litery, cyfry i symbole.

1.5 Przepływ danych

Pełen cykl życia bezpiecznego transferu przebiega następująco:

1.5.1 Wysyłanie (przesyłanie) — tryb A (automatycznie wygenerowany klucz)

  1. Nadawca wybiera plik w przeglądarce.
  2. Przeglądarka generuje losowy 256-bitowy klucz AES-GCM i losowy 12-bajtowy IV za pomocą crypto.getRandomValues().
  3. Przeglądarka szyfruje plik za pomocą AES-256-GCM, tworząc zaszyfrowany tekst + tag uwierzytelniania.
  4. Przeglądarka konkatenuje [IV | zaszyfrowany tekst+tag] w pojedynczy blob.
  5. Przeglądarka przesyła zaszyfrowany blob na serwer przez HTTPS.
  6. Serwer przechowuje blob w Supabase Storage i tworzy rekord metadanych (ID transferu, wygaśnięcie, limit pobrań, nazwa pliku, rozmiar pliku).
  7. Serwer zwraca URL transferu. Przeglądarka dodaje wyeksportowany klucz do fragmentu URL (#).
  8. Nadawca udostępnia pełny URL (z fragmentem) odbiorcy.

1.5.2 Wysyłanie (przesyłanie) — tryb B (hasło)

  1. Nadawca wybiera plik i wprowadza hasło w przeglądarce.
  2. Przeglądarka generuje losową 16-bajtową sól i 12-bajtowy IV za pomocą crypto.getRandomValues().
  3. Przeglądarka wyprowadza klucz AES-256 z hasła za pomocą PBKDF2 (600 tys. iteracji).
  4. Przeglądarka szyfruje plik za pomocą AES-256-GCM, tworząc zaszyfrowany tekst + tag uwierzytelniania.
  5. Przeglądarka konkatenuje [sól | IV | zaszyfrowany tekst+tag] w pojedynczy blob.
  6. Przeglądarka przesyła zaszyfrowany blob na serwer przez HTTPS.
  7. Serwer przechowuje blob w Supabase Storage i tworzy rekord metadanych (ID transferu, wygaśnięcie, limit pobrań, nazwa pliku, rozmiar pliku).
  8. Serwer zwraca URL transferu zawierający ID transferu.
  9. Nadawca udostępnia URL transferu i hasło odbiorcy oddzielnymi kanałami.

1.5.3 Odbieranie (pobieranie) — tryb A

  1. Odbiorca otwiera pełny URL transferu (z kluczem we fragmencie).
  2. Przeglądarka wyodrębnia klucz AES z fragmentu URL (nigdy nie wysyłanego na serwer).
  3. Przeglądarka pobiera zaszyfrowany blob z serwera przez HTTPS.
  4. Przeglądarka wyodrębnia IV (pierwsze 12 bajtów) z bloba.
  5. Przeglądarka odszyfrowuje zaszyfrowany tekst za pomocą AES-256-GCM z wyodrębnionym kluczem i IV.
  6. Jeśli odszyfrowanie się powiedzie (tag GCM zwalidowany), plik tekstu jawnego jest prezentowany użytkownikowi.
  7. Serwer aktualizuje licznik pobrań i, jeśli włączone jest spalanie po przeczytaniu, usuwa blob.

1.5.4 Odbieranie (pobieranie) — tryb B

  1. Odbiorca otwiera URL transferu i wprowadza hasło w przeglądarce.
  2. Przeglądarka pobiera zaszyfrowany blob z serwera przez HTTPS.
  3. Przeglądarka wyodrębnia sól (pierwsze 16 bajtów) i IV (kolejne 12 bajtów) z bloba.
  4. Przeglądarka wyprowadza klucz AES-256 z hasła + soli za pomocą PBKDF2 (600 tys. iteracji).
  5. Przeglądarka odszyfrowuje zaszyfrowany tekst za pomocą AES-256-GCM z wyprowadzonym kluczem i IV.
  6. Jeśli odszyfrowanie się powiedzie (tag GCM zwalidowany), plik tekstu jawnego jest prezentowany użytkownikowi.
  7. Jeśli odszyfrowanie się nie powiedzie (złe hasło = zły klucz = nieprawidłowy tag GCM), wyświetlany jest błąd.
  8. Serwer aktualizuje licznik pobrań i, jeśli włączone jest spalanie po przeczytaniu, usuwa blob.

1.6 Co serwer przechowuje, a czego nigdy nie widzi

Element danychCzy serwer ma dostęp?Szczegóły
Zaszyfrowany blob (sól + IV + zaszyfrowany tekst + tag)TakNieprzezroczyste dane binarne; serwer nie może zinterpretować zawartości
ID transferuTakIdentyfikatory transferu to wartości UUID v4 generowane przez gen_random_uuid() PostgreSQL, zapewniające 122 bity kryptograficznej losowości z CSPRNG serwera
Oryginalna nazwa plikuTakPrzechowywana w metadanych do wyświetlenia odbiorcy
Oryginalny rozmiar plikuTakPrzechowywany w metadanych do celów wyświetlania
Znacznik czasu wygaśnięciaTakUżywany do egzekwowania automatycznego usuwania
Liczba pobrań / limitTakUżywane do egzekwowania spalania po przeczytaniu i limitu pobrań
ID użytkownika nadawcy (jeśli uwierzytelniony)TakŁączy transfer z kontem do zarządzania
HasłoNie — nigdyNigdy nie wysyłane na serwer; nigdy nie opuszcza przeglądarki
Wyprowadzony klucz szyfrowaniaNie — nigdyIstnieje tylko w pamięci przeglądarki podczas szyfrowania/odszyfrowywania
Zawartość pliku w postaci jawnejNie — nigdyTylko zaszyfrowany tekst dociera do serwera
Liczba iteracji PBKDF2NieZakodowana w kliencie; nie przesyłana na serwer

1.7 Automatyczne wygasanie i spalanie po przeczytaniu

Wszystkie bezpieczne transfery są efemeryczne z założenia. Nie ma opcji trwałego przechowywania.

1.7.1 Opcje wygaśnięcia

OpcjaZachowanieEgzekwowanie
Spal po przeczytaniuZaszyfrowany blob jest usuwany natychmiast po pierwszym pomyślnym pobraniuPo stronie serwera: blob usunięty z pamięci po zakończeniu strumienia pobierania
1 godzinaAutomatycznie usuwany po 1 godzinie niezależnie od statusu pobieraniaPo stronie serwera: zaplanowane zadanie czyszczenia + sprawdzenie wygaśnięcia przy dostępie
24 godzinyAutomatycznie usuwany po 24 godzinachTak samo jak powyżej
7 dniMaksymalna retencja; automatycznie usuwany po 7 dniachTak samo jak powyżej

1.7.2 Gwarancja usunięcia

Gdy transfer wygasa lub jest spalany po przeczytaniu, zaszyfrowany blob jest trwale usuwany z Supabase Storage. Rekord metadanych jest zachowywany przez 30 dni w stanie miękkiego usunięcia (do badania nadużyć), zanim zostanie trwale wyczyszczony. Rekord metadanych nie zawiera klucza szyfrowania, hasła ani żadnych informacji, które mogłyby być użyte do zrekonstruowania pliku.

1.8 Model zagrożeń i środki łagodzące

ZagrożenieWektor atakuŚrodek łagodzący
Kompromitacja serwera Atakujący uzyskuje pełny dostęp do bazy danych i pamięci Wszystkie przechowywane dane to zaszyfrowany tekst AES-256-GCM. Atakujący uzyskuje tylko nieprzezroczyste bloby. Bez hasła atak brute force na 256-bitowe AES jest obliczeniowo niewykonalny.
Przechwycenie sieci (MITM) Atakujący przechwytuje dane w tranzycie Cały ruch używa TLS 1.3. Nawet jeśli TLS zostanie złamany, atakujący uzyskuje tylko zaszyfrowane bloby (jak w przypadku kompromitacji serwera).
Słabe hasło Atakujący atakuje brute force krótkie lub powszechne hasło PBKDF2 z 600 tys. iteracji sprawia, że każda próba jest kosztowna obliczeniowo. 4-znakowe hasło nadal wymagałoby znacznej mocy obliczeniowej. UI egzekwuje minimalną długość hasła i zapewnia informacje zwrotne o sile.
Przechwycenie hasła Atakujący przechwytuje hasło udostępnione między nadawcą a odbiorcą Hasło jest udostępniane poza pasmem (nie przez nasz system). Zalecamy udostępnianie innym kanałem niż URL transferu. To odpowiedzialność użytkownika.
Manipulacja kodem po stronie klienta Atakujący modyfikuje JavaScript dostarczony użytkownikowi Wszystkie zasoby dostarczane przez HTTPS z CDN Vercel. Hashe Subresource Integrity (SRI) chronią przed manipulacją na poziomie CDN. Użytkownicy mogą weryfikować kod źródłowy w DevTools przeglądarki.
Ekstrakcja pamięci Atakujący wyodrębnia klucz szyfrowania z pamięci przeglądarki Klucze Web Crypto API są oznaczane jako nieekstraktowalne tam, gdzie to możliwe. Wyprowadzone klucze istnieją w pamięci tylko podczas operacji szyfrowania/odszyfrowywania. Izolacja pamięci przeglądarki zapewnia ochronę na poziomie systemu operacyjnego.
Atak powtórzeniowy Atakujący odtwarza przechwycony zaszyfrowany blob Każdy transfer ma unikalne ID i jest chroniony limitami pobrań i wygaśnięciem. Transfery ze spalaniem po przeczytaniu są usuwane po pierwszym dostępie.

1.8.1 Założenia

1.8.2 Poza zakresem

1.9 Ograniczenia i uczciwe ujawnienie

Uczciwe ograniczenia: Żaden system bezpieczeństwa nie jest doskonały. Wierzymy w transparentne ujawnianie znanych ograniczeń.


Kryptograficzne podpisywanie dokumentów stawiające prywatność na pierwszym miejscu

Dokument techniczny PDF Pro WP-002 — architektura i bezpieczeństwo

2.1 Streszczenie

Niniejszy dokument techniczny opisuje architekturę systemu Podpisu prywatności PDF Pro, kryptograficznego mechanizmu podpisywania dokumentów, w którym dokument PDF nigdy nie opuszcza przeglądarki podpisującego. System wykorzystuje ECDSA z krzywą P-256 i hashowanie SHA-256 do wytwarzania oddzielonych podpisów cyfrowych. Tylko hash dokumentu, podpis kryptograficzny i klucz publiczny są przesyłane na serwer. Klucze prywatne są generowane, szyfrowane i przechowywane wyłącznie w przeglądarce użytkownika za pomocą IndexedDB, chronione przez wyprowadzanie klucza PBKDF2 (600 000 iteracji) i szyfrowanie AES-256-GCM. Niniejszy dokument szczegółowo opisuje pełną architekturę podpisywania i weryfikacji, model zarządzania kluczami, schemat podpisanego ładunku, projekt śladu audytu, model zagrożeń i uczciwe ograniczenia.

2.2 Przegląd architektury

System Podpisu prywatności jest zaprojektowany wokół fundamentalnego ograniczenia: dokument PDF nigdy nie może być przesłany na serwer. Jest to egzekwowane architektonicznie przez model oddzielonego podpisu.

Główna gwarancja: Serwer nigdy nie widzi, nie otrzymuje ani nie przetwarza dokumentu PDF. Jedynymi danymi związanymi z dokumentem, które serwer kiedykolwiek otrzymuje, jest hash SHA-256 — wartość 256-bitowa o stałym rozmiarze, z której oryginalnego dokumentu nie można zrekonstruować.

2.3 Algorytm podpisywania: ECDSA P-256 / SHA-256

2.3.1 Parametry algorytmu

ParametrWartośćUzasadnienie
Algorytm podpisuECDSA (Elliptic Curve Digital Signature Algorithm)NIST FIPS 186-4; kompaktowe podpisy; silne bezpieczeństwo na bit klucza
KrzywaP-256 (secp256r1 / prime256v1)Zatwierdzona przez NIST; 128-bitowy poziom bezpieczeństwa; szerokie wsparcie Web Crypto API
Funkcja haszującaSHA-256NIST FIPS 180-4; 256-bitowy skrót; odporna na kolizje
Rozmiar klucza256-bitowy klucz prywatny, 512-bitowy klucz publiczny (nieskompresowany)Standard dla P-256; równoważny ~3072-bitowemu RSA
Rozmiar podpisu64 bajty (r: 32 bajty, s: 32 bajty)Kompaktowy; odpowiedni do przechowywania i transmisji
Format podpisuIEEE P1363 (surowy r || s)Natywne wyjście Web Crypto API; kodowane base64url do przechowywania

Kodowanie podpisu: Web Crypto API ECDSA z P-256 produkuje surowy 64-bajtowy podpis składający się z dwóch 32-bajtowych liczb całkowitych (r || s) w formacie big-endian o stałej szerokości. To surowe wyjście jest kodowane base64url do przechowywania. To NIE jest kodowane DER — to format IEEE P1363, który Web Crypto API natywnie produkuje.

2.3.2 Przepływ podpisywania

// 1. Hashuj dokument PDF (po stronie klienta)
const fileBuffer = await file.arrayBuffer();
const hashBuffer = await crypto.subtle.digest("SHA-256", fileBuffer);
const hashHex = Array.from(new Uint8Array(hashBuffer))
  .map(b => b.toString(16).padStart(2, '0')).join('');

// 2. Podpisz hash kluczem prywatnym (po stronie klienta)
const signature = await crypto.subtle.sign(
  { name: "ECDSA", hash: "SHA-256" },
  privateKey,   // CryptoKey z IndexedDB (odszyfrowany)
  hashBuffer
);

// 3. Wyślij na serwer: hash + podpis + klucz publiczny (NIE PDF)
await submitSignature({
  documentHash: hashHex,
  signature: base64Encode(signature),
  publicKey: exportedPublicKeyJWK
});

2.4 Zarządzanie kluczami: efemeryczne kontra trwałe

Typ kluczaKontekstCykl życiaPrzechowywanie
Efemeryczne Użytkownicy goście (niezalogowani) Generowane na sesję; niszczone po zamknięciu karty Tylko w pamięci (obiekt CryptoKey); nigdy nie utrwalane
Trwałe Uwierzytelnieni użytkownicy (zalogowani) Generowane raz; utrwalane między sesjami; możliwe do odwołania IndexedDB (zaszyfrowane PBKDF2 + AES-256-GCM)

2.4.1 Generowanie kluczy

// Generuj parę kluczy ECDSA P-256 (Web Crypto API)
const keyPair = await crypto.subtle.generateKey(
  {
    name: "ECDSA",
    namedCurve: "P-256"
  },
  true,   // ekstraktowalny (potrzebny do szyfrowania + przechowywania)
  ["sign", "verify"]
);

// Eksportuj klucz publiczny jako JWK do rejestracji na serwerze
const publicKeyJWK = await crypto.subtle.exportKey("jwk", keyPair.publicKey);

// Eksportuj klucz prywatny jako JWK do zaszyfrowanego przechowywania
const privateKeyJWK = await crypto.subtle.exportKey("jwk", keyPair.privateKey);

Format klucza publicznego: Klucze publiczne są eksportowane i przechowywane w formacie JWK (JSON Web Key). Odcisk palca klucza jest obliczany jako SHA-256 kanonicznego JWK zawierającego tylko pola publiczne {crv, kty, x, y} z kluczami posortowanymi alfabetycznie.

2.5 Ochrona klucza prywatnego: PBKDF2 + AES-GCM → IndexedDB

Trwałe klucze prywatne nigdy nie są przechowywane w postaci jawnej. Przed zapisaniem w IndexedDB klucz prywatny (eksportowany jako JWK JSON) jest szyfrowany przy użyciu tego samego wzorca co Bezpieczny transfer:

2.5.1 Parametry ochrony

ParametrWartość
Wyprowadzanie kluczaPBKDF2-SHA256, 600 000 iteracji
SólLosowe 16 bajtów (na klucz)
SzyfrowanieAES-256-GCM
IVLosowe 12 bajtów (na szyfrowanie)
WejścieJWK klucza prywatnego (ciąg JSON, kodowanie UTF-8)
Wyjście przechowywane w IndexedDB{ salt, iv, ciphertext, publicKeyJWK, keyId, createdAt }

2.5.2 Schemat przechowywania

// Struktura rekordu IndexedDB dla zaszyfrowanego klucza podpisywania
{
  "keyId":       "uuid-v4-unikalny-identyfikator",
  "publicKey":   { /* format JWK, niezaszyfrowany */ },
  "encryptedPrivateKey": {
    "salt":       "base64-zakodowane-16-bajtów",
    "iv":         "base64-zakodowane-12-bajtów",
    "ciphertext": "base64-zakodowany-zaszyfrowany-tekst-aes-gcm"
  },
  "algorithm":   "ECDSA",
  "curve":       "P-256",
  "createdAt":   "2026-04-16T00:00:00.000Z",
  "userId":      "id-uzytkownika-supabase-auth"
}

Odzyskiwanie kluczy: Jeśli użytkownik zapomni hasła do podpisywania, klucza prywatnego nie można odzyskać. Nie mamy hasła, wyprowadzonego klucza ani żadnego mechanizmu obejścia ochrony PBKDF2 + AES-GCM. Użytkownicy powinni eksportować kopie zapasowe kluczy.

2.6 Model oddzielonego podpisu

PDF Pro używa modelu oddzielonego podpisu, co oznacza, że podpis jest przechowywany oddzielnie od dokumentu. To krytyczny mechanizm prywatności: dokument nigdy nie opuszcza urządzenia podpisującego.

2.6.1 Co jest wysyłane na serwer

2.6.2 Czego NIE wysyła się na serwer

Gwarancja kryptograficzna: SHA-256 to jednokierunkowa funkcja haszująca. Mając tylko hash e3b0c44298fc1c14..., daje silną gwarancję kryptograficzną, w ramach założeń bezpieczeństwa ECDSA i SHA-256, że oryginalnego dokumentu nie można zrekonstruować. Hash nie ujawnia niczego o zawartości, długości ani strukturze dokumentu poza potwierdzeniem tożsamości po ponownym zhashowaniu.

2.7 Schemat podpisanego ładunku v1.0

Następujący schemat JSON definiuje pełen rekord podpisu przechowywany na serwerze:

{
  "schemaVersion":  "1.0",
  "signatureId":    "uuid-v4",
  "documentHash":   "sha256-hex-64-znaków",
  "hashAlgorithm":  "SHA-256",
  "signature":      "base64-zakodowany-podpis-ecdsa",
  "signatureAlgorithm": "ECDSA",
  "curve":          "P-256",
  "publicKey": {
    "kty": "EC",
    "crv": "P-256",
    "x":   "base64url-zakodowana-współrzędna-x",
    "y":   "base64url-zakodowana-współrzędna-y"
  },
  "signer": {
    "identityLevel": "authenticated | self-asserted",
    "displayName":   "ciąg lub null",
    "email":         "ciąg lub null",
    "userId":        "supabase-uid lub null"
  },
  "timestamp":      "ISO-8601-UTC",
  "metadata": {
    "fileName":     "oryginalna-nazwa-pliku.pdf",
    "fileSize":     123456,
    "pageCount":    12,
    "clientVersion": "2.0.0",
    "userAgent":    "ciąg-user-agent-przeglądarki"
  },
  "auditChain": {
    "previousEventHash": "sha256-poprzedniego-zdarzenia-audytu lub null",
    "eventHash":         "sha256-tego-rekordu"
  }
}

2.8 Przepływ weryfikacji

Weryfikacja podpisu to dwufazowy proces: kryptograficzna weryfikacja po stronie klienta, po której następuje weryfikacja krzyżowa po stronie serwera.

2.8.1 Faza 1: Kryptograficzna weryfikacja po stronie klienta

  1. Użytkownik wczytuje PDF do przeglądarki.
  2. Przeglądarka oblicza hash SHA-256 wczytanego PDF.
  3. Przeglądarka pyta serwer o wszelkie zapisy podpisów pasujące do tego hasha.
  4. Dla każdego zwróconego zapisu podpisu przeglądarka wykonuje weryfikację ECDSA:
    const isValid = await crypto.subtle.verify(
      { name: "ECDSA", hash: "SHA-256" },
      importedPublicKey,
      signatureBuffer,
      hashBuffer
    );
  5. Jeśli isValid === true, podpis jest kryptograficznie ważny: dokument nie został zmodyfikowany od podpisania, a podpis został wykonany przez posiadacza odpowiedniego klucza prywatnego.

2.8.2 Faza 2: Weryfikacja krzyżowa po stronie serwera

  1. Serwer potwierdza, że klucz publiczny w zapisie podpisu jest zarejestrowany dla znanego użytkownika (dla podpisów uwierzytelnionych).
  2. Serwer potwierdza, że zapis podpisu nie został odwołany.
  3. Serwer potwierdza integralność śladu audytu (walidacja łańcucha hashy).
  4. Serwer zwraca poziom tożsamości i status rejestracji podpisującego.

2.8.3 Wyniki weryfikacji

WynikZnaczenie
Ważny (uwierzytelniony)Podpis jest kryptograficznie ważny ORAZ klucz publiczny należy do zarejestrowanego, uwierzytelnionego użytkownika PDF Pro.
Ważny (samodzielnie zadeklarowany)Podpis jest kryptograficznie ważny, ale tożsamość podpisującego jest samodzielnie zadeklarowana (użytkownik gość lub niezweryfikowana nazwa).
NieważnyWeryfikacja kryptograficzna nie powiodła się. Dokument został zmodyfikowany od podpisania lub podpis jest uszkodzony.
OdwołanyPodpis był ważny, ale został wyraźnie odwołany przez podpisującego.
Nie znaleziono podpisuBrak zapisu podpisu dla tego hasha dokumentu.

2.9 Ślad audytu: zdarzenia łączone hashami

Każde zdarzenie podpisywania i weryfikacji jest rejestrowane w odpornym na manipulacje śladzie audytu. Zdarzenia są łączone hashami: każde zdarzenie zawiera hash SHA-256 poprzedniego zdarzenia, tworząc łańcuch tylko do dodawania podobny do blockchaina.

2.9.1 Typy zdarzeń audytu

Typ zdarzeniaWyzwalaczZapisane dane
KEY_REGISTEREDUżytkownik rejestruje nowy klucz publicznyJWK klucza publicznego, ID użytkownika, znacznik czasu
DOCUMENT_SIGNEDUżytkownik podpisuje dokumentHash dokumentu, podpis, klucz publiczny, tożsamość podpisującego, znacznik czasu
SIGNATURE_VERIFIEDDowolny użytkownik weryfikuje podpisHash dokumentu, wynik weryfikacji, informacje o weryfikatorze (jeśli uwierzytelniony), znacznik czasu
SIGNATURE_REVOKEDPodpisujący odwołuje podpisID podpisu, powód odwołania, znacznik czasu
KEY_REVOKEDUżytkownik odwołuje klucz publicznyID klucza publicznego, powód odwołania, znacznik czasu

2.9.2 Struktura łańcucha hashy

// Każde zdarzenie audytu zawiera:
{
  "eventId":            "uuid-v4",
  "eventType":          "DOCUMENT_SIGNED",
  "timestamp":          "ISO-8601-UTC",
  "data":               { /* ładunek specyficzny dla zdarzenia */ },
  "previousEventHash": "sha256-poprzedniego-zdarzenia-json",
  "eventHash":          "sha256-tego-zdarzenia-json-bez-eventHash"
}

// Wykrywanie manipulacji: aby zweryfikować łańcuch, oblicz:
// SHA-256(JSON.stringify(zdarzenie bez pola eventHash))
// i potwierdź, że pasuje do eventHash.
// Następnie potwierdź, że previousEventHash pasuje do eventHash poprzedniego zdarzenia.

Jeśli jakiekolwiek zdarzenie w łańcuchu zostanie zmodyfikowane, wszystkie kolejne ogniwa hashy zostaną przerwane, czyniąc manipulację natychmiast wykrywalną. Zapewnia to silną gwarancję integralności śladu audytu.

Ważne ograniczenie: Łańcuch hashy sprawia, że manipulacje są wykrywalne w ramach zapisanej sekwencji zdarzeń. Jednak administrator bazy danych z bezpośrednim dostępem mógłby teoretycznie usunąć i zrekonstruować łańcuch. Silniejsze gwarancje wymagałyby zewnętrznego znacznika czasu lub atestacji strony trzeciej, co nie jest zaimplementowane w tej wersji.

2.10 Poziomy tożsamości

PoziomWymaganiaWłaściwości zaufaniaPrzypadek użycia
Uwierzytelniony Zalogowany użytkownik PDF Pro z zweryfikowanym e-mailem; trwała para kluczy zarejestrowana na koncie E-mail zweryfikowany przez Supabase Auth; klucz publiczny powiązany z uwierzytelnionym kontem; ślad audytu połączony z ID użytkownika Dokumenty biznesowe, umowy, formalne porozumienia
Samodzielnie zadeklarowany Użytkownik gość lub uwierzytelniony użytkownik z samodzielnie wprowadzoną nazwą; efemeryczna lub trwała para kluczy Integralność kryptograficzna gwarantowana; tożsamość podpisującego jest samodzielnie zadeklarowana i nie jest niezależnie weryfikowana Szybkie podpisywanie, dokumenty osobiste, nieformalne porozumienia

Uwaga: W UI produktu "self_asserted" może być wyświetlane jako "Tożsamość samodzielnie zadeklarowana" lub "Podpisywanie gościa". "authenticated" może być wyświetlane jako "Uwierzytelnione konto". Są to etykiety wyświetlania tych samych poziomów tożsamości.

Powiązanie tożsamości: Podpis "uwierzytelniony" oznacza, że klucz publiczny jest zarejestrowany na koncie PDF Pro ze zweryfikowanym e-mailem. NIE oznacza to, że tożsamość świata rzeczywistego podpisującego została zweryfikowana przez dokument tożsamości wydany przez rząd, biometrię ani weryfikację osobistą. Nie wykonujemy sprawdzeń Know Your Customer (KYC).

2.11 Model zagrożeń

ZagrożenieWektor atakuŚrodek łagodzący
Atak powtórzeniowy Atakujący kopiuje ważny podpis i stosuje go do innego dokumentu Podpis jest powiązany z hashem SHA-256 konkretnego dokumentu. Inny dokument będzie miał inny hash, a weryfikacja ECDSA się nie powiedzie.
Fałszerstwo Atakujący tworzy ważny podpis bez klucza prywatnego Bezpieczeństwo ECDSA P-256 opiera się na problemie logarytmu dyskretnego krzywych eliptycznych (ECDLP). Sfałszowanie podpisu bez klucza prywatnego jest obliczeniowo niewykonalne (128-bitowy poziom bezpieczeństwa).
Podstawienie klucza Atakujący rejestruje własny klucz publiczny i twierdzi, że podpis został wykonany przez kogoś innego Uwierzytelnione podpisy wiążą klucz publiczny ze zweryfikowanym e-mailem. Ślad audytu rejestruje, który klucz podpisał który dokument. Zdarzenia rejestracji kluczy są łączone hashami.
Zastąpienie dokumentu Atakujący modyfikuje podpisany PDF i twierdzi, że podpis jest nadal ważny Każda modyfikacja PDF zmienia jego hash SHA-256. Istniejący podpis nie przejdzie weryfikacji względem nowego hasha. Znalezienie kolizji (innego dokumentu z tym samym hashem) wymaga ~2^128 operacji.
Kradzież klucza prywatnego Atakujący wyodrębnia zaszyfrowany klucz prywatny z IndexedDB Klucz prywatny jest zaszyfrowany AES-256-GCM, kluczowany przez PBKDF2 (600 tys. iteracji). Bez hasła odszyfrowanie klucza jest obliczeniowo niewykonalne.
Kompromitacja serwera Atakujący uzyskuje pełny dostęp do serwera Serwer ma tylko klucze publiczne i zapisy podpisów. Klucze prywatne nigdy nie docierają do serwera. Atakujący nie może sfałszować nowych podpisów. Mógłby usunąć lub zmodyfikować istniejące zapisy, ale przerwy łańcucha hashy byłyby wykrywalne.
Manipulacja śladem audytu Atakujący modyfikuje zdarzenia śladu audytu na serwerze Zdarzenia łączone hashami: modyfikacja jakiegokolwiek zdarzenia przerywa łańcuch od tego punktu. Niezależne narzędzia weryfikacji mogą wykryć przerwy łańcucha.

2.11.1 Założenia

2.11.2 Poza zakresem

2.12 Uczciwe ujawnienie

Czym podpisy PDF Pro NIE są:

2.12.1 Do czego podpisy PDF Pro są odpowiednie

Nasze zobowiązanie: Budujemy bezpieczeństwo przez architekturę, a nie marketing. Te dokumenty techniczne opisują dokładnie, jak działają nasze systemy, w tym ich ograniczenia. Wierzymy, że użytkownicy świadomi bezpieczeństwa zasługują na pełną przejrzystość techniczną. Jeśli masz pytania dotyczące jakiegokolwiek aspektu naszej architektury, skontaktuj się z nami pod info@webdesign9.com.