💡 Voll funktionsfähiges Beispiel auf GitHub verfügbar:
qr-sign-password-protected-pdf-python

Einführung

Es gibt ein dreistufiges Muster, das die meisten Teams anwenden, wenn ein zu signierendes Dokument verschlüsselt ist: Entschlüsseln, den Klartext signieren, das Ergebnis wieder verschlüsseln. Es funktioniert. Es bedeutet jedoch auch, dass für ein paar hundert Millisekunden eine lesbare Kopie eines bewusst geschützten Dokuments in einem temporären Verzeichnis existiert, und in einer auditierten Pipeline ist dieses Zeitfenster das eigentliche Problem und nicht die Signatur.

Das Signieren eines geschützten PDFs ist eine GroupDocs.Signature‑Funktion für Python via .NET, die diese drei Schritte vollständig überspringt: Das Passwort öffnet die Quelle an Ort und Stelle, die Signatur wird angewendet und die Ausgabe wird wieder geschützt zurückgeschrieben. Dieser Artikel vergleicht die vier Passwort‑Pfade – zwei, die funktionieren, und zwei, die absichtlich fehlschlagen – und behandelt den Fehlvertrag, der für diese Bindung spezifisch ist.

Warum das wichtig ist

Passwort‑Handling ist dort, wo Dokumenten‑Pipelines lecken. Nicht durch die Signatur‑Bibliothek selbst, sondern meist durch das Gerüst drumherum: die temporäre Datei, die eigentlich gelöscht werden sollte, der Ausnahme‑Handler, der einen falschen Passwort‑Fehler verschluckt und endlos erneut versucht, die signierte Kopie, die mit einem Passwort übergeben wird, das dem Empfänger nie mitgeteilt wurde.

Alle drei haben dieselbe Grundursache: Das Passwort wird als etwas behandelt, das aus dem Weg geräumt werden muss, anstatt als Teil der Operation. LoadOptions und SaveOptions setzen es wieder in die Operation ein.

Voraussetzungen

Python 3 und groupdocs-signature-net==26.1, plus ein PDF mit einem Benutzer‑Passwort. Ohne Lizenz läuft die Bibliothek im Evaluierungsmodus, signiert zwar, fügt aber eigenen Text zur Seite hinzu.

Installation

pip install groupdocs-signature-net==26.1

Methode 1 – Originales Passwort beibehalten

Der Standard und die Variante, die am wenigsten Code erfordert. Das Passwort wird über LoadOptions übergeben, und es wird überhaupt kein SaveOptions übergeben:

load_options = LoadOptions()
load_options.password = password
options = _build_qr_options(qr_text)
with signature.Signature(source_path, load_options) as sign:
    result = sign.sign(output_path, options)
    return len(result.succeeded)

Das Fehlen von SaveOptions leistet hier die eigentliche Arbeit. use_original_password ist standardmäßig True, sodass GroupDocs das Quell‑Passwort auf die signierte Ausgabe erneut anwendet. Es gibt keinen Moment, in dem eine ungeschützte Version existiert, weder auf der Festplatte noch anderswo, und len(result.succeeded) gibt an, wie viele Signaturen geschrieben wurden.

Methode 2 – Signierte Kopie neu verschlüsseln

Wenn das signierte Dokument an eine andere Partei übergeben wird, ist es sinnvoll, der Kopie eigene Anmeldedaten zu geben und die Quelle unverändert zu lassen:

save_options = SaveOptions()
save_options.password = new_password
save_options.use_original_password = False
with signature.Signature(source_path, load_options) as sign:
    result = sign.sign(output_path, options, save_options)
    return len(result.succeeded)

Beide Zeilen von SaveOptions sind erforderlich, und das ist die Detail‑Erinnerung: Das Setzen von password, während use_original_password auf dem Standard bleibt, bewirkt nichts Sichtbares. Das Flag gewinnt, die Ausgabe behält das alte Passwort, und Sie entdecken es, wenn der Empfänger meldet, dass das von Ihnen gesendete Passwort nicht funktioniert.

Methode 3 und 4 – Die beiden Fehlermöglichkeiten

Ein verschlüsseltes Dokument reagiert unterschiedlich auf ein fehlendes Passwort und ein falsches, und dieser Unterschied sollte behandelt werden.

Ohne LoadOptions schlägt das Öffnen fehl und es wird nichts geschrieben:

try:
    with signature.Signature(source_path) as sign:
        sign.sign(output_path, options)
    return ""
except RuntimeError as error:
    return proxy_error_name(error)

Das liefert PasswordRequiredException. Wird stattdessen ein falsches Passwort übergeben, liefert derselbe Code IncorrectPasswordException. Das eine bedeutet, den Benutzer nach einem Anmelde­datum zu fragen; das andere bedeutet, dass das vorhandene Anmelde­datum veraltet ist. Ein Handler, der sie nicht unterscheiden kann, versucht immer wieder ein Passwort, das niemals funktionieren wird.

Der Fehlvertrag und warum der offensichtliche Code scheitert

Hier ist der Teil, der einen Nachmittag kostet, wenn niemand Sie warnt. Die Bindung stellt PasswordRequiredException, IncorrectPasswordException und GroupDocsSignatureException als nackte Namen bereit, die nicht von BaseException erben. Schreiben Sie den intuitiven Handler:

except IncorrectPasswordException:
    ...

und Python wirft TypeError: catching classes that do not inherit from BaseException is not allowed. Der ursprüngliche Fehler ist verschwunden, ersetzt durch einen, der auf Ihre except‑Zeile zeigt statt auf das Passwort. Ich habe genau diesen Handler beim ersten Mal geschrieben, und die zwanzig Minuten, die ich damit verbrachte, den TypeError zu lesen, sind der Grund, warum dieser Abschnitt existiert.

Was tatsächlich ankommt, ist ein RuntimeError, dessen Meldung mit Proxy error(<Name>): beginnt. Das Parsen dieses Präfixes ermittelt die Ursache:

message = str(error)
marker = "Proxy error("
if not message.startswith(marker):
    return ""
start = len(marker)
end = message.find(")", start)
if end < 0:
    return ""
return message[start:end]

Verzweigen Sie nach dem zurückgegebenen Namen und nicht nach dem Meldungstext, der Dateipfade enthält und zwischen Durchläufen variiert.

Vor dem Signieren prüfen

Es gibt einen fünften Pfad, der es wert ist, bekannt zu sein, und er schreibt überhaupt nichts. Das Öffnen des Dokuments mit LoadOptions und das Aufrufen von get_document_info liefert das Format, die Seitenzahl und die Größe, während die Datei auf der Festplatte verschlüsselt bleibt:

with signature.Signature(source_path, load_options) as sign:
    info = sign.get_document_info()
    return info.file_type.file_format, info.page_count, info.size

Zwei Anwendungsfälle dafür. Wenn das Passwort aus einem Benutzerformular stammt, validiert dies das Anmelde­datum mit einem günstigen Aufruf, anstatt erst mitten in einem Stapel von zweihundert Dokumenten. Und wenn eine Pipeline überhaupt keinen Klartext speichern darf, ermöglicht sie dennoch, dass die Pipeline berichtet, was sie hält – Seitenzahlen für ein Audit‑Log, Größen für ein Kontingent – ohne irgendetwas zu entschlüsseln.

Vergleich der Methoden: Wann welche verwenden

Methode Am besten für Wesentliche Vorteile Einschränkungen
Keep original password Pipelines, die vor Ort signieren keine SaveOptions, nichts wird im Klartext geschrieben Empfänger benötigt das Quell‑Passwort
Re-key on save Übergabe an eine andere Partei Quelle behält ihr Passwort, Kopie bekommt ein neues zwei SaveOptions‑Zeilen, leicht, nur eine zu setzen
No password (fails) Vertrag in Tests nachweisen schlägt beim Öffnen fehl, schreibt nichts kein Signatur‑Pfad
Wrong password (fails) veraltetes Anmelde­datum unterscheiden eindeutiger Ausnahme‑Name kein Signatur‑Pfad

Lohnt sich das Auslesen für den zusätzlichen Aufruf?

Ja, aus zwei Gründen. Das erneute Öffnen der signierten Datei mit QrCodeVerifyOptions beweist, dass die Signatur das Speichern überlebt hat, und weil beim erneuten Öffnen das Passwort angegeben werden muss, beweist es zudem, dass die Ausgabe tatsächlich noch verschlüsselt ist. Eine Null‑Anzahl ist fast immer ein Lizenzproblem und kein Signatur‑Fehler – der Signatur‑Aufruf wirft, wenn er wirklich fehlschlägt, sodass Stille plus Null‑Treffer auf einen nicht lizenzierten Build hinweist.

Was der Wechsel kostet

Nichts strukturelles. Wenn Ihr Code bereits in eine temporäre Datei entschlüsselt, besteht die Änderung darin, diesen Schritt zu entfernen, das Passwort in LoadOptions zu verschieben und den Re‑Encrypt‑Aufruf am Ende zu entfernen – typischerweise ein Netto‑Verlust an Zeilen. Der eigentliche Signatur‑Aufruf ändert sich nicht, und die Ausgabe ist byte‑für‑byte ein signiertes PDF mit demselben Schutz, den es beim Eingang hatte.

Der eine Ort, an dem man genau hinschauen muss, ist der Aufräum‑Code. Eine Pipeline, die um Decrypt‑Sign‑Reencrypt gebaut ist, hat normalerweise einen finally‑Block, der die temporäre Datei löscht, und sobald die temporäre Datei weg ist, löscht dieser Block einen Pfad, der nicht mehr existiert.

Bewährte Vorgehensweisen

  • Lassen Sie use_original_password unverändert, es sei denn, Sie rotieren bewusst; der Standard ist der sichere.
  • Parsen Sie den Proxy‑Namen einmal in einer Hilfsfunktion und verzweigen Sie überall sonst danach.
  • Validieren Sie ein vom Benutzer bereitgestelltes Passwort mit get_document_info, bevor Sie einen Stapel starten, sodass ein falsches Anmelde­datum nur einen günstigen Aufruf kostet statt einen halb‑fertigen Durchlauf.
  • Schreiben Sie die signierte Ausgabe niemals über den Quell‑Pfad, damit ein Fehler das Original wiederherstellbar lässt.

Fazit

Das Passwort ist kein Hindernis, das vor dem Signieren umgangen werden muss – es ist ein Argument der Operation. Öffnen Sie mit LoadOptions, entscheiden Sie den Ausgabeschutz mit SaveOptions, parsen Sie den Proxy‑Namen, wenn etwas fehlschlägt, und prüfen Sie anschließend mit dem Passwort. Das Beispiel führt alle vier Pfade in einem Durchlauf aus, sodass der Unterschied zwischen ihnen mit einem einzigen Befehl sichtbar wird und nicht erst nach einem langen Absatz vertraut werden muss.

Weitere Ressourcen