đź’ˇ Full working example available on GitHub: sign-word-with-ml-dsa-certificates-dotnet

The Old Way Was a Project Plan

Ask what it takes to make document signing post-quantum and you get a roadmap: evaluate algorithms, pick a library, write an abstraction layer over the signing code, plan a dual-signing period, budget a quarter.

Most of that is still true for the organisational half - certificate procurement, policy, validator support. The code half turned out to be smaller than the roadmap suggests, and that is worth knowing before anyone budgets a quarter for it.

ML-DSA signing is a GroupDocs.Signature capability for .NET that signs Word documents with certificates based on FIPS 204, the NIST post-quantum signature standard. It arrived in 26.9, and from the calling code’s point of view it is a different PFX file.

There Is a Better Way

Here is the whole of the code change:

using var signature = new Signature(sourcePath);

var options = new DigitalSignOptions(pfxPath)
{
    Password = certificatePassword
};

SignResult result = signature.Sign(outputPath, options);

That is the same call used for an RSA certificate. The algorithm is a property of the certificate, so no option selects it, no abstraction layer is needed, and no second code path appears for the transition period. Point DigitalSignOptions at an ML-DSA PFX and the output is an ML-DSA signature.

Reading the certificate back out of the result is worth doing while several certificates are in play:

var created = result.Succeeded.OfType<DigitalSignature>().FirstOrDefault();
return created?.Certificate?.Subject ?? "(no certificate returned)";

Picking a Level, With Numbers Instead of Opinions

ML-DSA comes in three parameter sets, mapping to NIST security categories 2, 3 and 5. Stronger means larger - both the key and the signature - and the sensible way to decide is to sign your own document three times and look:

var levels = new Dictionary<string, string>
{
    ["ML-DSA-44"] = MlDsa44Pfx,
    ["ML-DSA-65"] = MlDsa65Pfx,
    ["ML-DSA-87"] = MlDsa87Pfx
};

The sample writes one signed copy per level and records each size, so the trade-off is a measurement rather than a table from a specification. For a single contract the difference is unremarkable; for an archive of several million signed documents it is a capacity question worth asking before standardising on the highest level.

ML-DSA-65 is the reasonable default when no policy prescribes one. Profiles like CNSA 2.0 name ML-DSA-87 explicitly, and ML-DSA-44 makes sense only when size matters more than margin.

Verification Needs Only the Public Certificate

The distribution story is unchanged from RSA, which is the second piece of good news:

var options = new DigitalVerifyOptions(certificatePath);
if (password != null)
{
    options.Password = password;
}

VerificationResult result = signature.Verify(options);

A recipient needs the signer’s .cer and nothing else. The result is valid only when the signature matches the content and the certificate matches by serial number and thumbprint, so a document signed by a different party fails the check - which the sample proves by running the verification twice, once with the right certificate and once with somebody else’s.

Side-by-Side: Expected vs. Actual

What a migration plan assumes What 26.9 actually requires
Code change abstraction layer over signing a different PFX path
API surface new post-quantum methods DigitalSignOptions, unchanged
Level selection library configuration which certificate you load
Verification new tooling for recipients the signer’s public .cer
Platform work per-OS key handling none - the library falls back internally
Format coverage all formats Word formats only, for now

The last row is the one that constrains planning, and it leads to the honest part of this article.

What Does Not Work Yet

Two limits, both worth knowing before you promise anything.

Format coverage is Word only in 26.9 - DOCX, DOC, ODT and the rest of the Word family. PDF, spreadsheets and presentations cannot be signed with ML-DSA. For a PDF-first pipeline this release is for prototyping and measurement rather than migration.

Validator support is the other. There is no standard XML-DSig identifier for ML-DSA yet, so Microsoft Word may not report the signature as valid even though it is cryptographically sound and verifies correctly through the API. That is a standards gap rather than a defect, and it means verification belongs in your code rather than in a reviewer opening the file and looking at the banner.

There is also a platform detail that needs no action: .NET cannot read ML-DSA keys everywhere, including Linux on .NET 8. Where it cannot, GroupDocs.Signature reads the certificate through the Word engine instead, so the same build runs on a developer laptop and a Linux container without conditional code.

Is it worth doing now, given those limits?

Yes, for two reasons that have nothing to do with the code. Certificate procurement is slow - public CAs are still rolling out ML-DSA issuance - so the organisation-side work benefits from starting early. And “can we produce a post-quantum signature today” is a question compliance teams are beginning to ask; being able to answer with a signed document rather than a plan is worth the afternoon it takes.

What the Sample Actually Proves

Four methods, run in order, with the exit code tied to the outcome. It signs the contract with ML-DSA-65 and prints the subject of the certificate that was used. It signs the same contract at all three levels and prints the resulting sizes. It verifies the signed file twice - once with the signer’s public certificate, expecting success, and once with a different signer’s certificate, expecting failure. Then it lists the digital signatures found on the output.

The second verification is the one worth copying. A routine that has only ever been shown valid input tells you nothing about whether it would reject an invalid one, and for signatures that is the entire question.

Real-World Example: The Thirty-Year Contract

Long-retention archives are where this stops being theoretical. A contract signed today and kept for thirty years has to remain verifiable across whatever happens to cryptography in that window, and “harvest now, decrypt later” is a documented threat model for exactly that kind of material.

For an archive like that, the practical move today is dual-track: keep RSA for the formats ML-DSA does not cover yet, start signing Word output with ML-DSA-65 or 87, and record which algorithm was used per document so a future audit can tell them apart without opening files.

One Thing to Fix in the Sample Before Copying It

The repository ships self-signed ML-DSA certificates so the demonstration runs out of the box, which means four PFX files and a hard-coded password sit in documents/. For a throwaway test certificate valid only inside that sample, that is fine.

It is not a pattern to carry into your own repository. Generate test certificates at run time instead, the way GroupDocs' certificate-validity sample does, or keep them out of version control entirely. A committed key is awkward to revoke and tends to outlive the demo it was written for.

Conclusion

The expensive parts of post-quantum migration are certificates, policy and validators. The code, at least for Word documents in .NET, is a different PFX and the same DigitalSignOptions call. Clone the sample, point it at one of your own contracts, and you will have three signed files, two verification results and a size comparison in a few minutes - which is a better basis for a migration plan than an estimate.

Additional Resources