Sign and verify data
You sign byte sequences, such as invoice data or messages in your own protocol, and verify the signature on the receiving side, either as a composite ML-DSA signature that combines RSA or ECDSA with ML-DSA, or as a pure ML-DSA signature.
Solution overview
| Element | Purpose |
|---|---|
RSA.SignData, ECDsa.SignData with CryptoMode.Hybrid | composite ML-DSA signature |
RSA.VerifyData, ECDsa.VerifyData with CryptoMode.Hybrid | verifies both parts of the composite signature |
SignatureExtensions.SetTransientPqKey | sets the ML-DSA key pair for the current thread |
SignatureExtensions.SetTransientPqPublicKey | sets the ML-DSA public key for verification |
SignatureExtensions.ClearTransientPqKey | removes the thread's keys and overwrites the private key |
MLDsaKey.SignData, MLDsaKey.VerifyData | pure ML-DSA signature |
Implementation
A composite signature with RSA-3072 and ML-DSA-65:
byte[] data = "Invoice 2026-0042"u8.ToArray(); using var rsa = RSA.Create(3072); using var pqKey = MLDsaKey.Generate(MLDsaParameterSet.MLDsa65); SignatureExtensions.SetTransientPqKey(pqKey.ExportSubjectPublicKeyInfo(), pqKey.ExportPkcs8PrivateKey()); byte[] signature; try { signature = rsa.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1, CryptoMode.Hybrid); } finally { SignatureExtensions.ClearTransientPqKey(); }
Verification on the receiving side, which knows the RSA public key and the ML-DSA public key:
SignatureExtensions.SetTransientPqPublicKey(pqKey.ExportSubjectPublicKeyInfo()); try { bool valid = rsa.VerifyData( data, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1, CryptoMode.Hybrid); Console.WriteLine(valid); } finally { SignatureExtensions.ClearTransientPqKey(); }
A pure ML-DSA signature:
byte[] pqSignature = pqKey.SignData(data); Console.WriteLine(pqKey.VerifyData(data, pqSignature));
Remarks
- Only the pairs defined in the draft. Composite ML-DSA uses fixed combinations of parameter set, classical key, and hash algorithm, such as ML-DSA-65 with RSA-3072 or RSA-4096, and ML-DSA-65 with ECDSA P-256 or P-384. The library rejects any other pair, such as ML-DSA-65 with RSA-2048, with a
CryptographicExceptionthat names the pair. For the full list, see Formats of the post-quantum parts. - One key per thread. The keys from
SetTransientPqKeyapply only to the thread that sets them. In asynchronous code, set them right before the call, with noawaitin between, and remove them on the same thread. If the key is missing,SignDatathrows aCryptographicException. ClearTransientPqKeyoverwrites your array. The library doesn't copy the private key.ClearTransientPqKeyfills the array you passed with zeros. So pass a copy that you won't need afterward, such as the result ofExportPkcs8PrivateKeyin the example.- .NET doesn't verify the composite signature.
rsa.VerifyDatawithoutCryptoModereturnsfalsefor a composite signature, because it isn't a plain RSA signature. Exchange composite signatures only with peers that verify composite ML-DSA. - Size. A composite signature from ML-DSA-65 and RSA-3072 is 3,693 bytes long, and a pure ML-DSA-65 signature is 3,309 bytes. Size your database columns and protocol frames accordingly.
Related topics
en/wiki/pqcrypt/lib/how-to/sign-and-verify-data.txt · Zadnja sprememba: uporabnika Wolfgang van der Stille