💡 Volledig werkend voorbeeld beschikbaar op GitHub:
qr-sign-password-protected-pdf-python
Introductie
Er is een drie‑stappen‑patroon dat de meeste teams gebruiken wanneer een te ondertekenen document versleuteld blijkt te zijn: het decrypten, de platte tekst ondertekenen, het resultaat opnieuw encrypten. Het werkt. Het betekent ook dat er gedurende enkele honderden milliseconden een leesbare kopie van een opzettelijk beschermd document bestaat in een tijdelijke map, en in een geauditde pijplijn is dat venster de bevinding in plaats van de handtekening.
Een beschermd PDF ondertekenen is een GroupDocs.Signature‑functionaliteit voor Python via .NET die die drie stappen volledig overslaat: het wachtwoord opent de bron op zijn plaats, de handtekening wordt toegepast, en de uitvoer wordt terugbeschermd weggeschreven. Dit artikel vergelijkt de vier wachtwoordpaden – twee die werken en twee die opzettelijk falen – en behandelt het foutcontract dat specifiek is voor deze binding.
Waarom dit belangrijk is
Wachtwoordafhandeling is waar document‑pijplijnen lekken. Niet via de ondertekeningsbibliotheek, meestal, maar via de omringende infrastructuur: het tijdelijke bestand dat verwijderd had moeten worden, de exceptie‑handler die een fout bij een verkeerd wachtwoord opslokte en eindeloos opnieuw probeerde, de ondertekende kopie die werd overhandigd met een wachtwoord waar de ontvanger nooit van op de hoogte was gesteld.
Alle drie hebben dezelfde oorzaak, namelijk dat het wachtwoord wordt behandeld als iets dat uit de weg moet worden geruimd in plaats van als onderdeel van de bewerking. LoadOptions en SaveOptions plaatsen het terug in de bewerking.
Voorvereisten
Python 3 en groupdocs-signature-net==26.1, plus een PDF met een gebruikers‑wachtwoord. Zonder licentie draait de bibliotheek in evaluatiemodus, die nog steeds ondertekent maar eigen tekst aan de pagina toevoegt.
Installatie
pip install groupdocs-signature-net==26.1
Methode 1 – Houd het oorspronkelijke wachtwoord
De standaard, en de methode die de minste code vereist. Het wachtwoord wordt via LoadOptions meegegeven, en er wordt helemaal geen SaveOptions doorgegeven:
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)
Het ontbreken van SaveOptions doet hier het echte werk. use_original_password staat standaard op True, zodat GroupDocs het bron‑wachtwoord opnieuw toepast op de ondertekende uitvoer. Er is geen moment waarop een onbeschermde versie bestaat, op schijf of anderszins, en len(result.succeeded) geeft aan hoeveel handtekeningen zijn weggeschreven.
Methode 2 – Versleutel de ondertekende kopie opnieuw
Wanneer het ondertekende document naar een andere partij gaat, is de logische stap om de kopie een eigen referentie te geven en de bron onaangeroerd te laten:
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 regels van SaveOptions zijn vereist, en dit is de nuance die je moet onthouden: het instellen van password terwijl use_original_password op de standaardwaarde blijft, heeft geen waarneembaar effect. De vlag wint, de uitvoer behoudt het oude wachtwoord, en je ontdekt het wanneer de ontvanger meldt dat het wachtwoord dat je hebt gestuurd niet werkt.
Methode 3 en 4 – De twee fouten
Een versleuteld document reageert anders op een ontbrekend wachtwoord dan op een verkeerd wachtwoord, en dat verschil is het waard om af te handelen.
Zonder LoadOptions faalt het openen en wordt er niets weggeschreven:
try:
with signature.Signature(source_path) as sign:
sign.sign(output_path, options)
return ""
except RuntimeError as error:
return proxy_error_name(error)
Dat levert PasswordRequiredException op. Geef in plaats daarvan een onjuist wachtwoord op en dezelfde code levert IncorrectPasswordException op. De eerste betekent: vraag de gebruiker om een referentie; de tweede betekent dat de referentie die je hebt verouderd is. Een handler die ze niet van elkaar kan onderscheiden, blijft een wachtwoord proberen dat nooit zal werken.
Het foutcontract, en waarom de voor de hand liggende code breekt
Hier is het deel dat een middag kost als niemand je waarschuwt. De binding exposeert PasswordRequiredException, IncorrectPasswordException en GroupDocsSignatureException als kale namen die niet van BaseException erven. Schrijf de intuïtieve handler:
except IncorrectPasswordException:
...
en Python geeft TypeError: catching classes that do not inherit from BaseException is not allowed. De oorspronkelijke fout is verdwenen, vervangen door één die wijst op je except‑regel in plaats van op het wachtwoord. Ik schreef precies die handler de eerste keer, en de twintig minuten die ik besteedde aan het lezen van de TypeError is de reden dat dit gedeelte bestaat.
Wat er daadwerkelijk aankomt, is een RuntimeError waarvan het bericht begint met Proxy error(<Name>): . Het ontleden van dat voorvoegsel herstelt de oorzaak:
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]
Branch op de geretourneerde naam in plaats van op de berichttekst, die bestands‑paden bevat en per uitvoering kan variëren.
Inspecteren vóór je ondertekent
Er is een vijfde pad dat het waard is om te kennen, en dat helemaal niets schrijft. Het document openen met LoadOptions en get_document_info aanroepen geeft het formaat, het aantal pagina’s en de grootte terug terwijl het bestand versleuteld op schijf blijft:
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
Twee toepassingen. Wanneer het wachtwoord uit een gebruikersformulier komt, valideert dit de referentie met een goedkope oproep in plaats van halverwege een batch van tweehonderd documenten. En wanneer een pijplijn niet is toegestaan om platte tekst op te slaan, laat het die pijplijn toch rapporteren wat hij vasthoudt – paginatellingen voor een auditlog, groottes voor een quotum – zonder iets te decrypten.
Vergelijking van de methoden: wanneer welke te gebruiken
| Methode | Beste voor | Belangrijkste voordelen | Beperkingen |
|---|---|---|---|
| Houd origineel wachtwoord | pijplijnen die in‑place ondertekenen | geen SaveOptions, niets wordt in het helder geschreven | ontvanger heeft het bron‑wachtwoord nodig |
| Versleutel bij opslaan | overdracht aan een andere partij | bron behoudt zijn referentie, kopie krijgt een nieuwe | twee SaveOptions‑regels, gemakkelijk om er per ongeluk één te vergeten |
| Geen wachtwoord (faalt) | contract testen | faalt bij openen, schrijft niets | geen ondertekeningspad |
| Verkeerd wachtwoord (faalt) | onderscheid maken van een verouderde referentie | aparte exceptienaam | geen ondertekeningspad |
Is het opnieuw uitlezen de extra oproep waard?
Ja, om twee redenen. Het opnieuw openen van het ondertekende bestand met QrCodeVerifyOptions bewijst dat de handtekening de opslaan‑stap heeft overleefd, en omdat het heropenen het wachtwoord moet leveren, bewijst het ook dat de uitvoer nog steeds versleuteld is. Een nul‑telling duidt bijna altijd op een licentieprobleem in plaats van een ondertekeningsfout – de sign‑call gooit een fout wanneer hij echt faalt, dus stilte plus nul duidt op een niet‑gelicentieerde build.
Wat het kost om over te stappen
Niets structureels. Als je code al decrypt naar een tijdelijk bestand, is de wijziging het verwijderen van die stap, het verplaatsen van het wachtwoord naar LoadOptions, en het weglaten van de her‑encryptie‑aanroep aan het einde – meestal een netto‑verlies aan regels. De ondertekeningsaanroep zelf verandert niet van vorm, en de uitvoer is byte‑voor‑byte een ondertekende PDF met dezelfde bescherming als bij binnenkomst.
De enige plek om goed op te letten is de opruimcode. Een pijplijn die is opgebouwd rond decrypt‑sign‑reencrypt heeft meestal een finally‑blok dat het tijdelijke bestand verwijdert, en zodra dat tijdelijke bestand weg is, probeert dat blok een pad te verwijderen dat niet meer bestaat.
Best practices
- Laat
use_original_passwordongemoeid tenzij je bewust rotert; de standaard is de veilige. - Parse de proxy‑naam één keer, in een helper, en branch daarop overal verder.
- Valideer een door de gebruiker opgegeven wachtwoord met
get_document_infovoordat je een batch start, zodat een slechte referentie één goedkope oproep kost in plaats van een half‑voltooide run. - Schrijf de ondertekende uitvoer nooit over het bronpad, zodat een fout de originele versie herstelbaar laat.
Conclusie
Het wachtwoord is geen obstakel dat je moet omzeilen vóór het ondertekenen – het is een argument van de bewerking. Open met LoadOptions, bepaal de uitvoerbescherming met SaveOptions, parse de proxy‑naam wanneer iets faalt, en verifieer daarna via het wachtwoord. Het voorbeeld doorloopt alle vier de paden in één keer, zodat het verschil tussen hen met één commando zichtbaar wordt in plaats van een alinea om te vertrouwen.