Technischer Artikel

PDF-Verschlüsselung und Berechtigungen in Delphi prüfen

Ein Berechtigungsflag ist kein Sicherheitsmechanismus. Das Bit, das „kein Kopieren“ sagt, liegt im selben /Encrypt-Dictionary wie die Kryptografie, was ihm einen Anschein von Durchsetzung verleiht, den es nicht hat, und sobald Sie beides als eine Sache behandeln, beginnt Ihr Audit falsche Antworten zu liefern. Die einzige Frage, die sich an ein PDF zu stellen lohnt, lautet nicht „ist es verschlüsselt“. Sie ist spezifischer und schwerer: welcher Algorithmus, welche Revision des Security-Handlers, welches der beiden Passwörter gesetzt wurde, welche Berechtigungsbits beansprucht werden und welche Teile der Datei die Verschlüsselung tatsächlich berührt. Eine Datei kann formal verschlüsselt und praktisch offen sein. Sie kann sich dem Lesen verweigern und trotzdem ihre Metadaten im Klartext lassen. Sie kann das Drucken in einem Flag sperren, das jeder Viewer ignorieren darf. Ein PDF zu auditieren heißt, all das getrennt aufzulösen, und PDF Library for Delphi, die PDF-Engine von losLab für Delphi und C++Builder, stellt jeden dieser Punkte sowohl über eine flache Integer-Handle-API als auch über eine typisierte Klassenschicht bereit

Was das /Encrypt-Dictionary tatsächlich festhält

ISO 32000-1 §7.6 definiert die Dokumentsicherheit über eine Handvoll Dictionary-Einträge, und PDF Library for Delphi spiegelt sie eins zu eins im Record TPDFEncryption. Filterversion V und Revision R wählen die Algorithmusfamilie. Length trägt die Schlüsselgröße. Die Berechtigungsbits sitzen in P, die Validierungsstrings für Owner- und User-Passwort in O und U (für AES-256 ergänzt um OE und UE), ein EncryptMetadata-Flag läuft nebenher mit, und drei weitere Felder benennen die Crypt-Filter, die jeweils auf Strings, Streams und eingebettete Dateien angewendet werden

Der Wert dieses Records liegt darin, dass er nichts für Sie interpretiert. Er liefert das rohe Dictionary und überlässt Ihnen die Schlussfolgerungen, genau das, was ein Audit braucht. Der Fall „Klartext innerhalb von verschlüsselt“ zeigt sich in StringFilterIdentity und StreamFilterIdentity: Ist eines davon wahr, passieren die entsprechenden Daten den Identity-Filter unverändert, ganz gleich, was der Verschlüsselungsstatus des Dokuments meldet. Ein Scanner, der bei „ein /Encrypt-Dictionary ist vorhanden“ stehen bleibt, nennt eine solche Datei geschützt, während ihre Strings und Streams im Klartext liegen. Dieselbe Nuance gilt für Metadaten. Ist EncryptMetadata falsch, bleibt das XMP-Paket für jeden Indexer lesbar, der Seiteninhalt hingegen nicht, was man in dem Moment wissen sollte, in dem die eigenen Routing-Regeln auf ein Titel- oder Autorfeld reagieren

PDF Library for Delphi: Diagramm der Felder des PDF-/Encrypt-Dictionarys, abgebildet auf die Audit-Eigenschaften von TPDFEncryption, einschließlich der Identity-Crypt-Filter-Fallen
Jeder /Encrypt-Eintrag bildet auf ein TPDFEncryption-Feld ab, und die Identity-Filter-Flags verraten, welche Strings, Streams oder Metadaten unabhängig vom Verschlüsselungsstatus lesbar bleiben

Eine kurze Sicherheitssonde mit der flachen API

Für die meisten Pipelines beantworten vier flache Aufrufe die Alltagsfragen. LoadFromFile liefert bei Erfolg 1, und sobald das Dokument offen ist, berichten die Verschlüsselungsinspektoren über seinen entschlüsselten Zustand:

var
  PDF: TPDFlib;
begin
  PDF := TPDFlib.Create;
  try
    if PDF.LoadFromFile('contract.pdf', UserPassword) <> 1 then
      raise Exception.Create('Open failed: wrong password or damaged file');
    Writeln('status    : ', PDF.EncryptionStatus);     // entschlüsselt / verschlüsselt / unbekannt
    Writeln('algorithm : ', PDF.EncryptionAlgorithm);  // RC4- vs. AES-Familie
    Writeln('strength  : ', PDF.EncryptionStrength);   // Schlüssellängenklasse
    Writeln('owner pw? : ', PDF.CheckPassword(CandidatePassword));
  finally
    PDF.Free;
  end;
end;

CheckPassword ist wichtiger, als seine einzeilige Signatur vermuten lässt. PDF definiert zwei Passwörter mit ungleicher Macht. Das User-Passwort ist nötig, um die Datei überhaupt zu öffnen. Das Owner-Passwort gewährt volle Rechte und übersteuert jedes Berechtigungsbit. Die Bytes auf der Festplatte sind in beiden Fällen identisch, aber eine mit dem Owner-Passwort geöffnete Sitzung kann Dinge tun, die die User-Passwort-Sitzung nicht kann, sodass ein Audit, das nicht festhält, welche Berechtigung vorgelegt wurde, nur die halbe Wahrheit festhält. Die Klassenschicht macht die Unterscheidung abfragbar. TPDFDocument.HasUserPassword und HasOwnerPassword melden, was die Datei verlangt, während IsUserPassword und IsOwnerPassword melden, welches Passwort die aktuelle Sitzung tatsächlich geöffnet hat. Protokollieren Sie diesen Umstand. Protokollieren Sie nie die Passwortwerte selbst

Die Strength-Leiter, auf der „AES-256“ zweierlei bedeutet

Die flachen Funktionen Encrypt und EncryptFile nehmen einen Integer Strength mit fünf bedeutsamen Werten: 0 für 40-Bit-RC4, 1 für 128-Bit-RC4, 2 für 128-Bit-AES, lesbar ab Acrobat 7, 3 für 256-Bit-AES, wie mit Acrobat 9 eingeführt, und 4 für 256-Bit-AES, wie von Acrobat X und später verlangt

Das Interessante ist, dass 3 und 4 beide als AES-256 bezeichnet werden und nicht dasselbe Schema sind. Strength 3 entspricht Security-Handler-Revision 5, einem Übergangsdesign, das Acrobat 9 auslieferte und das die ISO nie übernahm. Strength 4 entspricht Revision 6, deren Schlüsselableitungsfunktion gehärtet und in ISO 32000-2 standardisiert wurde. Für ein Dokument, das Sie heute erstellen, gibt es keinen Grund, 3 statt 4 zu wählen. Für ein Audit ist die Lücke entscheidend: Eine Richtlinie, die „AES-256 nach ISO 32000-2“ verlangt, erfüllt allein R6, und eine R5-Datei, die sich AES-256 nennt, fällt bei dieser Richtlinie durch, während sie eine naive Stärkeprüfung besteht. Die Klassenschicht hält beide namentlich auseinander, esAES256Bit für R5 gegenüber esAES256BitAcroX für R6, und die Eigenschaft EncryptionAcroX beantwortet die Revisionsfrage mit einem einzigen Boolean

PDF-Verschlüsselungs-Strength-Leiter von 40-Bit-RC4 bis AES-256 Revision 5 gegenüber Revision 6 für Delphi-Audits
Strength 3 und 4 heißen beide AES-256, doch nur Revision 6 erfüllt eine ISO-32000-2-Richtlinie, daher müssen Audits die Handler-Revision festhalten statt nur das Etikett

Berechtigungsbits und ihr Kleingedrucktes zur Schlüssellänge

EncodePermissions packt acht Flags in den Integer, den Encrypt und EncryptFile erwarten. Drucken, Kopieren, Ändern und Notizen-Hinzufügen bilden den Grundsatz; Felder-Ausfüllen, Kopieren-für-Barrierefreiheit, Zusammenstellen und Drucken-in-voller-Qualität bilden den erweiterten Satz. Das Kleingedruckte, das die bibliothekseigene Verschlüsselungsdemo ausdrücklich nennt: Die erweiterten vier greifen nur ab 128-Bit-Stärke. Das Flag für Druck in voller Qualität fällt unter dieselbe Regel: Löschen Sie es, um niedrig aufgelöstes Drucken zu erzwingen, ignoriert Sie ein 40-Bit-Dokument, denn auch diese Herabstufung erfordert 128-Bit- oder stärkere Verschlüsselung. Kodieren Sie eine Richtlinie „nur niedrig aufgelöst drucken“ in eine 40-Bit-Datei, druckt trotzdem jeder Viewer in voller Qualität

Die tiefere Frage ist, wer eines dieser Bits durchsetzt, und die Antwort lautet: niemand, dem Sie vertrauen können. Berechtigungen sind Anweisungen an konforme Reader, keine kryptografischen Beschränkungen. Der Entschlüsselungsschlüssel ist identisch, ob Kopieren erlaubt oder verweigert ist, ein restriktiver Berechtigungssatz hält also nur ehrliche Viewer ehrlich. Ein Reader, der die Bits ignorieren will, stößt auf keinerlei kryptografisches Hindernis. Besteht die Pflicht darin, Extraktion zu verhindern statt sie zu erschweren, braucht die Datei ein User-Passwort und der Workflow Kontrollen auf Prozessebene drumherum, und ein Audit-Bericht sollte benennen, unter welchem der beiden Regimes jede Datei tatsächlich steht, statt ein Berechtigungsflag als Schloss zu behandeln

Richtlinie setzen und nachweisen, dass sie gegriffen hat

Um bestehende Dateien zu verschlüsseln, müssen sie nicht in den Objektbaum geladen werden. EncryptFile verarbeitet Eingabe zu Ausgabe in einem einzigen Aufruf, und die Audit-Schleife öffnet das Ergebnis erneut, um zu bestätigen, was auf der Festplatte gelandet ist. Die mitgelieferte Verschlüsselungsdemo folgt demselben Muster aus Schreiben und Zurücklesen:

var
  PDF: TPDFlib;
  R: Integer;
begin
  PDF := TPDFlib.Create;
  try
    R := PDF.EncryptFile('in.pdf', 'out.pdf', 'owner-secret', 'user-secret', 4,
      PDF.EncodePermissions(1, 0, 0, 0,    // Drucken erlaubt; Kopieren/Ändern/Notizen verweigert
                            0, 0, 0, 1));  // erweiterter Satz: nur Druck in voller Qualität
    if (R = 1) and (PDF.LoadFromFile('out.pdf', 'user-secret') = 1) then
    begin
      Writeln('algorithm = ', PDF.EncryptionAlgorithm);
      Writeln('strength  = ', PDF.EncryptionStrength);
      Writeln('owner pw accepted: ', PDF.CheckPassword('owner-secret'));
    end;
  finally
    PDF.Free;
  end;
end;

Teams, die auf Dokumentebene arbeiten, bekommen dieselbe Operation mit typisierten Mengen statt Bit-Packen, was ein Code-Review mit deutlich weniger Stirnrunzeln übersteht:

if not Doc.Encrypt('owner-secret', 'user-secret', esAES256BitAcroX,
  [ppCanPrint], [ppCanPrintFull]) then
  raise Exception.Create('Encryption failed');

So oder so ist der Rücklese-Schritt kein optionales Zeremoniell. Er fängt die Deployment-Fehler ab, die sonst Monate später auf der Maschine eines Kunden auftauchen: ein alter Bibliotheks-Build, der die angeforderte Stärke stillschweigend herabstuft, ein Ausgabepfad, der nie geschrieben wurde, weil das Verzeichnis schreibgeschützt war, ein Berechtigungs-Integer, dessen Argumente in falscher Reihenfolge hineingingen. Alle drei bestehen einen lokalen Smoke-Test und versagen im Feld, und das erneute Öffnen der Ausgabe verwandelt jeden davon in eine Exception, die Sie während des Laufs sehen, der die Datei erzeugt hat. GetEncryptionFingerprint liefert einen kompakten Wert, den Sie beim Job-Datensatz speichern können, sodass ein späterer Vergleich sagen kann, ob zwei Ausgaben dieselbe Verschlüsselungskonfiguration teilen, ohne eine von beiden erneut zu öffnen

Audit-Fehlalarme, für die sich Code lohnt

Ein paar Muster treiben Sicherheitsscanner zuverlässig zum falschen Schluss, und jedes davon entsteht, wenn eine mehrteilige Frage auf eine Ja-oder-Nein-Antwort zusammengefaltet wird. Der Identity-Crypt-Filter ist das sauberste Beispiel. Ein /Encrypt-Dictionary ist vorhanden, die Datei meldet sich als verschlüsselt, und doch laufen Strings und Streams unverändert durch den Identity-Filter, der eigentliche Inhalt ist also Klartext. StringFilterIdentity und StreamFilterIdentity zu lesen, bevor man irgendetwas für geschützt erklärt, ist die Abhilfe

Die Metadaten-Trennung ist subtiler. EncryptMetadata kann dem Rest des Dokuments in beide Richtungen widersprechen und eine verschlüsselte Datei mit lesbarem XMP-Paket hinterlassen oder, seltener, das Umgekehrte. „Die Datei ist verschlüsselt“ sagt nichts darüber, ob es ihre Metadaten sind, was in dem Moment zählt, in dem ein Indexer oder eine Routing-Regel nach dem Titel greift. Eingebettete Dateien fügen eine dritte Achse hinzu: PDF erlaubt einen eigenen Crypt-Filter nur für Anhänge, sodass die Anhänge der einzige verschlüsselte Teil eines ansonsten offenen Dokuments sein können oder der einzige Klartextteil eines verschlüsselten. Erfassen Sie die drei Filterzuweisungen als getrennte Felder für Strings, Streams und eingebettete Dateien, und keine dieser Fallen kann Sie erwischen. Speichern Sie einen einzigen Boolean, und die falsche Entscheidung ist nur eine Frage der Zeit

PDF Library for Delphi: Audit-Ablauf, der StringFilterIdentity, StreamFilterIdentity, EncryptMetadata und den Crypt-Filter für eingebettete Dateien prüft, bevor ein PDF als geschützt gilt
Vier unabhängige Achsen entscheiden, ob eine verschlüsselt wirkende Datei wirklich versiegelt ist, und wer sie auf einen Boolean zusammenfaltet, klassifiziert irgendwann eine Datei falsch

Verschlüsselung entfernen und für neue Dateien wählen

Ein Audit endet oft mit der Entscheidung, den Schutz zu entfernen, und die Mechanik ist dabei nicht das Hindernis. DecryptFile(InputFileName, OutputFileName, Password) schreibt eine entschlüsselte Kopie ohne vollständiges Laden, und das Decrypt des geladenen Dokuments tut dasselbe im Speicher, sobald eine Datei bereits offen ist. Beide verlangen ein gültiges Passwort; keines umgeht die Kryptografie. Das eigentliche Tor ist Richtlinie, nicht Code, lassen Sie Ihre Eingangsregeln also klar sagen, wann das Entfernen erlaubt ist, und halten Sie die Passwortklasse fest, die es autorisiert hat, denn der technische Schritt selbst hinterlässt keine Spur

Die Wahl für neue Ausgaben ist enger, als die fünf Strength-Werte nahelegen. Verwenden Sie Strength 4, AES-256 Revision 6, sofern Sie Dateien nicht in Viewern älter als Acrobat X öffnen müssen. Strength 2, AES-128, ist die pragmatische Untergrenze für eine alternde Viewer-Flotte, die sich nicht aktualisieren lässt. Die RC4-Optionen 0 und 1 sind da, damit Sie historische Archive lesen und auditieren können, nicht damit Sie etwas Neues damit erzeugen; wer in einem Design von 2026 danach greift, hat ein Zeichen dafür, dass eine Anforderung weiter vorne veraltet ist

Der Verschlüsselungszustand fließt direkt in Signaturentscheidungen ein, denn eine Workbench, die Dokumente validiert und signiert, braucht dieselbe Rücklese-Disziplin, auf die dieses Audit setzt. Dieses Terrain deckt der Artikel zur Compliance- und Signatur-Workbench ab. Wenn ein Batch EncryptFile über Tausende großer Dokumente anwendet, zeigt der Direktzugriffs-Leitfaden zu großen PDFs, wie der Speicherbedarf dabei flach bleibt. Die vollständige Referenz der Verschlüsselungs-API finden Sie auf der Produktseite von PDF Library for Delphi