Łańcuchy skrótów i wykrywanie manipulacji: jak jeden zmieniony bajt unieważnia całe archiwum
Gdy ktoś mówi, że archiwum dokumentów „ujawnia manipulacje” (tamper-evident), jest to konkretne twierdzenie: każda modyfikacja dowolnego rekordu powoduje wykrywalną niespójność. Nie chodzi o odporność na manipulacje (tamper-proof): to inne, mocniejsze twierdzenie. Ujawnianie manipulacji oznacza, że jeśli archiwum zostało zmodyfikowane, możesz tę modyfikację znaleźć.
Łańcuchy skrótów to standardowa konstrukcja pozwalająca osiągnąć tę właściwość. Nie są niczym nowym (koncepcja wyprzedza blockchain o dziesięciolecia), ale są powszechnie źle rozumiane, co prowadzi zarówno do nadmiernej pewności siebie, jak i do zbyt rzadkiego ich stosowania.
Ten artykuł wyjaśnia, jak działają łańcuchy skrótów, co gwarantują, czego nie gwarantują i dlaczego konkretne wybory implementacyjne mają znaczenie.
Czym jest kryptograficzny skrót
Kryptograficzna funkcja skrótu przyjmuje dane wejściowe o dowolnej długości i zwraca wynik o stałej długości, nazywany skrótem (hash lub digest). SHA-384 daje 384-bitowy (48-bajtowy) wynik, zwykle przedstawiany jako 96 znaków szesnastkowych.
Trzy właściwości sprawiają, że funkcje skrótu są przydatne do wykrywania manipulacji:
Determinizm. Te same dane wejściowe zawsze dają ten sam wynik. Jeśli dwukrotnie obliczysz skrót tego samego dokumentu, otrzymasz ten sam skrót.
Efekt lawinowy. Niewielka zmiana danych wejściowych daje zupełnie inny wynik. Zmiana jednego bajtu w dokumencie o rozmiarze 10 000 bajtów daje skrót, który w niczym nie przypomina oryginału. Nie da się przewidzieć, jak będzie wyglądał nowy skrót.
Jednokierunkowość. Mając skrót, nie da się w wykonalnym czasie obliczeniowym odtworzyć danych wejściowych. Nie można cofnąć się od skrótu do dokumentu.
Trzecia właściwość sprawia, że łańcuchy skrótów są wiążące: gdy opublikujesz lub zapiszesz skrót, zobowiązujesz się do treści, która go wytworzyła, nie ujawniając samej treści.
Podstawowa konstrukcja
Łańcuch skrótów łączy sekwencję zdarzeń tak, że każde zdarzenie zależy od wszystkich poprzednich.
W implementacji SealDoc zdarzenie dowodowe zawiera:
- Typ zdarzenia (np.
vault.ingested,anchor.rfc3161) - Numer sekwencyjny
- Znacznik czasu
- Szczegóły specyficzne dla zdarzenia (jako kanoniczny JSON)
- Skrót poprzedniego zdarzenia
- Skrót tego zdarzenia, obliczony na podstawie wszystkich powyższych pól
Skrót każdego zdarzenia zawiera skrót poprzedniego zdarzenia. Oznacza to, że aby odtworzyć skrót zdarzenia N, potrzebujesz dokładnej treści zdarzenia N oraz dokładnego skrótu zdarzenia N-1. Skrót zdarzenia N-1 zależał od zdarzenia N-2 i tak dalej, aż do pierwszego zdarzenia.
Pierwsze zdarzenie używa jako „poprzedniego skrótu” stałej wartości genesis: powszechnie znanej stałej, którą każdy może zweryfikować.
W ten sposób powstaje łańcuch, w którym:
Hash(Event 1) = H("vault.ingested" | seq:1 | ts:... | details:... | genesis)
Hash(Event 2) = H("anchor.rfc3161" | seq:2 | ts:... | details:... | Hash(Event 1))
Hash(Event 3) = H("vault.storage-lock.applied" | seq:3 | ts:... | details:... | Hash(Event 2))
Co się dzieje, gdy ktoś przy nim manipuluje
Załóżmy, że przeciwnik chce zmodyfikować szczegóły zdarzenia 1. Zmienia pole w zdarzeniu, a następnie ponownie oblicza skrót zdarzenia 1. Nowy skrót różni się od oryginalnego.
Jednak skrót zdarzenia 2 został obliczony z użyciem oryginalnego skrótu zdarzenia 1. Przeciwnik musi teraz ponownie obliczyć skrót zdarzenia 2 z użyciem nowego skrótu zdarzenia 1. To daje inny skrót zdarzenia 2.
Teraz zdarzenie 3 ma niewłaściwy poprzedni skrót. I tak dalej dla każdego kolejnego zdarzenia.
Aby modyfikacja pozostała niewykryta, przeciwnik musi ponownie obliczyć skróty wszystkich zdarzeń od miejsca manipulacji naprzód. W lokalnym, samodzielnym systemie jest to możliwe. Sam łańcuch temu nie zapobiega.
Dlatego właśnie kotwiczenie ma znaczenie.
Kotwiczenie: zewnętrzna weryfikowalność łańcucha
Łańcuch skrótów, który istnieje wyłącznie w Twojej własnej bazie danych, dowodzi integralności tylko w obrębie Twojego własnego systemu. Przeciwnik, który kontroluje Twoją bazę danych, może przepisać cały łańcuch. Łańcuch musi zostać zakotwiczony w czymś, co znajduje się poza Twoją kontrolą.
Standardowym mechanizmem jest znacznik czasu RFC 3161. Przepływ wygląda następująco:
- W momencie utworzenia dokumentu oblicz skrót zdarzenia dowodowego.
- Prześlij ten skrót do niezależnego urzędu znacznikowania czasu (Time Stamping Authority, TSA).
- TSA podpisuje odpowiedź zawierającą Twój skrót, znacznik czasu TSA i tożsamość TSA. Ta podpisana odpowiedź to token znacznika czasu.
- Przechowuj token znacznika czasu razem z łańcuchem skrótów.
Token znacznika czasu jest podpisany kluczem prywatnym TSA. Łańcuch certyfikatów TSA jest publicznie weryfikowalny. Każdy, kto ma dokument, oryginalny skrót i token znacznika czasu, może niezależnie zweryfikować, że dokument istniał w tym stanie w tym czasie, bez konieczności ufania Ci w czymkolwiek.
Teraz problem przeciwnika jest trudniejszy. Aby niepostrzeżenie zmodyfikować zdarzenie 1, musi:
- Zmodyfikować zdarzenie i ponownie obliczyć wszystkie kolejne skróty.
- Uzyskać od zaufanego TSA nowy znacznik czasu RFC 3161 dla nowego skrótu.
Drugi krok jest niemożliwy wstecznie. Znacznik czasu TSA ma wbudowany czas. Znacznik czasu twierdzący, że zmodyfikowany dokument istniał w pierwotnym czasie, musiałby zostać wydany przez TSA w tym pierwotnym czasie, czego przeciwnik nie może zorganizować po fakcie.
Tak w praktyce wygląda „ujawnianie manipulacji”: nie chodzi o to, że modyfikacji zapobiega się na poziomie fizycznym, lecz o to, że każda modyfikacja, którą przeciwnik chciałby ukryć, wymaga albo złamania podpisu niezależnego TSA, albo przepisania historii, a oba te działania są obliczeniowo niewykonalne.
Dlaczego dwa skróty i który gdzie
SHA-256 to najczęściej używany algorytm skrótu w tej klasie zastosowań. SealDoc używa go dla łańcucha przetwarzania i manifestu pakietu, a SHA-384 dla rejestru dowodowego. Oba działają dla każdego dokumentu; odpowiadają na różne pytania.
Łańcuch przetwarzania nie może wybierać dowolnie. Jego skrót jest jednocześnie kluczem przechowywania i odciskiem wiadomości (message imprint), który podpisuje urząd znacznikowania czasu, więc linia dowodowa biegnie od bajtów przez odcisk do tokenu. Zmiana oznaczałaby migrację każdego zapisanego lokalizatora i każdego istniejącego tokenu, a nie przestawienie ustawienia.
Rejestr dowodowy powstał później i nie ma takiego powiązania, dlatego używa dłuższego skrótu.
Praktyczna różnica to margines bezpieczeństwa. SHA-256 jest obecnie uważany za bezpieczny, ale zalecenia kryptograficzne ewoluują. SHA-384 należy do tej samej rodziny SHA-2 i jest obliczany w identyczny sposób, tylko z większym stanem wewnętrznym. Dla archiwów z długimi okresami przechowywania (7 lat dla unijnych faktur VAT, do 20 lat dla niektórych dokumentów sektora publicznego) dodatkowy margines ma znaczenie.
Dłuższy skrót zmniejsza też (i tak znikome) prawdopodobieństwo kolizji skrótów, gdy dwa różne dokumenty dają ten sam skrót. Dla archiwów compliance jest to istotna właściwość: dwie różne faktury nigdy nie powinny dawać tego samego identyfikatora.
Weryfikacja łańcucha
Weryfikacja przebiega wstecz od najnowszego zdarzenia. Mając zbiór zdarzeń uporządkowanych według numeru sekwencyjnego:
- Dla każdego zdarzenia ponownie oblicz skrót na podstawie zapisanych pól: typu zdarzenia, numeru sekwencyjnego, znacznika czasu, szczegółów i poprzedniego skrótu.
- Porównaj ponownie obliczony skrót z zapisanym skrótem. Jeśli się zgadzają, treść zdarzenia nie została zmieniona od chwili obliczenia skrótu.
- Sprawdź, czy
previousHashkażdego zdarzenia odpowiada zapisanemu skrótowi poprzedzającego zdarzenia. Jeśli się zgadzają, łańcuch jest nieprzerwany. - Dla każdego zdarzenia, które ma kotwicę w postaci znacznika czasu RFC 3161, zweryfikuj token znacznika czasu względem łańcucha certyfikatów TSA.
Endpoint SealDoc GET /api/vault/{recordId}/verify ponownie oblicza każdy skrót, sprawdza każde powiązanie, weryfikuje podpisy zdarzeń i raportuje kotwicę czasową, z zestawieniem dla każdego zdarzenia:
{
"chainValid": true,
"eventCount": 3,
"events": [
{
"sequenceNo": 1,
"eventType": "vault.ingested",
"hashVerified": true,
"chainLinkVerified": true,
"signaturePresent": true,
"signatureVerified": true
}
]
}
hashVerified oznacza, że ponownie obliczony skrót odpowiada zapisanemu. chainLinkVerified oznacza, że wskaźnik poprzedniego skrótu jest spójny. signatureVerified oznacza, że podpis ECDSA na skrócie zdarzenia jest ważny względem znanego klucza podpisującego.
Łańcuch, w którym każde zdarzenie zwraca hashVerified: true i chainLinkVerified: true, to łańcuch, w którym żadne zapisane dane nie zostały zmodyfikowane. Znaczniki czasu kotwic dostarczają zewnętrznego, weryfikowalnego przez TSA dowodu, kiedy każde zdarzenie miało miejsce.
Czego łańcuch nie gwarantuje
Ujawnianie manipulacji to konkretna i ograniczona właściwość. Warto precyzyjnie określić, co obejmuje, a czego nie.
Nie zapobiega usuwaniu. Łańcuch skrótów wykrywa modyfikację istniejących rekordów, ale nie potrafi wykryć, że rekord został usunięty. Jeśli zdarzenie 2 w łańcuchu pięciu zdarzeń zostanie całkowicie usunięte, łańcuch się zrywa, ale tylko wtedy, gdy weryfikujący wie, że zdarzeń powinno być pięć, a nie cztery. Zapobieganie usuwaniu to odrębne zagadnienie warstwy przechowywania, rozwiązywane na przykład przez magazyn WORM.
Nie zapobiega temu, że system źródłowy kłamie. Jeśli dokument został uszkodzony, zanim trafił do potoku, łańcuch skrótów utrwala tę uszkodzoną wersję. Łańcuch dowodzi integralności od momentu przyjęcia naprzód, a nie od momentu utworzenia. Dlatego walidacja (sprawdzanie zgodności z PDF/A, walidacja schematu Factur-X) odbywa się przed zatwierdzeniem skrótu.
Łańcuch jest tak silny, jak jego kotwice. Łańcuch bez zewnętrznych kotwic RFC 3161 to system, który sam się pieczętuje. Dowodzi wewnętrznej spójności, ale nie powiązania w czasie. W każdym poważnym zastosowaniu związanym ze zgodnością niezbędne są znaczniki czasu zakotwiczone w niezależnym TSA.
Praktyczne wnioski dla inżynierów
Jeśli budujesz potok dokumentów i potrzebujesz ujawniania manipulacji, implementacja jest prosta:
- Przy przyjęciu oblicz skrót dokumentu za pomocą SHA-256 lub SHA-384.
- Prześlij skrót do TSA RFC 3161 i zapisz zwrócony token znacznika czasu.
- Dla każdego kolejnego zdarzenia powiąż skrót zdarzenia ze skrótem poprzedniego zdarzenia.
- Przechowuj łańcuch w magazynie typu append-only (bez aktualizacji ani usuwania w tabeli zdarzeń).
- Udostępnij endpoint weryfikacji, który na żądanie ponownie oblicza i sprawdza łańcuch.
Złożoność koncepcyjna jest niewielka. Złożoność implementacyjna wynika z kwestii operacyjnych: zarządzania kluczami, niezawodności TSA, migracji łańcucha w razie zmiany algorytmu skrótu oraz obsługi sytuacji, w której przesłanie do TSA nie powiedzie się w trakcie transakcji.
SealDoc obsługuje te kwestie jako infrastrukturę, więc potoki, które kierują dokumenty przez API, otrzymują łańcuch bez konieczności jego implementowania. Jeśli jednak budujesz go samodzielnie, powyższe to minimalna działająca konstrukcja.
SealDoc Evidence Vault tworzy dla każdego dokumentu dziennik zdarzeń powiązany łańcuchem skrótów i uzyskuje znacznik czasu RFC 3161 od niezależnego unijnego urzędu znacznikowania czasu. Łańcuch i znaczniki czasu można zweryfikować przez GET /api/vault/{recordId}/verify bez konieczności ufania infrastrukturze SealDoc.