PDF Library for Delphi może otworzyć zaszyfrowany PDF z surowego klucza szyfrowania pliku zamiast z hasła. DAOpenFileWithEncryptionKey przyjmuje klucz jako tekst szesnastkowy, sprawdza go względem weryfikatora zapisanego już w słowniku szyfrowania i zwraca uchwyt tylko do odczytu Direct Access; DAOpenFromStreamWithEncryptionKey robi to samo dla należącego do wywołującego TStream. Oba punkty wejścia pojawiły się w wersji v3.496.0
To wąski, ale rzeczywisty scenariusz. Podczas analizy śledczej otrzymujesz klucz odzyskany z obrazu pamięci, ale nie masz hasła. Masowe archiwizowanie obejmuje dziesięć tysięcy dokumentów, których klucze plików leżą w bazie escrow, ponieważ pierwotny system DRM od lat nie wydaje haseł. Migracja z wycofanego produktu do zarządzania prawami dostarcza materiał kluczowy i nic więcej. W każdym z tych przypadków posiadane poświadczenie jest wynikiem wyprowadzania klucza, a nie jego wejściem, i żaden parametr hasła w API go nie przyjmie
Dlaczego klucz szyfrowania pliku nie jest hasłem?
Hasło i klucz szyfrowania pliku leżą po przeciwnych stronach wyprowadzania klucza w standardowym module bezpieczeństwa PDF (ISO 32000-1 §7.6.3). Moduł pobiera hasło, miesza je z /O, /P, identyfikatorem pliku i haszem zależnym od rewizji, po czym tworzy klucz pliku. Wstawienie klucza pliku w miejsce hasła daje nonsens zahaszowany w inny nonsens, dlatego potrzebny jest osobny punkt wejścia, a nie flaga w DAOpenFile
Miejsce wstrzyknięcia surowego klucza jest ustalone przez rewizję. Rewizje 2–4 nadal wyprowadzają osobny klucz dla każdego obiektu z klucza pliku, numeru obiektu, numeru generacji oraz — dla AESV2 — soli AES, więc posiadanie klucza pliku wcale nie pozwala pominąć wyprowadzania na poziomie obiektu. Rewizje 5–7 używają bezpośrednio 32-bajtowego klucza pliku dla AES-256, bez kroku per obiekt. Wspólną warstwą obu grup jest sam klucz pliku, dlatego właśnie tylko w tym miejscu PDF Library for Delphi przyjmuje klucz dostarczony z zewnątrz. Jeśli nadal masz hasło, pozostań przy zwykłej ścieżce i pozwól cyklowi ponawiania hasła obsłużyć błędną pierwszą próbę, ponieważ ścieżka surowego klucza celowo rezygnuje z kilku udogodnień zachowanych przez ścieżkę hasła
Jakie dane wejściowe przyjmuje DAOpenFileWithEncryptionKey?
Wyłącznie nieprefiksowany, pozbawiony białych znaków, parzystej długości heksadecymalny ASCII, przy czym liczba zdekodowanych bajtów musi dokładnie odpowiadać rewizji szyfrowania. Dla rewizji 2–4 oczekiwana długość pochodzi z /Length w słowniku szyfrowania: jest wielokrotnością 8 bitów i mieści się między 5 a 16 bajtami, a gdy /Length brakuje, domyślnie oznacza 40 bitów. Dla rewizji 5–7 wynosi dokładnie 32 bajty, bez negocjacji. Prefiks 0x, nieparzysta liczba cyfr, ponad 64 znaki szesnastkowe, nieznany bit w Options albo dokument, który w ogóle nie jest zaszyfrowany, prowadzą do tego samego wyniku: uchwytu o wartości 0 i LastErrorCode ustawionego na PDFLIB_ERROR_RAW_ENCRYPTION_KEY_INVALID, czyli 425. Ta rygorystyczność jest zamierzona. Pobłażliwy parser, który usuwa białe znaki i dopełnia krótki wpis zerami, łatwo zamieni ucięte wklejenie ze schowka w poświadczenie, a następnie zawiedzie w miejscu znacznie trudniejszym do odczytania
Var
Lib: TPDFlib;
FileHandle, PageRef: Integer;
Begin
Lib:= TPDFlib.Create;
Try
// KeyHex to 32 znaków szesnastkowych dla AES-128 R4 i 64 dla AES-256 R6
FileHandle:= Lib.DAOpenFileWithEncryptionKey(CaseFile, KeyHex);
If FileHandle= 0 Then
Raise Exception.CreateFmt('raw key refused (error %d)', [Lib.LastErrorCode]);
Try
PageRef:= Lib.DAFindPage(FileHandle, 1);
WriteLn(Lib.DAExtractPageText(FileHandle, PageRef, 0));
Finally
Lib.DACloseFile(FileHandle);
End;
Finally
Lib.Free;
End;
End;
Pozostałe kody awarii pozostają rozdzielone, aby zadanie wsadowe mogło odróżnić błąd operatora od problemu z dowodem: 411 oznacza brak pliku, 401 niemożność otwarcia go do odczytu, a 409 uszkodzoną strukturę odsyłaczy. Wszystko związane z kluczem jest celowo składane do 425, ponieważ punkt wejścia surowego klucza, który informowałby, która jego część była błędna, stałby się wyrocznią
Co naprawdę potwierdza weryfikacja?
PDF Library for Delphi potwierdza, że dostarczony klucz należy do tego dokumentu, korzystając z weryfikatora, który słownik szyfrowania już zawiera, a sposób sprawdzenia zależy od rewizji. Rewizja 2 ponownie oblicza szyfrowanie RC4 32-bajtowego standardowego ciągu dopełnienia i porównuje wszystkie 32 bajty z /U. Rewizje 3 i 4 haszują dopełnienie razem z identyfikatorem pliku, wykonują przebieg RC4 oraz 19 rund opartych na XOR i porównują pierwsze 16 bajtów /U. Rewizje 5–7 odszyfrowują 16-bajtowy łańcuch /Perms z zerowym IV i sprawdzają naraz cztery niezależne rzeczy: little-endianowe słowo uprawnień względem /P, cztery bajty FF na pozycjach 5–8, flagę encrypt-metadata jako T albo F oraz znacznik adb na pozycjach 10–12
Jeśli weryfikator istnieje, ale nie pasuje, otwarcie jest bezwarunkowo odrzucane. Warto powiedzieć to wprost, bo na tej gwarancji opiera się cała funkcja. Trzeba też zauważyć, czym weryfikacja nie jest: mówi, że klucz odszyfrowuje ten plik, a nie że ktoś upoważnił cię do jego użycia. Słowo uprawnień odczytane z /Perms jest dowodem dotyczącym klucza, a nie zezwoleniem; jeśli chcesz wiedzieć, na co dokument faktycznie pozwala, jest to osobne zadanie dla audytu szyfrowania i uprawnień. Problemy z normalizacją po stronie hasła, takie jak obsługa SASLprep dla haseł AES-256 zawierających znaki spoza ASCII, tutaj nie występują, ponieważ żaden tekst nie trafia do hasza
Kiedy działa PDF_RAW_KEY_ALLOW_UNVERIFIED?
PDF_RAW_KEY_ALLOW_UNVERIFIED obejmuje dokładnie jedną sytuację: dokument nie zawiera użytecznego weryfikatora, ponieważ /Perms brakuje albo nie ma 16 bajtów, lub /U jest zbyt krótkie do porównania. Nie może obejść niezgodnego dowodu. Uszkodź jedną cyfrę szesnastkową w /Perms i podaj poprawny klucz z ustawioną opcją, a PDF Library for Delphi nadal zwróci 0 i 425. Podaj klucz złożony z 32 zerowych bajtów przy poprawnym weryfikatorze i tej samej opcji, a wynik będzie taki sam. Opcja łagodzi brak dowodu, nigdy sprzeczność z dowodem. Ponieważ otwarcie odzyskowe i otwarcie zweryfikowane są różnymi stanami epistemicznymi, są też raportowane osobno zamiast składania ich w wartości zwrotnej, a DAGetEncryptionKeyValidation przyjmuje otwarty uchwyt i odpowiada jedną z trzech stałych:
PDF_RAW_KEY_VALIDATION_VERIFIED(1) oznacza, że weryfikator był obecny i pasowałPDF_RAW_KEY_VALIDATION_UNVERIFIED(2) oznacza, że klucz zaakceptowano tylko dlatego, że nie można było ocenić żadnego weryfikatora, a wywołujący jawnie zażądał takiej politykiPDF_RAW_KEY_VALIDATION_NONE(0) jest raportowane przez zwykły uchwyt otwarty z hasłem
FileHandle:= Lib.DAOpenFileWithEncryptionKey(CaseFile, KeyHex);
If (FileHandle= 0)And
(Lib.LastErrorCode= PDFLIB_ERROR_RAW_ENCRYPTION_KEY_INVALID) Then
// brak weryfikatora w tym pliku? ponów próbę z jawną polityką odzyskiwania
FileHandle:= Lib.DAOpenFileWithEncryptionKey(CaseFile, KeyHex,
PDF_RAW_KEY_ALLOW_UNVERIFIED);
If FileHandle<> 0 Then
Begin
Case Lib.DAGetEncryptionKeyValidation(FileHandle) Of
PDF_RAW_KEY_VALIDATION_VERIFIED:
Chain.Note('key verified against the encryption dictionary');
PDF_RAW_KEY_VALIDATION_UNVERIFIED:
Chain.Note('no verifier available: extraction is unattested');
End;
End;
Tylko do odczytu z konstrukcji i własność strumienia
Wariant plikowy z surowym kluczem zawsze otwiera źródło z fmOpenRead or fmShareDenyWrite i oznacza cały łańcuch Direct Access jako tylko do odczytu, dlatego DAAppendFile odmawia zapisu w miejscu i zwraca 2 zamiast próbować aktualizacji przyrostowej. Nie jest to polityka, od której można odwieść uchwyt; zostaje ustawiona w konstruktorze, zanim plik zostanie choćby sparsowany. W pracy z dowodami potrzebujesz gwarancji, że bajty źródła po zamknięciu uchwytu są identyczne bajt po bajcie, a pakiet regresyjny sprawdza to dokładnie dla wzorca rewizji 4 AES-128 i wzorca rewizji 6 AES-256. Wariant strumieniowy zachowuje się tak samo przy zapisie i dodaje jedną zasadę: DAOpenFromStreamWithEncryptionKey nigdy nie przejmuje własności, więc DACloseFile pozostawia twój TStream żywy, a jego zwolnienie należy do ciebie
Source:= TMemoryStream.Create;
Try
Source.LoadFromFile(ArchivePath);
Source.Position:= 0;
FileHandle:= Lib.DAOpenFromStreamWithEncryptionKey(Source, KeyHex);
If FileHandle<> 0 Then
Try
// 2 = uchwyt tylko do odczytu: eksportuj gdzie indziej, nigdy nie dopisuj do dowodu
Assert(Lib.DAAppendFile(FileHandle)= 2);
Harvest(Lib, FileHandle);
Finally
Lib.DACloseFile(FileHandle);
End;
Finally
Source.Free; // uchwyt nigdy nie był właścicielem tego strumienia
End;
Higiena klucza i to, co eksportuje DLL
Zamknięcie łańcucha nadpisuje klucz pliku, pamięć podręczną haseł i wyprowadzone klucze obiektów, a zdekodowany klucz jest czyszczony w bloku Finally samego punktu wejścia niezależnie od tego, czy otwarcie się powiodło. Kryje się za tym subtelność, która zabrała sporo czasu podczas debugowania: każda kopia, która musi przetrwać, jest jawnie klonowana przez SetLength i Move, a nie przypisywana. Przypisz jeden AnsiString do drugiego w Delphi, a obie nazwy współdzielą bufor dzięki copy-on-write, więc wyczyszczenie strony wywołującej wyzerowałoby klucz nadal używany przez handler szyfrowania, a dokument odszyfrowywałby się do śmieci z powodów, których nie wyjaśniłby żaden stack trace. Granicę DLL przekraczają tylko punkty wejścia plikowe, w wariantach wide i ANSI, wraz z akcesorem statusu walidacji; wariant TStream pozostaje wyłącznie po stronie Delphi, ponieważ zależy od czasu życia obiektów Delphi i semantyki referencji, której nie da się uczciwie odwzorować w płaskim ABI C. Jeśli twoje narzędzie odzyskiwania jest klientem DLL, zaplanuj etapowanie do pliku tymczasowego i jego usunięcie z tymi samymi zabezpieczeniami, które stosujesz wobec klucza
Traktuj klucz szesnastkowy jak materiał uwierzytelniający i stosuj wobec niego zasady obsługi przewidziane dla hasła dokumentu, a status walidacji zapisuj w tym samym dzienniku, który tworzy łańcuch dowodowy, aby późniejszy czytelnik mógł odróżnić ekstrakcję zweryfikowaną od niepoświadczonej. Jeśli oceniasz komponent PDF dla Delphi pod kątem analizy śledczej, masowego archiwizowania albo migracji DRM, punkty wejścia surowego klucza, gwarancja tylko do odczytu i powierzchnia audytu szyfrowania należą do tej samej biblioteki, a pełną listę funkcji znajdziesz na stronie produktu PDF Library for Delphi