PDF Library for Delphi kann ein verschlüsseltes PDF über seinen rohen Dateiverschlüsselungsschlüssel statt über ein Passwort öffnen. DAOpenFileWithEncryptionKey nimmt den Schlüssel als Hexadezimaltext an, prüft ihn gegen den bereits im Verschlüsselungswörterbuch gespeicherten Verifier und liefert ein schreibgeschütztes Direct-Access-Handle; DAOpenFromStreamWithEncryptionKey macht dasselbe für einen vom Aufrufer verwalteten TStream. Beide Einstiegspunkte kamen in v3.496.0 hinzu
Das Szenario ist speziell, aber real. Bei einer forensischen Untersuchung wird ein aus einem Speicherabbild gewonnener Schlüssel ohne Passwort übergeben. Ein Archivlauf verarbeitet zehntausend Dokumente, deren Dateischlüssel in einer Escrow-Datenbank liegen, weil das ursprüngliche DRM-System seit Jahren keine Passwörter mehr ausstellt. Oder eine Migration von einem eingestellten Rechteverwaltungssystem besitzt das Schlüsselmaterial, aber sonst nichts. In all diesen Fällen liegt als Zugangsdaten das Ergebnis der Schlüsselableitung vor und nicht deren Eingabe, und kein Passwortparameter der API kann es aufnehmen
Warum ist ein Dateiverschlüsselungsschlüssel kein Passwort?
Ein Passwort und ein Dateiverschlüsselungsschlüssel liegen bei der PDF-Sicherheitsbehandlung (ISO 32000-1 §7.6.3) auf entgegengesetzten Seiten der Schlüsselableitung. Die Behandlung nimmt ein Passwort, vermischt es mit /O, /P, der Datei-ID und einem versionsabhängigen Hash und erzeugt daraus den Dateischlüssel. Einen Dateischlüssel in den Passwortslot zu stecken, liefert einen Unsinns-Hash aus einem anderen Unsinn, weshalb dafür ein eigener Einstiegspunkt und nicht nur ein Flag an DAOpenFile nötig ist
Wo der Rohschlüssel eingespeist werden kann, ist durch die Revision festgelegt. Revisionen 2 bis 4 leiten weiterhin aus Dateischlüssel, Objektnummer, Generationsnummer und bei AESV2 dem AES-Salt einen eigenen Schlüssel je Objekt ab. Der Besitz des Dateischlüssels erlaubt also keineswegs, die Ableitung auf Objektebene zu überspringen. Revisionen 5 bis 7 verwenden den 32 Byte langen Dateischlüssel direkt für AES-256, ohne einen Schritt je Objekt. Die gemeinsame Schicht beider Wege ist der Dateischlüssel selbst, und genau dort akzeptiert PDF Library for Delphi einen extern gelieferten Schlüssel. Wenn das Passwort noch vorhanden ist, bleiben Sie stattdessen beim normalen Weg und überlassen Sie den Passwort-Wiederholungszyklus dem falschen ersten Versuch, denn der Rohschlüssel-Einstieg verzichtet absichtlich auf einige Annehmlichkeiten, die der Passwortweg bietet
Welche Eingabe akzeptiert DAOpenFileWithEncryptionKey?
Nur unpräfiguriertes, leerraumfreies ASCII-Hex mit gerader Länge, und die Zahl der dekodierten Bytes muss exakt zur Verschlüsselungsrevision passen. Für Revisionen 2 bis 4 stammt die erwartete Länge aus /Length im Verschlüsselungswörterbuch: ein Vielfaches von 8 Bit zwischen 5 und 16 Bytes, bei fehlendem /Length standardmäßig 40 Bit. Für Revisionen 5 bis 7 sind es genau 32 Bytes, ohne Aushandlung. Ein 0x-Präfix, eine ungerade Ziffernzahl, mehr als 64 Hexzeichen, ein unbekanntes Bit in Options oder ein unverschlüsseltes Dokument führen alle zum gleichen Ergebnis: Handle 0 und LastErrorCode auf PDFLIB_ERROR_RAW_ENCRYPTION_KEY_INVALID, also 425. Diese Strenge ist beabsichtigt. Ein nachsichtiger Parser, der Leerraum entfernt und kurze Eingaben mit Nullen auffüllt, macht aus einem abgeschnittenen Clipboard-Inhalt bereitwillig Zugangsdaten und scheitert dann an einer viel schwerer lesbaren Stelle
Var
Lib: TPDFlib;
FileHandle, PageRef: Integer;
Begin
Lib:= TPDFlib.Create;
Try
// KeyHex hat 32 Hexzeichen für AES-128 R4 und 64 für 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;
Die übrigen Fehlercodes bleiben unterscheidbar, damit ein Batchjob Bedienfehler und Beweisprobleme trennen kann: 411, wenn die Datei nicht existiert, 401, wenn sie nicht zum Lesen geöffnet werden kann, 409 bei einer beschädigten Querverweisstruktur. Alles, was den Schlüssel betrifft, fällt absichtlich in 425 zusammen, denn ein Rohschlüssel-Einstiegspunkt, der meldet, welcher Teil des Schlüssels falsch war, wäre ein Orakel
Was beweist die Verifizierung tatsächlich?
PDF Library for Delphi beweist mit dem bereits im Verschlüsselungswörterbuch enthaltenen Verifier, dass der gelieferte Schlüssel zu diesem Dokument gehört; die Prüfung unterscheidet sich je nach Revision. Revision 2 berechnet die RC4-Verschlüsselung des 32 Byte langen Standard-Padding-Strings neu und vergleicht alle 32 Bytes mit /U. Revisionen 3 und 4 hashen das Padding zusammen mit der Datei-ID, führen den RC4-Durchlauf plus die 19 XOR-abgeleiteten Runden aus und vergleichen die ersten 16 Bytes von /U. Revisionen 5 bis 7 entschlüsseln den 16 Byte langen /Perms-String mit einem Null-IV und prüfen gleichzeitig vier unabhängige Dinge: das Little-Endian-Berechtigungswort gegen /P, die vier FF-Bytes an den Positionen 5 bis 8, das Metadaten-Verschlüsselungsflag als T oder F und den adb-Marker an den Positionen 10 bis 12
Existiert ein Verifier, stimmt aber nicht überein, wird das Öffnen bedingungslos abgelehnt. Das sollte man ausdrücklich sagen, denn darauf beruht die gesamte Garantie des Features. Ebenso wichtig ist, was die Verifizierung nicht beweist: Sie sagt, dass der Schlüssel diese Datei entschlüsselt, nicht, dass jemand seine Nutzung autorisiert hat. Das aus /Perms gewonnene Berechtigungswort ist ein Beleg über den Schlüssel und keine Erlaubnis. Wenn Sie wissen müssen, was das Dokument tatsächlich erlaubt, ist das eine separate Aufgabe für einen Prüfungsdurchlauf für Verschlüsselung und Berechtigungen. Probleme bei der Normalisierung von Passwörtern wie SASLprep für Nicht-ASCII-AES-256-Passwörter treten hier nicht auf, weil kein String einen Hash erreicht
Wann greift PDF_RAW_KEY_ALLOW_UNVERIFIED?
PDF_RAW_KEY_ALLOW_UNVERIFIED deckt genau eine Situation ab: Das Dokument besitzt keinen brauchbaren Verifier, weil /Perms fehlt oder nicht 16 Bytes lang ist oder /U für einen Vergleich zu kurz ist. Ein Scheitern der vorhandenen Evidenz kann es nicht überstimmen. Ändern Sie eine Hexziffer in /Perms und übergeben Sie den korrekten Schlüssel mit gesetzter Option, liefert PDF Library for Delphi weiterhin 0 und 425. Übergeben Sie einen Schlüssel aus 32 Nullbytes gegen einen intakten Verifier mit gesetzter Option, ist das Ergebnis dasselbe. Die Option lockert das Fehlen eines Beweises, niemals einen Widerspruch dazu. Weil ein Wiederherstellungsöffnen und ein verifiziertes Öffnen unterschiedliche Erkenntnisstände sind, werden sie auch getrennt gemeldet statt in den Rückgabewert einzufalten, und DAGetEncryptionKeyValidation nimmt das geöffnete Handle und liefert eine von drei Konstanten:
PDF_RAW_KEY_VALIDATION_VERIFIED(1) bedeutet, dass ein Verifier vorhanden war und übereinstimmtePDF_RAW_KEY_VALIDATION_UNVERIFIED(2) bedeutet, dass der Schlüssel nur akzeptiert wurde, weil kein Verifier ausgewertet werden konnte und der Aufrufer diese Richtlinie ausdrücklich angefordert hatPDF_RAW_KEY_VALIDATION_NONE(0) meldet ein gewöhnlich per Passwort geöffnetes Handle
FileHandle:= Lib.DAOpenFileWithEncryptionKey(CaseFile, KeyHex);
If (FileHandle= 0)And
(Lib.LastErrorCode= PDFLIB_ERROR_RAW_ENCRYPTION_KEY_INVALID) Then
// kein Verifier in dieser Datei? Unter der Recovery-Richtlinie erneut versuchen
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;
Von Haus aus schreibgeschützt – und wem der Stream gehört
Der Dateieinstieg mit Rohschlüssel öffnet die Quelle immer mit fmOpenRead or fmShareDenyWrite und markiert die gesamte Direct-Access-Kette als schreibgeschützt. Daher verweigert DAAppendFile das Schreiben an Ort und Stelle und liefert 2, statt ein inkrementelles Update zu versuchen. Das ist keine Richtlinie, von der man das Handle überreden kann; sie wird im Konstruktor gesetzt, noch bevor die Datei geparst wird. Für Beweisarbeit ist entscheidend, dass die Quellbytes nach dem Schließen des Handles byteidentisch sind; genau das prüft die Regressionssuite an einer AES-128-Revision-4- und einer AES-256-Revision-6-Fixture. Der Stream-Einstieg verhält sich bei Schreibzugriffen genauso und ergänzt eine Regel: DAOpenFromStreamWithEncryptionKey übernimmt niemals den Besitz, daher lässt DACloseFile Ihren TStream am Leben und Sie geben ihn selbst frei
Source:= TMemoryStream.Create;
Try
Source.LoadFromFile(ArchivePath);
Source.Position:= 0;
FileHandle:= Lib.DAOpenFromStreamWithEncryptionKey(Source, KeyHex);
If FileHandle<> 0 Then
Try
// 2 = schreibgeschütztes Handle: anderswo exportieren, niemals Beweisdaten anhängen
Assert(Lib.DAAppendFile(FileHandle)= 2);
Harvest(Lib, FileHandle);
Finally
Lib.DACloseFile(FileHandle);
End;
Finally
Source.Free; // das Handle hat diesen Stream nie besessen
End;
Schlüssel-Hygiene und die DLL-Exporte
Beim Schließen der Kette werden Dateischlüssel, Passwort-Cache und abgeleitete Objektschlüssel überschrieben; auch der dekodierte Schlüssel wird im Finally-Block des Einstiegspunkts selbst gelöscht, unabhängig davon, ob das Öffnen erfolgreich war. Dahinter steckt eine Feinheit, die echte Debugging-Zeit gekostet hat: Jede Kopie, die fortbestehen muss, wird ausdrücklich mit SetLength plus Move geklont und nicht zugewiesen. Weist man in Delphi einen AnsiString einem anderen zu, teilen beide Namen unter Copy-on-Write zunächst denselben Puffer. Ein Löschen auf der Aufruferseite würde dann den Schlüssel auf Null setzen, den der Krypto-Handler noch verwendet, und das Dokument aus Gründen, die kein Stacktrace erklärt, in Datenmüll entschlüsseln. Nur die dateibasierten Einstiegspunkte überschreiten die DLL-Grenze, in Wide- und ANSI-Form, zusammen mit dem Zugriff auf den Validierungsstatus. Die TStream-Variante bleibt Delphi-only, weil sie von Delphis Objektlebensdauer und Referenzsemantik abhängt, die in einer flachen C-ABI keine ehrliche Darstellung haben. Wenn Ihr Wiederherstellungswerkzeug ein DLL-Client ist, planen Sie die Zwischenablage in eine temporäre Datei und deren Löschung unter denselben Kontrollen wie beim Schlüssel
Behandeln Sie den Hexschlüssel wie Zugangsdaten mit denselben Regeln wie ein Dokumentpasswort, und protokollieren Sie den Validierungsstatus in dem Log Ihrer Beweiskette. So kann ein späterer Leser eine verifizierte Extraktion von einer nicht attestierten unterscheiden. Wenn Sie eine Delphi-PDF-Komponente für Forensik, Massenarchivierung oder eine DRM-Migration bewerten, gehören die Rohschlüssel-Einstiegspunkte, die Schreibschutzgarantie und die Oberfläche für Verschlüsselungsprüfungen alle zu derselben Bibliothek. Die vollständige Featureliste finden Sie auf der Produktseite der PDF Library for Delphi