1import java.security.KeyFactory
2import java.security.KeyPairGenerator
3import java.security.PrivateKey
4import java.security.PublicKey
5import java.security.Signature
6import java.security.spec.PKCS8EncodedKeySpec
7import java.security.spec.X509EncodedKeySpec
8import java.util.Base64
9import kotlin.io.path.Path
10import kotlin.io.path.readText
11
12fun mlDsaSign(key: PrivateKey, message: ByteArray): ByteArray =
13 Signature.getInstance("ML-DSA").run { initSign(key); update(message); sign() }
14
15fun mlDsaVerify(key: PublicKey, message: ByteArray, signature: ByteArray): Boolean =
16 Signature.getInstance("ML-DSA").run { initVerify(key); update(message); verify(signature) }
17
18private fun pemBody(path: String) = Base64.getMimeDecoder().decode(Path(path).readText().replace(Regex("-----[^-]+-----"), ""))
19
20fun readPublicKey(path: String): PublicKey = KeyFactory.getInstance("ML-DSA").generatePublic(X509EncodedKeySpec(pemBody(path)))
21fun readPrivateKey(path: String): PrivateKey = KeyFactory.getInstance("ML-DSA").generatePrivate(PKCS8EncodedKeySpec(pemBody(path)))
22
23fun main() {
24 val keys = KeyPairGenerator.getInstance("ML-DSA-65").generateKeyPair()
25 val message = "hello, post-quantum world".encodeToByteArray()
26 val signature = mlDsaSign(keys.private, message)
27 println("signature ${signature.size} bytes, verified: ${mlDsaVerify(keys.public, message, signature)}")
28 println("tampered message: ${mlDsaVerify(keys.public, message + '!'.code.toByte(), signature)}")
29
30 // Keys and signatures from OpenSSL 3.5: SPKI public key, PKCS#8 seed
31 val opensslKey = readPublicKey("openssl-pub.pem")
32 val opensslSignature = Base64.getMimeDecoder().decode(Path("openssl-sig.b64").readText())
33 println("OpenSSL signature verifies: ${mlDsaVerify(opensslKey, message, opensslSignature)}")
34 val seeded = readPrivateKey("seed.pem")
35 println("seed.pem signs for OpenSSL's public key: ${mlDsaVerify(opensslKey, message, mlDsaSign(seeded, message))}")
36}
37