💡 Exemplo completo em funcionamento disponível no GitHub:
qr-sign-password-protected-pdf-python
Introdução
Existe um padrão de três etapas que a maioria das equipes utiliza quando um documento que precisa ser assinado está criptografado: descriptografá‑lo, assinar o texto puro e re‑criptografar o resultado. Funciona. Também significa que, por alguns centenas de milissegundos, uma cópia legível de um documento deliberadamente protegido existe em um diretório temporário e, em um pipeline auditado, essa janela é a vulnerabilidade, não a assinatura.
Assinar um PDF protegido é uma capacidade do GroupDocs.Signature para Python via .NET que elimina essas três etapas completamente: a senha abre a fonte no local, a assinatura é aplicada e a saída é gravada novamente protegida. Este artigo compara os quatro caminhos de senha – dois que funcionam e dois que falham intencionalmente – e aborda o contrato de falha que é específico desta binding.
Por que isso importa
O manuseio de senhas é onde os pipelines de documentos vazam. Não através da biblioteca de assinatura, geralmente, mas através da estrutura ao seu redor: o arquivo temporário que deveria ser excluído, o manipulador de exceções que engoliu um erro de senha incorreta e tentou indefinidamente, a cópia assinada entregue com uma senha que o destinatário nunca foi informado.
Todos os três têm a mesma causa raiz, que é tratar a senha como algo a ser removido do caminho, e não como parte da operação. LoadOptions e SaveOptions a devolvem à operação.
Pré‑requisitos
Python 3 e groupdocs-signature-net==26.1, além de um PDF com senha de usuário. Sem licença, a biblioteca roda em modo de avaliação, que ainda assina, mas adiciona seu próprio texto à página.
Instalação
pip install groupdocs-signature-net==26.1
Método 1 – Manter a senha original
O padrão, e o que requer menos código. A senha é passada por LoadOptions e nenhum SaveOptions é fornecido:
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)
A ausência de SaveOptions faz o trabalho real aqui. use_original_password tem o valor padrão True, portanto o GroupDocs reaplica a senha da fonte ao PDF assinado. Não há momento em que exista uma versão desprotegida, em disco ou de outra forma, e len(result.succeeded) informa quantas assinaturas foram gravadas.
Método 2 – Re‑chavear a cópia assinada
Quando o documento assinado será entregue a outra parte, a medida sensata é dar à cópia sua própria credencial e deixar a fonte intacta:
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)
Ambas as linhas de SaveOptions são necessárias, e este é o detalhe que vale a pena lembrar: definir password enquanto use_original_password permanece no padrão não produz nenhum efeito observável. A flag prevalece, a saída mantém a senha antiga e você a descobre quando o destinatário relata que a senha enviada não funciona.
Métodos 3 e 4 – As duas falhas
Um documento criptografado responde de forma diferente a uma senha ausente e a uma senha errada, e a diferença vale ser tratada.
Sem LoadOptions, a abertura falha e nada é gravado:
try:
with signature.Signature(source_path) as sign:
sign.sign(output_path, options)
return ""
except RuntimeError as error:
return proxy_error_name(error)
Isso devolve PasswordRequiredException. Forneça uma senha incorreta e o mesmo código devolve IncorrectPasswordException. Uma indica que se deve solicitar ao usuário a credencial; a outra indica que a credencial que você tem está desatualizada. Um manipulador que não consegue diferenciá‑las acaba tentando uma senha que nunca funcionará.
O contrato de falha e por que o código óbvio quebra
Esta é a parte que custa uma tarde se ninguém lhe avisar. A binding expõe PasswordRequiredException, IncorrectPasswordException e GroupDocsSignatureException como nomes “nus” que não herdam de BaseException. Escreva o manipulador intuitivo:
except IncorrectPasswordException:
...
e o Python lança TypeError: catching classes that do not inherit from BaseException is not allowed. O erro original desaparece, substituído por um que aponta para a sua linha except em vez de para a senha. Eu escrevi exatamente esse manipulador na primeira vez, e os vinte minutos que passei lendo o TypeError são a razão desta seção existir.
O que realmente chega é um RuntimeError cuja mensagem começa com Proxy error(<Name>): . Analisar esse prefixo recupera a causa:
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]
Faça a ramificação com base no nome retornado, e não no texto da mensagem, que contém caminhos de arquivos e varia entre execuções.
Inspecionando antes de assinar
Existe um quinto caminho que vale a pena conhecer, e ele não grava nada. Abrir o documento com LoadOptions e chamar get_document_info devolve o formato, número de páginas e tamanho enquanto o arquivo permanece criptografado em disco:
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
Dois usos para isso. Quando a senha vem de um formulário de usuário, isso valida a credencial em uma chamada barata ao invés de no meio de um lote de duzentos documentos. E quando um pipeline não tem permissão para armazenar texto puro, ainda permite que o pipeline relate o que está segurando – contagem de páginas para um log de auditoria, tamanhos para uma cota – sem descriptografar nada.
Comparando os Métodos: Quando usar cada um
| Método | Melhor para | Principais vantagens | Limitações |
|---|---|---|---|
| Manter senha original | pipelines que assinam no local | sem SaveOptions, nada escrito em texto claro |
o destinatário precisa da senha da fonte |
| Re‑chavear ao salvar | entrega a outra parte | a fonte mantém sua credencial, a cópia recebe nova | duas linhas de SaveOptions, fácil de definir apenas uma |
| Sem senha (falha) | provar o contrato em testes | falha na abertura, nada é gravado | não é um caminho de assinatura |
| Senha errada (falha) | distinguir credencial obsoleta | nome de exceção distinto | não é um caminho de assinatura |
Vale a pena ler de volta com a chamada extra?
Sim, por duas razões. Reabrir o arquivo assinado com QrCodeVerifyOptions comprova que a assinatura sobreviveu ao salvamento e, como a reabertura precisa fornecer a senha, também comprova que a saída continua criptografada. Uma contagem zero quase sempre indica um problema de licença e não uma falha de assinatura – a chamada sign lança exceção quando realmente falha, portanto silêncio mais zero aponta para uma build não licenciada.
O que custa mudar
Nada estrutural. Se seu código já descriptografa para um arquivo temporário, a mudança consiste em excluir essa etapa, mover a senha para LoadOptions e remover a chamada de re‑criptografia ao final – tipicamente uma perda líquida de linhas. A chamada de assinatura em si não muda de forma, e a saída é byte‑a‑byte um PDF assinado com a mesma proteção que tinha ao entrar.
O único ponto que requer atenção cuidadosa é o código de limpeza. Um pipeline construído em torno de descriptografar‑assinar‑recriptografar geralmente tem um bloco finally que exclui o arquivo temporário, e, uma vez que o arquivo temporário desaparece, esse bloco tenta excluir um caminho que já não existe.
Boas práticas
- Deixe
use_original_passwordcomo está, a menos que você esteja rotacionando deliberadamente; o padrão é o mais seguro. - Analise o nome do proxy uma única vez, em um helper, e faça a ramificação com base nele em todo o resto.
- Valide uma senha fornecida pelo usuário com
get_document_infoantes de iniciar um lote, assim uma credencial ruim custa uma chamada barata em vez de uma execução pela metade. - Nunca grave a saída assinada sobre o caminho da fonte, para que um erro deixe o original recuperável.
Conclusão
A senha não é um obstáculo a contornar antes de assinar – ela é um argumento da operação. Abra com LoadOptions, decida a proteção da saída com SaveOptions, analise o nome do proxy quando algo falhar e verifique através da senha depois. O exemplo executa os quatro caminhos de uma só vez, de modo que a diferença entre eles pode ser vista com um único comando, em vez de um parágrafo para confiar.