💡 Esempio completo funzionante disponibile su GitHub:
sign-docx-with-mldsa-certificates-python
Introduzione
Firma un contratto questo pomeriggio con RSA‑2048 e hai fatto una promessa che deve durare finché il contratto è rilevante. Se ciò significa venti o trenta anni – e per atti, moduli di consenso e approvazioni ingegneristiche lo è spesso – la promessa deve superare l’algoritmo. L’attacco non deve esistere oggi. Deve esistere prima che il documento smetta di interessare, e allora chiunque possieda la chiave pubblica può derivare quella privata e firmare a tuo nome.
La firma di documenti post‑quantistica è la funzionalità di GroupDocs.Signature per Python che sostituisce quella promessa con una basata su ML‑DSA, l’algoritmo di firma standardizzato da NIST come FIPS 204 nel 2024. Il supporto al formato Word è arrivato in GroupDocs.Signature 26.9, e riutilizza l’API che già conosci: una chiave ML‑DSA vive in un PFX e viene inserita in DigitalSignOptions esattamente come una chiave RSA.
Questa guida firma un DOCX in quattro passaggi, confronta i tre livelli di sicurezza sull’output misurato, verifica la firma usando solo un certificato pubblico e termina con i due limiti da conoscere prima di impegnarsi.
Perché questo è più importante della consueta migrazione
La migrazione delle firme è diversa dalla migrazione della crittografia in un aspetto che la rende più facile da rimandare e più scomoda da correggere.
Con la crittografia, il problema “raccogli‑ora‑decripta‑più‑tardi” è immediato: tutto ciò che viene intercettato oggi può essere archiviato e aperto in futuro. Con le firme, nulla di ciò che hai già firmato diventa falsificabile retroattivamente – ma nulla di ciò che hai firmato rimane provvisoriamente tuo, una volta che la chiave può essere derivata dal certificato di cui tutti hanno una copia. Rifirmare un decennio di documenti archiviati con nuove chiavi è possibile e nessuno vuole essere la persona che lo pianifica.
Ecco perché il consiglio pratico è ristretto anziché generico: migra i documenti la cui conservazione è lunga, lascia gli altri. Alcuni profili hanno già fissato il livello – CNSA 2.0 richiede ML‑DSA‑87 per i sistemi di sicurezza nazionale – e per tutti gli altri il fattore decisivo è quanto tempo il file deve rimanere difendibile.
Prerequisiti
- Python 3.9 o successivo su un interprete a 64 bit – il pacchetto include un runtime .NET integrato e non ha wheel a 32 bit
- GroupDocs.Signature per Python via .NET 26.10.0, con una licenza temporanea gratuita per rimuovere i limiti di valutazione
- Un certificato ML‑DSA in un PFX protetto da password e un documento Word da firmare
Installazione
pip install groupdocs-signature-net
Passo 1 – Firma con un certificato ML‑DSA
Il certificato fa il lavoro. La chiamata è quella che scriveresti per RSA:
with signature.Signature(source_path) as sign:
options = DigitalSignOptions(pfx_path)
options.password = CERTIFICATE_PASSWORD
result = sign.sign(output_path, options)
Questa è l’intera storia di adozione per il codice che già firma: punta DigitalSignOptions a un PFX diverso. Nessuna nuova opzione, nessun parametro algoritmo separato, nessun ramo per il post‑quantistico.
Leggere il firmatario indietro richiede un passaggio in più e contiene la trappola specifica di Python in questo esercizio:
for created in result.succeeded:
certificate = getattr(created, "certificate", None)
subject = getattr(certificate, "subject", None)
if subject:
return str(subject)
Il certificato su un DigitalSignature è un oggetto ponte che risolve gli attributi in modo dinamico. certificate.subject restituisce CN=GroupDocs.Signature MLDSA65 test, mentre dir() su quello stesso oggetto non elenca nulla. L’ho ispezionato prima con dir(), ho concluso che il soggetto non fosse esposto e mi sono sbagliato – quindi, se introspezioni prima di leggere, salti un valore che è presente.
Passo 2 – Confronta i tre livelli di sicurezza
ML‑DSA è disponibile in tre set di parametri, e vengono selezionati passando un certificato diverso:
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)
Questo è il passaggio che vale la pena eseguire, perché il compromesso è solitamente descritto e raramente misurato. Da un contratto sorgente di 132 KB:
| Livello | Categoria di sicurezza NIST | File firmato | Rispetto del più piccolo |
|---|---|---|---|
| ML-DSA-44 | 2 | 138 202 byte | - |
| ML-DSA-65 | 3 | 140 650 byte | +2 448 byte |
| ML-DSA-87 | 5 | 143 971 byte | +5 769 byte |
Meno di 6 KB separano il livello più debole da quello più forte. Su un contratto ciò è irrilevante, il che semplifica la decisione: usa ML‑DSA‑65 come predefinito, ML‑DSA‑87 dove un profilo richiede categoria 5 o dove la dimensione è irrilevante, e ML‑DSA‑44 solo quando firmi così tanti file che i kilobyte si aggregano in qualcosa di reale.
Passo 3 – Verifica con un certificato pubblico
Il destinatario ha bisogno solo del certificato pubblico del firmatario e di nulla di segreto:
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
Il campione chiama questo due volte sullo stesso file: una volta con mldsa65.cer, la metà pubblica della chiave di firma, e una volta con un PFX di un firmatario diverso. La prima restituisce True, la seconda False. Nota che il certificato errato restituisce False anziché sollevare un’eccezione – “firmato da qualcun altro” è una risposta che il tuo codice dovrebbe gestire, non un’eccezione. Il controllo copre il contenuto del documento insieme al numero di serie e all’impronta digitale del certificato, quindi un file modificato dopo la firma fallisce comunque.
Passo 4 – Leggi le firme da un documento
Quando arriva un documento firmato e non sai quale certificato aspettarti:
with signature.Signature(signed_path) as sign:
found = sign.search(SignatureType.DIGITAL)
for item in found:
print(item.sign_time, item.is_valid)
search con SignatureType.DIGITAL restituisce oggetti DigitalSignature che contengono il certificato, l’ora di firma e un flag di validità. Un documento Word può contenere più firme, inclusa una combinazione di RSA e ML‑DSA, e ciascuna viene riportata con il proprio certificato e la propria validità.
Questo cambia il modo in cui i destinatari verificano?
In nessun modo lo noteranno. Un destinatario ha ancora bisogno solo del certificato pubblico del firmatario, lo passa allo stesso DigitalVerifyOptions e riceve ancora un valore booleano. Nulla del percorso di verifica è specifico a ML‑DSA. L’unico punto in cui l’algoritmo si manifesta è l’indicatore di firma di Microsoft Word, che potrebbe non riconoscere ancora ML‑DSA perché il formato non ha un identificatore standard per esso.
Applicazioni nel mondo reale
Contratti a lungo termine
Il caso più chiaro. Un documento che deve rimanere verificabile per decenni viene firmato una sola volta, ora, con ML‑DSA‑65 o ML‑DSA‑87, e non necessita più di rifirmare perché l’algoritmo è ormai obsoleto.
Ambienti regolamentati con profilo nominato
Dove si applica CNSA 2.0 o un profilo simile, il livello non è una decisione soggettiva – ML‑DSA‑87 è il requisito, e l’unica domanda ingegneristica è se il formato è supportato.
Pipeline miste durante la migrazione
Firmare nuovi documenti post‑quantistici lasciando intatto l’archivio è uno stato intermedio perfettamente ragionevole, e la segnalazione di search di ogni firma separatamente è ciò che lo rende gestibile.
Buone pratiche e consigli
- Migra in base alla conservazione, non al volume. I documenti che ne hanno bisogno sono quelli a lunga vita; una ricevuta valida per 90 giorni non lo è.
- Imposta come predefinito ML‑DSA‑65 salvo che un profilo ne specifichi un altro, e non agonizzare sulla differenza di dimensione – è inferiore a 6 KB per firma.
- Mantieni RSA dove il destinatario verifica in Word. Le firme corrette che un lettore segnala sono peggiori di una migrazione più lenta.
- Sostituisci i certificati di test. I file PFX del campione sono autofirmati con una password pubblicata, quindi qualsiasi firma con essi non dimostra nulla.
- Verifica dopo la firma in qualsiasi pipeline, usando il certificato pubblico che il destinatario avrebbe.
Risoluzione dei problemi comuni
Microsoft Word non mostra la firma come valida. È previsto per ora: non esiste un identificatore XML‑DSig standard per ML‑DSA, quindi Word potrebbe non riconoscerla anche se la firma è corretta e GroupDocs.Signature la verifica. Verifica nella tua pipeline e mantieni RSA per i documenti i cui destinatari si affidano all’indicatore di Word.
La chiamata di firma rifiuta un PDF o un foglio di calcolo. La firma ML‑DSA copre i formati Word – DOCX, DOC, ODT e simili. PDF, fogli di calcolo e presentazioni non sono ancora supportati, e si continua a firmare con RSA o ECDSA come prima.
Il soggetto del certificato ritorna vuoto. Quasi sempre la trappola dir() del Passo 1: l’attributo si risolve dinamicamente, quindi leggilo invece di testarlo prima.
Conclusione
La modifica al codice è una modifica al certificato, che è la parte che rende questa operazione utile prima che diventi urgente. Firma i documenti Word a lunga vita con ML‑DSA‑65, usa ML‑DSA‑87 dove un profilo lo richiede, verifica con il certificato pubblico e mantieni RSA dove il formato o il lettore lo esigono.
Esegui il campione su uno dei tuoi contratti e le tre dimensioni ti diranno, in byte, esattamente quanto costa il livello più forte disponibile. Sul file che ho testato era 5 769 byte.