💡 Exemplo completo em funcionamento disponível no GitHub:
sign-docx-with-mldsa-certificates-python
Introdução
Assine um contrato esta tarde com RSA-2048 e você fez uma promessa que deve permanecer válida enquanto o contrato for relevante. Se isso for por vinte ou trinta anos – e para escrituras, formulários de consentimento e aprovações de engenharia costuma ser – a promessa precisa superar o algoritmo. O ataque não precisa existir hoje. Ele precisa existir antes que o documento deixe de ser relevante, e então qualquer pessoa que possua a chave pública pode derivar a chave privada e assinar em seu nome.
A assinatura de documentos pós‑quântica é o recurso do GroupDocs.Signature para Python que substitui essa promessa por uma baseada em ML‑DSA, o algoritmo de assinatura padronizado pela NIST como FIPS 204 em 2024. O suporte ao formato Word chegou no GroupDocs.Signature 26.9, e reutiliza a API que você já tem: uma chave ML‑DSA vive em um PFX e vai para DigitalSignOptions exatamente como uma chave RSA.
Este guia assina um DOCX em quatro etapas, compara os três níveis de segurança na saída medida, verifica a assinatura apenas com um certificado público e termina com os dois limites que vale a pena conhecer antes de se comprometer.
Por que isso importa mais do que a migração usual
A migração de assinaturas difere da migração de criptografia em um aspecto que a torna mais fácil de adiar e mais incômoda de corrigir.
Com criptografia, o problema de “colher‑agora‑decriptar‑depois” é imediato: tudo que for interceptado hoje pode ser armazenado e aberto depois. Com assinaturas, nada que você já assinou se torna falsificável retroativamente – mas nada que você assinou permanece provadamente seu também, uma vez que a chave pode ser derivada do certificado que todos têm uma cópia. Reassinar uma década de documentos arquivados com novas chaves é possível e ninguém quer ser a pessoa que planeja isso.
Por isso o conselho prático é estreito em vez de abrangente: migre os documentos cuja retenção é longa, deixe o resto. Alguns perfis já estabeleceram o padrão – CNSA 2.0 exige ML‑DSA‑87 para sistemas de segurança nacional – e para todos os demais o fator decisivo é quanto tempo o arquivo precisa permanecer defensável.
Pré‑requisitos
- Python 3.9 ou superior em um interpretador de 64 bits – o pacote inclui um runtime .NET empacotado e não possui roda de 32 bits
- GroupDocs.Signature para Python via .NET 26.10.0, com uma licença temporária gratuita para remover os limites de avaliação
- Um certificado ML‑DSA como PFX protegido por senha, e um documento Word para assinar
Instalação
pip install groupdocs-signature-net
Etapa 1 – Assinar com um certificado ML‑DSA
O certificado faz o trabalho. A chamada é a mesma que você escreveria para RSA:
with signature.Signature(source_path) as sign:
options = DigitalSignOptions(pfx_path)
options.password = CERTIFICATE_PASSWORD
result = sign.sign(output_path, options)
Essa é toda a história de adoção para código que já assina: aponte DigitalSignOptions para um PFX diferente. Nenhuma nova opção, nenhum parâmetro de algoritmo separado, nenhum ramo para pós‑quântico.
Ler o assinante de volta requer mais um passo, e contém a única armadilha específica do Python neste exercício:
for created in result.succeeded:
certificate = getattr(created, "certificate", None)
subject = getattr(certificate, "subject", None)
if subject:
return str(subject)
O certificado em um DigitalSignature é um objeto ponte que resolve atributos dinamicamente. certificate.subject devolve CN=GroupDocs.Signature MLDSA65 test, enquanto dir() naquele mesmo objeto não lista nada. Eu o inspecionei com dir() primeiro, concluí que o assunto não era exposto e estava simplesmente errado – então, se você introspectar antes de ler, vai pular um valor que está lá.
Etapa 2 – Comparar os três níveis de segurança
ML‑DSA vem em três conjuntos de parâmetros, e eles são selecionados ao fornecer um certificado diferente:
levels = (
("ML-DSA-44", MLDSA44_PFX),
("ML-DSA-65", MLDSA65_PFX),
("ML-DSA-87", MLDSA87_PFX),
)
for level, pfx_path in levels:
with signature.Signature(source_path) as sign:
options = DigitalSignOptions(pfx_path)
options.password = CERTIFICATE_PASSWORD
sign.sign(output_path, options)
sizes[f"{level} -> {file_name}"] = os.path.getsize(output_path)
Esta é a etapa que vale a pena realmente executar, porque a troca costuma ser descrita e raramente medida. A partir de um contrato fonte de 132 KB:
| Nível | Categoria de segurança NIST | Arquivo assinado | Sobre o menor |
|---|---|---|---|
| ML-DSA-44 | 2 | 138.202 bytes | - |
| ML-DSA-65 | 3 | 140.650 bytes | +2.448 bytes |
| ML-DSA-87 | 5 | 143.971 bytes | +5.769 bytes |
Menos de 6 KB separam o nível mais fraco do mais forte. Em um contrato isso é insignificante, o que simplifica a decisão: use ML‑DSA‑65 como padrão, ML‑DSA‑87 onde um perfil exige categoria 5 ou onde o tamanho é irrelevante, e ML‑DSA‑44 apenas quando você está assinando tantos arquivos que kilobytes se acumulam em algo real.
Etapa 3 – Verificar com um certificado público
Um destinatário precisa apenas do certificado público do assinante e nada secreto:
with signature.Signature(signed_path) as sign:
options = DigitalVerifyOptions(certificate_path)
if password is not None:
options.password = password
return sign.verify(options).is_valid
O exemplo chama isso duas vezes no mesmo arquivo: uma vez com mldsa65.cer, a metade pública da chave de assinatura, e outra vez com um PFX de outro assinante. A primeira retorna True, a segunda False. Observe que o certificado errado devolve False em vez de lançar exceção – “assinado por outra pessoa” é uma resposta que seu código deve tratar, não uma exceção. A verificação cobre o conteúdo do documento junto com o número de série e a impressão digital do certificado, de modo que um arquivo editado após a assinatura também falha.
Etapa 4 – Ler as assinaturas de um documento
Quando um documento assinado chega e você não sabe qual certificado esperar:
with signature.Signature(signed_path) as sign:
found = sign.search(SignatureType.DIGITAL)
for item in found:
print(item.sign_time, item.is_valid)
search com SignatureType.DIGITAL devolve objetos DigitalSignature que carregam o certificado, o horário da assinatura e uma bandeira de validade. Um documento Word pode conter várias assinaturas, incluindo uma mistura de RSA e ML‑DSA, e cada uma é relatada com seu próprio certificado e sua própria validade.
Isso muda a forma como os destinatários verificam?
De modo nenhum que eles percebam. Um destinatário ainda precisa apenas do certificado público do assinante, ainda o passa para o mesmo DigitalVerifyOptions e ainda recebe um booleano de volta. Nada no caminho de verificação é específico ao ML‑DSA. O único ponto onde o algoritmo aparece é o indicador de assinatura próprio do Microsoft Word, que pode ainda não reconhecer ML‑DSA porque o formato não tem identificador padrão para ele.
Aplicações no mundo real
Contratos de retenção longa
O caso mais claro. Um documento que deve permanecer verificável por décadas é assinado uma única vez, agora, com ML‑DSA‑65 ou ML‑DSA‑87, e nunca precisa ser re‑assinado porque seu algoritmo já expirou.
Ambientes regulados com perfil nomeado
Onde CNSA 2.0 ou perfil similar se aplica, o nível não é uma decisão – ML‑DSA‑87 é a exigência, e a única questão técnica é se o formato é suportado.
Pipelines mistos durante a migração
Assinar novos documentos pós‑quânticos enquanto o arquivo permanece intacto é um estado intermediário perfeitamente razoável, e o relatório de search de cada assinatura separadamente é o que o torna gerenciável.
Boas práticas e dicas
- Migrar por retenção, não por volume. Os documentos que precisam disso são os de longa vida; um recibo que vale 90 dias não.
- Usar ML‑DSA‑65 como padrão a menos que um perfil nomeie outro nível, e não se preocupe com a diferença de tamanho – é menos de 6 KB por assinatura.
- Manter RSA onde o destinatário valida no Word. Assinaturas corretas que um leitor sinaliza são piores que uma migração mais lenta.
- Substituir os certificados de teste. Os arquivos PFX do exemplo são autoassinados com senha publicada, portanto qualquer coisa assinada com eles não comprova nada.
- Verificar após assinar em qualquer pipeline, usando o certificado público que o destinatário teria.
Solução de problemas comuns
Microsoft Word não mostra a assinatura como válida. Esperado por enquanto: não há identificador XML‑DSig padrão para ML‑DSA, então o Word pode não reconhecê‑la mesmo que a assinatura esteja correta e o GroupDocs.Signature a verifique. Verifique em seu próprio pipeline e mantenha RSA para documentos cujos destinatários dependem do indicador do Word.
A chamada de assinatura rejeita um PDF ou planilha. A assinatura ML‑DSA cobre formatos Word – DOCX, DOC, ODT e afins. PDF, planilhas e apresentações ainda não são suportados, e continuam sendo assinados com RSA ou ECDSA como antes.
O assunto do certificado volta vazio. Quase sempre a armadilha do dir() da Etapa 1: o atributo é resolvido dinamicamente, então leia‑o em vez de testá‑lo antes.
Conclusão
A mudança de código é uma mudança de certificado, que é a parte que torna isso valer a pena antes de ser urgente. Assine os documentos Word de longa vida com ML‑DSA‑65, use ML‑DSA‑87 onde um perfil exigir, verifique com o certificado público e mantenha RSA onde o formato ou o leitor exigir.
Execute o exemplo contra um dos seus próprios contratos e os três tamanhos lhe dirão, em bytes, exatamente o que o nível mais forte disponível custa. No arquivo que testei, foram 5.769 bytes.