1import java.security.KeyFactory
2import java.security.KeyPairGenerator
3import java.security.MessageDigest
4import java.security.PrivateKey
5import java.security.PublicKey
6import java.security.spec.PKCS8EncodedKeySpec
7import java.security.spec.X509EncodedKeySpec
8import java.util.Base64
9import javax.crypto.KEM
10import javax.crypto.SecretKey
11import kotlin.io.path.Path
12import kotlin.io.path.readText
13
14/** Sender side: a fresh shared secret and the ciphertext to send with it. */
15fun encapsulate(encapsulationKey: PublicKey): Pair<SecretKey, ByteArray> =
16 KEM.getInstance("ML-KEM").newEncapsulator(encapsulationKey).encapsulate().let { it.key() to it.encapsulation() }
17
18/** Receiver side: the shared secret inside a ciphertext. */
19fun decapsulate(decapsulationKey: PrivateKey, ciphertext: ByteArray): SecretKey =
20 KEM.getInstance("ML-KEM").newDecapsulator(decapsulationKey).decapsulate(ciphertext)
21
22private fun pemBody(path: String) = Base64.getMimeDecoder().decode(Path(path).readText().replace(Regex("-----[^-]+-----"), ""))
23
24fun readEncapsulationKey(path: String): PublicKey = KeyFactory.getInstance("ML-KEM").generatePublic(X509EncodedKeySpec(pemBody(path)))
25fun readDecapsulationKey(path: String): PrivateKey = KeyFactory.getInstance("ML-KEM").generatePrivate(PKCS8EncodedKeySpec(pemBody(path)))
26
27fun main() {
28 val keys = KeyPairGenerator.getInstance("ML-KEM-768").generateKeyPair()
29 val (senderKey, ciphertext) = encapsulate(keys.public)
30 val receiverKey = decapsulate(keys.private, ciphertext)
31 println("ciphertext ${ciphertext.size} bytes, shared secret ${senderKey.encoded.size} bytes, " +
32 "agreed: ${MessageDigest.isEqual(senderKey.encoded, receiverKey.encoded)}")
33
34 // Keys and a ciphertext from OpenSSL 3.5: SPKI public key, PKCS#8 seed
35 val opensslCiphertext = Base64.getMimeDecoder().decode(Path("openssl-ct.b64").readText())
36 val secret = decapsulate(readDecapsulationKey("seed.pem"), opensslCiphertext)
37 println("secret from OpenSSL's ciphertext: ${secret.encoded.toHexString()}")
38 val (_, toOpenSsl) = encapsulate(readEncapsulationKey("openssl-pub.pem"))
39 println("ciphertext for OpenSSL's key: ${toOpenSsl.size} bytes")
40}
41