Color scheme: Riviera
You are here: start » en » Documentation » Post-Quantum Cryptography » WKL.System.Security.Cryptography overview » How-to guides » Sign and verify data

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

ElementPurpose
RSA.SignData, ECDsa.SignData with CryptoMode.Hybridcomposite ML-DSA signature
RSA.VerifyData, ECDsa.VerifyData with CryptoMode.Hybridverifies both parts of the composite signature
SignatureExtensions.SetTransientPqKeysets the ML-DSA key pair for the current thread
SignatureExtensions.SetTransientPqPublicKeysets the ML-DSA public key for verification
SignatureExtensions.ClearTransientPqKeyremoves the thread's keys and overwrites the private key
MLDsaKey.SignData, MLDsaKey.VerifyDatapure 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 CryptographicException that names the pair. For the full list, see Formats of the post-quantum parts.
  • One key per thread. The keys from SetTransientPqKey apply only to the thread that sets them. In asynchronous code, set them right before the call, with no await in between, and remove them on the same thread. If the key is missing, SignData throws a CryptographicException.
  • ClearTransientPqKey overwrites your array. The library doesn't copy the private key. ClearTransientPqKey fills the array you passed with zeros. So pass a copy that you won't need afterward, such as the result of ExportPkcs8PrivateKey in the example.
  • .NET doesn't verify the composite signature. rsa.VerifyData without CryptoMode returns false for 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.
en/wiki/pqcrypt/lib/how-to/sign-and-verify-data.txt · Last modified: by Wolfgang van der Stille