http://docker:8082Sign application binaries, libraries, and scripts with ML-DSA. Replaces GPG/ECDSA code signing.
ML-DSA-65 (FIPS 204)Sign container image digests (SHA-256 hashes) with ML-DSA. Replaces cosign/Notary with PQC.
ML-DSA-65 (FIPS 204)Sign firmware images with SLH-DSA (FIPS 205) for long-term integrity. Hash-based signatures that survive even if lattice assumptions break.
SLH-DSA-SHA2-128s / 256s (FIPS 205)Verify any PQC signature against its public key using provider.verify().
ML-DSA-44 / ML-DSA-65 / ML-DSA-87This service demonstrates PQC code signing — the same patterns you'll implement in your CI/CD pipeline using the Qudo JNI provider.
Sign: provider.sign(data, privKey, "ML-DSA-65")
Verify: provider.verify(data, sig, pubKey, "ML-DSA-65")
Algorithms: ML-DSA-44 (IoT), ML-DSA-65 (general), ML-DSA-87 (highest security)
Generate a keypair once with provider.generateKeyPair("ML-DSA-65"), then sign every artifact. Ship the signature + public key alongside your binary.
/api/sign
Sign data with ML-DSA-65 (general purpose)
/api/sign
Sign data with ML-DSA-44 (lightweight/IoT)
/api/sign
Sign data with ML-DSA-87 (highest security)
/api/sign
Sign with SLH-DSA-SHA2-128s (long-term integrity)
/api/verify
Verify a PQC signature
/api/sign-container
Sign a container image digest
/api/algorithms
List supported PQC signing algorithms and sizes
/api/health
Service health + available algorithms
Replace classical code signing (GPG, ECDSA, RSA) with post-quantum ML-DSA signatures. Use this playground to try PQC signing, then use the Qudo JNI provider directly in your build pipeline.
Sign binaries, JARs, packages before distribution. Customers verify the signature to ensure the software wasn't tampered with.
Sign Docker/OCI image digests. Replaces cosign/Notary ECDSA signatures with quantum-safe ML-DSA.
Sign PDFs, legal documents, audit logs. ML-DSA-87 provides highest security for long-term validity.
Sign firmware for IoT/embedded devices. Devices verify the signature before applying updates.
Test all three ML-DSA security levels. Sign data, verify the signature, then try tampering to see verification fail.
Verify the original data, then try tampering to see ML-DSA detect the change.
| Algorithm | Security | Signature Size | Sign Latency | Best For |
|---|---|---|---|---|
| ML-DSA-44 | NIST Level 2 | 2,420 bytes | ~118ms | IoT, high-throughput, constrained devices |
| ML-DSA-65 | NIST Level 3 | 3,309 bytes | ~120ms | General purpose (recommended default) |
| ML-DSA-87 | NIST Level 5 | 4,627 bytes | ~125ms | High-value, government, long-term |
Replace your classical signing tool with the Qudo JNI provider. Your build pipeline stays the same — only the sign/verify step changes.
// GPG signing
gpg --sign --armor myapp.jar
// ECDSA
Signature signer = Signature.getInstance(
"SHA256withECDSA");
signer.initSign(ecPrivateKey);
signer.update(data);
byte[] sig = signer.sign();
// cosign (container)
cosign sign --key cosign.key myimage@sha256:...
// Generate keypair once at startup
QudoKeyPair keys = provider.generateKeyPair(
"ML-DSA-65");
// Sign
byte[] sig = provider.sign(data,
keys.getPrivateKeyPem(), "ML-DSA-65");
// Verify
boolean ok = provider.verify(data, sig,
keys.getPublicKeyPem(), "ML-DSA-65");
import com.qudo.crypto.QudoCrypto;
import com.qudo.crypto.QudoKeyPair;
import java.nio.file.Files;
import java.nio.file.Path;
// ============================================================
// Initialize once at application startup
// ============================================================
QudoCrypto provider = QudoCrypto.create();
// Generate ML-DSA-65 keypair once (store securely, reuse across signs)
QudoKeyPair signingKeys = provider.generateKeyPair("ML-DSA-65");
// ============================================================
// Sign a binary artifact
// ============================================================
byte[] artifact = Files.readAllBytes(Path.of("myapp.jar"));
byte[] signature = provider.sign(artifact, signingKeys.getPrivateKeyPem(), "ML-DSA-65");
// Save signature and public key alongside artifact
Files.write(Path.of("myapp.jar.sig"), signature);
Files.write(Path.of("myapp.jar.pub"), signingKeys.getPublicKeyPem());
// ============================================================
// Verify (on the consumer side)
// ============================================================
byte[] sig = Files.readAllBytes(Path.of("myapp.jar.sig"));
byte[] pubKey = Files.readAllBytes(Path.of("myapp.jar.pub"));
boolean valid = provider.verify(artifact, sig, pubKey, "ML-DSA-65");
// ============================================================
// Sign a container image digest
// ============================================================
byte[] digest = "sha256:e3b0c44298fc1c149afbf4c8996fb924...".getBytes();
byte[] containerSig = provider.sign(digest, signingKeys.getPrivateKeyPem(), "ML-DSA-65");
provider.close();
com.qudo:qudo-jni-crypto to your build and set -Djava.library.path to the native library location. Generate the keypair once, sign every artifact with provider.sign(), ship the signature + public key alongside.
| Component | Changes? | What to do |
|---|---|---|
| Signing tool | Yes — use Qudo | provider.sign(data, privKey, "ML-DSA-65") |
| Verification | Yes — use Qudo | provider.verify(data, sig, pubKey, "ML-DSA-65") |
| Key format | Yes | ML-DSA PEM keys from provider.generateKeyPair() |
| Signature size | Yes | 2.4-4.6KB (vs 64-256 bytes for ECDSA/RSA) |
| Build pipeline | Minimal | Replace the sign step in CI/CD — same workflow, different call |
| Distribution | No | Ship signature + public key alongside artifact (same as classical) |
| Verification workflow | No | Consumer gets artifact + sig + pubkey, verifies — same pattern |
| Algorithm | Key Gen | Sign | Verify | Signature Size | Public Key Size |
|---|---|---|---|---|---|
| ML-DSA-44 | ~50ms | ~118ms (total) | ~40ms | 2,420 bytes | 1,860 bytes (PEM) |
| ML-DSA-65 | ~55ms | ~120ms (total) | ~41ms | 3,309 bytes | 2,726 bytes (PEM) |
| ML-DSA-87 | ~60ms | ~125ms (total) | ~45ms | 4,627 bytes | 3,595 bytes (PEM) |
| ECDSA P-256 (classical) | ~1ms | ~2ms | ~2ms | 64 bytes | 65 bytes |
-rawin flag for ML-DSA signingCause: Without -rawin, OpenSSL treats the input as a pre-hashed digest (max 64 bytes).
Fix: Always use openssl pkeyutl -sign -rawin -inkey key.pem -in data.bin -out sig.bin
Cause: This demo service generates a fresh keypair per request. In production, this is wasteful and loses the key binding.
Fix: Generate your signing keypair once (at deployment), store the private key securely, and reuse it for all signing operations. Publish the public key for verifiers.
pkeyutlCause: The Qudo provider supports SLH-DSA for certificate signing (openssl req) but not arbitrary data signing via pkeyutl.
Workaround: Use ML-DSA-87 for data signing. Use SLH-DSA only for certificate issuance via the CA service.
Cause: ML-DSA-65 signatures are 3.3KB. For IoT or embedded systems with limited storage/bandwidth, this may be too large.
Fix: Use ML-DSA-44 (2.4KB) for constrained environments. It's NIST Level 2, still quantum-safe.
| Component | Minimum Version | Notes |
|---|---|---|
| OpenSSL | 3.6.0+ | Required for ML-DSA keygen and signing |
| Qudo Provider | 1.0.0+ | FIPS-validated PQC provider |
| Java | 17+ | For the Qudo JNI provider runtime |
| Any language | N/A | Verification needs Qudo provider or OpenSSL 3.6+ with Qudo module |
com.qudo:qudo-jni-crypto) to your build pipeline. Set -Djava.library.path to the native library location. For verification on the consumer side, install Qudo provider or OpenSSL 3.6+.
A: No. This demo generates a fresh keypair per request for simplicity. In production, generate your signing keypair once (during deployment or key ceremony), store the private key in a secure vault (HSM, KMS), and reuse it for all signing operations. Publish the public key for verifiers.
A: ML-DSA-65 for most use cases. It's NIST Level 3 (128-bit quantum security), balances signature size (3.3KB) and performance (~120ms). Use ML-DSA-44 only if you're signing millions of items per hour or targeting constrained devices. Use ML-DSA-87 for government/defense or data that must stay secure for 50+ years.
A: Two options:
1. Call the POST /api/verify endpoint — the server verifies using Qudo and returns the result.
2. For offline verification, install OpenSSL 3.6+ with Qudo on the verifier's machine and use:
openssl pkeyutl -verify -rawin -pubin -inkey pub.pem -in data.bin -sigfile sig.bin
A: Add a signing step after your build:
1. Base64-encode your build artifact
2. POST to /api/sign with algorithm ML-DSA-65
3. Save the returned signature and public key alongside the artifact
4. In your deployment pipeline, POST to /api/verify before deploying
Same workflow as classical signing — just a different API call.
A: The Qudo provider currently supports SLH-DSA for certificate operations (openssl req)
but not for arbitrary data signing via openssl pkeyutl. This is a provider limitation, not a protocol limitation.
For long-term document signing, use ML-DSA-87 (highest security). For certificate signing, use the CA service which supports SLH-DSA.
A: ML-DSA supports both deterministic and randomized signing. The Qudo provider uses randomized signing by default, which means signing the same data twice produces different signatures. Both are valid and will verify correctly against the same public key.
A: Generate a new ML-DSA keypair, publish the new public key, and start signing with the new private key.
Keep the old public key available for verifying previously-signed artifacts. Use the /api/algorithms endpoint
to confirm the key type. Consider using the Cloud KMS service for managed key rotation.
Analyze any endpoint's cryptographic configuration. Enter a host:port to scan its TLS setup and identify what's quantum-safe vs what needs migration.