💡 Exemple complet fonctionnel disponible sur GitHub :
qr-sign-password-protected-pdf-python
Introduction
Il existe un schéma en trois étapes que la plupart des équipes utilisent lorsqu’un document à signer se révèle chiffré : le déchiffrer, signer le texte en clair, puis le rechiffrer. Cela fonctionne. Cela signifie aussi que, pendant quelques centaines de millisecondes, une copie lisible d’un document délibérément protégé existe dans un répertoire temporaire, et dans un pipeline audité, cette fenêtre représente la fuite plutôt que la signature.
Signer un PDF protégé est une fonctionnalité de GroupDocs.Signature pour Python via .NET qui supprime complètement ces trois étapes : le mot de passe ouvre la source en place, la signature est appliquée, et la sortie est ré‑écrite protégée. Cet article compare les quatre voies de mot de passe — deux qui fonctionnent et deux qui échouent volontairement — et décrit le contrat d’échec propre à cette liaison.
Pourquoi cela importe
La gestion des mots de passe est le point où les pipelines de documents fuient. Pas généralement à travers la bibliothèque de signature, mais à travers l’infrastructure qui l’entoure : le fichier temporaire qui devait être supprimé, le gestionnaire d’exceptions qui a avalé une erreur de mot de passe incorrect et a ré‑essayé indéfiniment, la copie signée remise avec un mot de passe dont le destinataire n’a jamais été informé.
Les trois cas ont la même cause première : le mot de passe est traité comme un obstacle à éliminer plutôt que comme une partie de l’opération. LoadOptions et SaveOptions le réintègrent dans l’opération.
Prérequis
Python 3 et groupdocs-signature-net==26.1, ainsi qu’un PDF protégé par un mot de passe utilisateur. Sans licence, la bibliothèque fonctionne en mode d’évaluation, qui signe tout de même mais ajoute son propre texte à la page.
Installation
pip install groupdocs-signature-net==26.1
Méthode 1 - Conserver le mot de passe d’origine
Le comportement par défaut, et celui qui nécessite le moins de code. Le mot de passe est fourni via LoadOptions, et aucune SaveOptions n’est passée :
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)
L’absence de SaveOptions fait tout le travail réel ici. use_original_password vaut True par défaut, donc GroupDocs ré‑applique le mot de passe source à la sortie signée. Aucun moment n’existe où une version non protégée apparaît, sur le disque ou ailleurs, et len(result.succeeded) indique combien de signatures ont été écrites.
Méthode 2 - Re‑chiffrer la copie signée
Lorsque le document signé est destiné à une autre partie, la démarche sensée consiste à donner à la copie ses propres identifiants et à laisser la source intacte :
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)
Les deux lignes de SaveOptions sont obligatoires, et c’est le détail qu’il faut retenir : définir password tout en laissant use_original_password à sa valeur par défaut ne produit aucun effet observable. Le drapeau l’emporte, la sortie conserve l’ancien mot de passe, et vous le découvrirez lorsque le destinataire signalera que le mot de passe que vous avez envoyé ne fonctionne pas.
Méthode 3 et 4 - Les deux échecs
Un document chiffré réagit différemment à l’absence de mot de passe et à un mot de passe erroné, et cette différence mérite d’être gérée.
Sans aucune LoadOptions, l’ouverture échoue et rien n’est écrit :
try:
with signature.Signature(source_path) as sign:
sign.sign(output_path, options)
return ""
except RuntimeError as error:
return proxy_error_name(error)
Cela renvoie PasswordRequiredException. Fournir un mot de passe incorrect à la place renvoie IncorrectPasswordException. Le premier indique qu’il faut demander à l’utilisateur un identifiant ; le second signifie que l’identifiant dont vous disposez est périmé. Un gestionnaire qui ne peut pas les différencier finit par ré‑essayer un mot de passe qui ne fonctionnera jamais.
Le contrat d’échec, et pourquoi le code évident échoue
Voici la partie qui coûte un après‑midi si personne ne vous prévient. La liaison expose PasswordRequiredException, IncorrectPasswordException et GroupDocsSignatureException comme des noms nus qui n’héritent pas de BaseException. Écrivez le gestionnaire intuitif :
except IncorrectPasswordException:
...
et Python lève TypeError: catching classes that do not inherit from BaseException is not allowed. L’erreur d’origine disparaît, remplacée par une qui pointe sur votre ligne except plutôt que sur le mot de passe. J’ai écrit exactement ce gestionnaire la première fois, et les vingt minutes passées à lire le TypeError sont la raison pour laquelle cette section existe.
Ce qui arrive réellement est un RuntimeError dont le message commence par Proxy error(<Name>): . Analyser ce préfixe permet de récupérer la cause :
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]
Bifurquez sur le nom retourné plutôt que sur le texte du message, qui contient des chemins de fichiers et varie d’une exécution à l’autre.
Inspection avant de signer
Il existe une cinquième voie utile, qui n’écrit rien du tout. Ouvrir le document avec LoadOptions et appeler get_document_info renvoie le format, le nombre de pages et la taille tandis que le fichier reste chiffré sur le disque :
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
Deux utilisations possibles. Lorsque le mot de passe provient d’un formulaire utilisateur, cela valide les identifiants avec un appel léger plutôt qu’au milieu d’un lot de deux cents documents. Et lorsqu’un pipeline n’est pas autorisé à stocker le texte en clair, cela permet tout de même au pipeline de rendre compte de ce qu’il détient — nombre de pages pour un journal d’audit, tailles pour un quota — sans déchiffrer quoi que ce soit.
Comparaison des méthodes : quand les utiliser
| Méthode | Idéal pour | Principaux avantages | Limitations |
|---|---|---|---|
| Conserver le mot de passe d’origine | pipelines qui signent en place | aucune SaveOptions, rien n’est écrit en clair |
le destinataire a besoin du mot de passe source |
| Re‑chiffrer à l’enregistrement | remise à une autre partie | la source garde ses identifiants, la copie en reçoit de nouveaux | deux lignes de SaveOptions, facile d’en oublier une |
| Pas de mot de passe (échoue) | prouver le contrat dans les tests | échoue à l’ouverture, n’écrit rien | pas une voie de signature |
| Mauvais mot de passe (échoue) | distinguer un identifiant périmé | nom d’exception distinct | pas une voie de signature |
Le re‑lecture vaut‑elle l’appel supplémentaire ?
Oui, pour deux raisons. Ré‑ouvrir le fichier signé avec QrCodeVerifyOptions prouve que la signature a survécu à l’enregistrement, et comme la réouverture doit fournir le mot de passe, cela prouve également que la sortie reste bien chiffrée. Un compte zéro indique presque toujours un problème de licence plutôt qu’un échec de signature — l’appel sign lève une exception lorsqu’il échoue réellement, donc silence + zéro pointe vers une version non licenciée.
Ce que cela coûte de changer
Rien de structurel. Si votre code déchiffre déjà vers un fichier temporaire, le changement consiste à supprimer cette étape, à déplacer le mot de passe dans LoadOptions, et à enlever l’appel de re‑chiffrement à la fin — généralement une perte nette de lignes. L’appel de signature lui‑même ne change pas de forme, et la sortie est byte‑for‑byte un PDF signé avec la même protection qu’à l’entrée.
Le seul endroit où il faut être vigilant est le code de nettoyage. Un pipeline construit autour de déchiffrer‑signer‑re‑chiffrer possède habituellement un bloc finally qui supprime le fichier temporaire, et une fois ce fichier supprimé ce bloc tente de supprimer un chemin qui n’existe plus.
Bonnes pratiques
- Laissez
use_original_passwordtel quel sauf si vous effectuez délibérément une rotation ; la valeur par défaut est la plus sûre. - Analysez le nom du proxy une fois, dans une fonction utilitaire, et bifurquez dessus partout ailleurs.
- Validez un mot de passe fourni par l’utilisateur avec
get_document_infoavant de lancer un lot, ainsi un mauvais identifiant ne coûte qu’un appel léger au lieu d’une exécution à moitié terminée. - N’écrivez jamais la sortie signée sur le même chemin que la source, afin qu’une erreur laisse l’original récupérable.
Conclusion
Le mot de passe n’est pas un obstacle à contourner avant la signature — c’est un argument de l’opération. Ouvrez avec LoadOptions, décidez de la protection de la sortie avec SaveOptions, analysez le nom du proxy lorsqu’une erreur survient, et vérifiez ensuite avec le mot de passe. L’exemple exécute les quatre voies en une fois, de sorte que la différence entre elles se voit avec une simple commande plutôt qu’avec un paragraphe à deviner.