💡 Voll funktionsfähiges Beispiel auf GitHub verfügbar: vault-transit-pdf-signing-python
Warum automatisiertes Signieren bei einer Schlüsselverwahrungsprüfung scheitert
Compliance- und Finanzoperationsteams signieren Dokumente in Tausenden: Kreditverträge, Abrechnungen, Richtlinienpakete, Prüfungsbriefe. Fast keines davon wird von einer Person durch Klicken eines Buttons signiert. Ein Dienst erledigt das nach einem Zeitplan, und das an diese Dokumente angehängte Zertifikat trägt den Namen der Organisation. Das macht den Signaturschlüssel zu einem der sensibelsten Dinge, die das Unternehmen besitzt, und er liegt normalerweise in einer PFX-Datei neben der Anwendung, die ihn verwendet.
External signing ist eine GroupDocs.Signature‑Funktion für Python via .NET, die den Dokumenten‑Hash an von Ihnen bereitgestellten Code weitergibt, anstatt mit einem privaten Schlüssel zu signieren, den die Anwendung hält. Sie existiert, weil das PFX‑Modell einer Prüfung nicht standhält. Eine Datei ist eine Kopie, und Kopien verbreiten sich: in das Container‑Image, die Bereitstellungspipeline, das Backup, den Entwickler‑Laptop, der im letzten Frühjahr ein Produktionsproblem debuggt hat. Jeder, der eine dieser Kopien und das Passwort besitzt, kann in Ihrem Namen signieren, bis jemand es bemerkt und das Zertifikat widerruft.
Was diese Teams benötigen, ist kein anderes Dateiformat. Sie benötigen, dass der Schlüssel an genau einem Ort existiert, mit einem Prüfprotokoll jedes Mal, wenn er verwendet wurde, und einem Signaturdienst, der um eine Signatur bitten kann, ohne jemals Zugriff auf den Schlüssel zu erhalten. Dafür ist ein Schlüsselmanager wie HashiCorp Vault gedacht, und er hilft nur, wenn die Dokumentenbibliothek angewiesen werden kann, die Kryptografie zu delegieren.
Warum eine passwortgeschützte Zertifikatsdatei nicht ausreicht
Der Instinkt ist, das PFX zu sichern: Dateiberechtigungen einschränken, das Passwort in einem Secret Manager speichern, das Volume verschlüsseln. Jeder Schritt hilft, aber keiner ändert die Grundwahrheit. Im Moment der Signatur wird der private Schlüssel in den Prozess geladen, sodass ein Speicher‑Dump, ein Container‑Escape oder eine nachlässige Log‑Ausgabe ihn immer noch preisgeben kann. Die Kontrolle, die Sie wollen, ist ein Schlüssel, der überhaupt nicht gelesen werden kann, nicht ein Schlüssel, der nur schwerer zu lesen ist.
Die Transit‑Engine von Vault bietet genau das. Sie führt Kryptografie als Service aus: Sie senden einen Wert, sie signiert oder verschlüsselt mit einem Schlüssel, den sie hält, und gibt das Ergebnis zurück. In ihr erstellte Schlüssel können als nicht exportierbar markiert werden, und sie speichert niemals, was sie erhält. GroupDocs.Signature deckt die andere Hälfte ab, weil DigitalSignOptions.custom_sign_hash es Ihnen ermöglicht, Ihren eigenen Signierer in die PDF‑Signaturpipeline einzubinden.
Die Einrichtung: GroupDocs.Signature für Python mit Vault Transit
GroupDocs.Signature erledigt alles bis zur Kryptografie. Es öffnet das PDF, bestimmt, welchen Byte‑Bereich die Signatur abdeckt, hash‑t diesen Bereich und ruft Ihren Signierer mit dem Hash auf. Ihr Signierer übergibt den Hash an Vault und gibt die von Vault erzeugten Bytes zurück. Die Bibliothek bettet sie zusammen mit dem Zertifikat ein und schreibt ein normal signiertes PDF.
Sie benötigen Python 3.11 oder 3.13, die Pakete groupdocs-signature-net und asn1crypto sowie Docker für einen Entwicklungs‑Vault‑Container. Eine GroupDocs‑Lizenz ist optional; ohne eine akzeptiert der Evaluierungsmodus Dokumente von bis zu 2 Seiten. Das Beispiel‑Repository läuft von Anfang bis Ende mit zwei Befehlen, und derselbe Code verweist auf einen Produktions‑Vault, indem zwei Umgebungsvariablen geändert werden.
Schrittweise Erstellung
Schritt 1 – Lesen des Signaturschlüssels aus Vault
Die Anwendung fragt Vault nach dem Schlüssel, den sie verwenden darf, und erhält eine Versionsnummer sowie einen öffentlichen Schlüssel zurück. Der private Teil ist nie Teil der Antwort.
key = vault.read_key(KEY_NAME)
if key is None:
# First run: enable Transit and create an RSA-2048 key.
vault.enable_transit()
vault.create_key(KEY_NAME, "rsa-2048")
key = vault.read_key(KEY_NAME)
print(f"Signing key: '{KEY_NAME}', version {key.version}")
Das einmalige Auslesen der Version ist der Zweck dieses Schrittes. Ein Zertifikat enthält einen öffentlichen Schlüssel, daher müssen das Zertifikat und jede Signatur aus derselben Version stammen.
Schritt 2 – Erstellen eines Zertifikats für einen Schlüssel, den niemand exportieren kann
Eine PDF‑Signatur enthält ein X.509‑Zertifikat, damit ein Prüfer den öffentlichen Schlüssel finden kann. Bei einem nicht exportierbaren Schlüssel wird das Zertifikat um den öffentlichen Teil herum aufgebaut und von Vault signiert.
tbs_certificate = x509.TbsCertificate({...}) # built around the Vault public key
# Hash locally, then have Vault sign the certificate's own digest.
digest = hashlib.sha256(tbs_certificate.dump()).digest()
signature = vault.sign_hash(key_name, key.version, digest, "sha2-256")
certificate = x509.Certificate({
"tbs_certificate": tbs_certificate,
"signature_algorithm": algorithm,
"signature_value": signature,
})
Das Zertifikat beginnt bewusst einen Tag vor dem aktuellen Datum. Ein PDF‑Reader vergleicht die Signaturzeit mit diesem Startdatum, und auf einer Maschine, die zwei Stunden östlich von UTC liegt, wurde ein Zertifikat, das Sekunden vor der Signatur ausgestellt wurde, von Adobe Acrobat als abgelaufen oder noch nicht gültig gemeldet.
Schritt 3 – Signieren des PDFs über den externen Signierer
Zwei Zeilen sind spezifisch für Python. Das Binding hat keinen X.509‑Typ, daher kommt das Zertifikatsobjekt aus DigitalVerifyOptions zurück; das Übergeben eines Pfads an DigitalSignOptions löst stattdessen den Fehler “Specified certificate doesn’t contain private key” aus. Und sign_time ist bewusst zeitzonenbewusst, weil ein Wert mit Offset in das PDF geschrieben wird.
Im Signierer wird der Hash mit prehashed = true und pkcs1v15 an Vault gesendet. Diese beiden Felder bestimmen, ob die Signatur gültig ist, und das erste schlägt stillschweigend fehl, wenn es fehlt.
certificate_object = DigitalVerifyOptions(
certificate_stream=io.BytesIO(certificate)).certificate
digital_signature = DigitalSignature(certificate=certificate_object)
digital_signature.sign_time = datetime.now().astimezone()
options = DigitalSignOptions()
options.signature = digital_signature
options.custom_sign_hash = VaultSigner(vault, KEY_NAME, key.version)
options.hash_algorithm = HashAlgorithm.SHA256
options.reason = "Document signed with HashiCorp Vault Transit"
with gs.Signature(str(input_pdf)) as signature:
signature.sign(str(output_pdf), options)
Schritt 4 – Verifizieren, bevor etwas den Dienst verlässt
options = DigitalVerifyOptions(certificate_stream=io.BytesIO(certificate))
with gs.Signature(str(signed_pdf)) as signature:
is_valid = signature.verify(options).is_valid
Signieren und Verifizieren im selben Durchlauf wandelt den stillen Fehlermodus in einen sofortigen um. Für eine Dokumenten‑Pipeline ist das das Tor: eine Datei, die nicht verifiziert wird, erreicht nie das Archiv, den Gegenpart oder das Kunden‑Portal.
Geschäftliche Auswirkungen: Was sich für das Audit ändert
| Bedenken | PFX auf dem Anwendungs‑Host | Signieren über Vault Transit |
|---|---|---|
| Kopien des privaten Schlüssels | eine pro Host, Image, Backup und Laptop | eine, innerhalb von Vault |
| Berechtigungen, die die Signatur‑App benötigt | voller Zugriff auf die Schlüsseldatei | Schlüssel lesen, transit/sign aufrufen |
| Nachweis, dass ein Dokument signiert wurde | Anwendungs‑Logs, falls jemand sie hinzugefügt hat | ein Vault‑Audit‑Eintrag pro Signatur |
| Reaktion auf einen vermuteten Leak | widerrufen, neu ausstellen, überall neu bereitstellen | Schlüssel in Vault rotieren, einmal neu ausstellen |
| Wer in Ihrem Namen signieren kann | jeder mit der Datei und dem Passwort | jeder, der ein gültiges Vault‑Token besitzt |
Die letzte Zeile ist die ehrliche. Dieses Design verlagert das Geheimnis, anstatt es zu entfernen: ein gestohlenes Vault‑Token kann weiterhin Signaturen anfordern. Der Unterschied ist, dass ein Token kurzlebig, an einem Ort widerrufbar, auf zwei Vorgänge beschränkt und jede Nutzung protokolliert wird. Das ist in einer Prüfung wesentlich leichter zu verteidigen als eine Datei, deren Kopien niemand aufzählen kann.
Realer Ablauf: ein Kreditvertrag, der von einem Dienst signiert wird
Eine Kreditplattform erzeugt ein Vertrags‑PDF, wenn ein Antrag genehmigt wird, signiert es und legt es im Dokumenten‑Vault des Kunden ab. Vor dieser Änderung hielt der Signatur‑Dienst das Unternehmenszertifikat und das Bereitstellungs‑Runbook enthielt einen Schritt, das PFX auf neue Hosts zu kopieren. Danach hält der Dienst ein AppRole‑Token mit zwei Berechtigungen, und der private Schlüssel des Zertifikats hat nie irgendwo außer Vault existiert.
Wenn Auditoren fragen, wie viele Dokumente im letzten Quartal signiert wurden und mit welchem Schlüssel, kommt die Antwort vom Audit‑Device von Vault statt aus der Rekonstruktion von Anwendungs‑Logs. Wenn das Team den Schlüssel rotiert, rotieren sie ihn an einem Ort und stellen ein neues Zertifikat für die neue Version aus.
Was sollte ich zuerst richtig machen?
Die Handhabung des Hashes. Ein falscher Algorithmusname schlägt laut fehl, wobei Vault mit 500 crypto/rsa: input must be hashed message antwortet, und kostet eine Minute. Ein fehlendes prehashed‑Flag erzeugt überhaupt keinen Fehler: Vault hash‑t den Digest erneut, die Signatur deckt den falschen Wert ab, und das PDF wirkt fertig. Verifizieren Sie jede Datei, die Sie signieren, und dieser Fehler wird nicht mehr unsichtbar.
Weitere Anwendungsbereiche für dieses Design
Der Hook weiß nicht, was dahinter steckt, sodass dieselben vier Schritte gegen ein Hardware‑Security‑Modul, ein Cloud‑KMS oder eine Smart‑Card funktionieren. Ich habe diesen Build von seinem .NET‑Gegenstück portiert und erwartete, dass der Signierer überarbeitet werden muss, und der einzige Teil, der sich wirklich geändert hat, war die Übergabe des Zertifikats; der Vault‑Request‑Body und das Version‑Pinning waren identisch. Teams im Gesundheitswesen, in der Regierung und in der Versicherung greifen aus demselben Grund darauf zurück wie die Finanzabteilung: Der Schlüssel muss an einem Ort sein, der nachweisen kann, wofür er verwendet wurde.
Erste Schritte
- Kostenlos starten: Testlizenz erhalten, voller API‑Zugriff
- Integrations‑Leitfaden lesen: PDFs mit einem Vault‑Transit‑Schlüssel in Python signieren
- Code durchsuchen: vault-transit-pdf-signing-python
- Implementierungsfragen im Community‑Forum stellen