]> Cypherpunks repositories - keks.git/commitdiff
Encapsulated ephemeral X25519 key
authorSergey Matveev <stargrave@stargrave.org>
Fri, 30 May 2025 19:27:26 +0000 (22:27 +0300)
committerSergey Matveev <stargrave@stargrave.org>
Fri, 30 May 2025 19:27:26 +0000 (22:27 +0300)
go/cm/cmd/cmenctool/main.go
go/cm/enc/kem.go
spec/cm/kem/mceliece6960119-x25519-hkdf-shake256

index 3b6efdef8653c051c3e5a33ce7d91ab6338cd56482550fd184729e3add044a2b..fd6028cfc50f130ef2aae717a143bf57471b829cdc7a3498b50ef4252d7b583f 100644 (file)
@@ -17,6 +17,7 @@ package main
 
 import (
        "bytes"
+       "crypto/cipher"
        "crypto/ecdh"
        "crypto/hkdf"
        "crypto/rand"
@@ -55,6 +56,8 @@ import (
 const (
        FdPubR = 4
        FdPrvR = 8
+
+       X25519KeyLen = 32
 )
 
 func blake2bHash() hash.Hash {
@@ -289,14 +292,14 @@ func main() {
                                if kem.Encap == nil {
                                        log.Fatalln("missing encap")
                                }
-                               if len(kem.Encap) != sntrup4591761.CiphertextSize+32 {
+                               if len(kem.Encap) != sntrup4591761.CiphertextSize+X25519KeyLen {
                                        log.Fatalln("invalid encap len")
                                }
                                for _, prv := range prvs {
                                        if prv.A != sntrup4591761x25519.SNTRUP4591761X25519 {
                                                continue
                                        }
-                                       if len(prv.V) != sntrup4591761.PrivateKeySize+32 {
+                                       if len(prv.V) != sntrup4591761.PrivateKeySize+X25519KeyLen {
                                                log.Fatalln("invalid private keys len")
                                        }
                                        var ourSNTRUP sntrup4591761.PrivateKey
@@ -348,8 +351,7 @@ func main() {
                                                        prk,
                                                        string(append(
                                                                []byte(cmenc.SNTRUP4591761X25519Info),
-                                                               encrypted.Id[:]...,
-                                                       )),
+                                                               encrypted.Id[:]...)),
                                                        chacha20poly1305.KeySize,
                                                )
                                                if err != nil {
@@ -377,30 +379,31 @@ func main() {
                                if kem.Encap == nil {
                                        log.Fatalln("missing encap")
                                }
-                               if len(kem.Encap) != mceliece6960119.CiphertextSize+32 {
+                               if len(kem.Encap) != mceliece6960119.CiphertextSize+X25519KeyLen+chacha20poly1305.Overhead {
                                        log.Fatalln("invalid encap len")
                                }
                                for _, prv := range prvs {
                                        if prv.A != mceliece6960119x25519.ClassicMcEliece6960119X25519 {
                                                continue
                                        }
-                                       if len(prv.V) != mceliece6960119.PrivateKeySize+32 {
+                                       if len(prv.V) != mceliece6960119.PrivateKeySize+X25519KeyLen {
                                                log.Fatalln("invalid private keys len")
                                        }
                                        var ourMcEliece *mceliece6960119.PrivateKey
                                        ourMcEliece, err = mceliece6960119.UnmarshalBinaryPrivateKey(
-                                               prv.V[:len(prv.V)-32],
+                                               prv.V[:len(prv.V)-X25519KeyLen],
                                        )
                                        if err != nil {
                                                log.Fatal(err)
                                        }
                                        x25519 := ecdh.X25519()
                                        var ourX25519 *ecdh.PrivateKey
-                                       ourX25519, err = x25519.NewPrivateKey(prv.V[len(prv.V)-32:])
+                                       ourX25519, err = x25519.NewPrivateKey(prv.V[len(prv.V)-X25519KeyLen:])
                                        if err != nil {
                                                log.Fatal(err)
                                        }
-                                       theirMcEliece := (kem.Encap)[:len(kem.Encap)-32]
+                                       theirMcEliece := (kem.Encap)[:len(kem.Encap)-X25519KeyLen-chacha20poly1305.Overhead]
+                                       theirX2559Encap := (kem.Encap)[len(kem.Encap)-X25519KeyLen-chacha20poly1305.Overhead:]
                                        var keyMcEliece []byte
                                        keyMcEliece, err = mceliece6960119.Decapsulate(
                                                ourMcEliece, theirMcEliece,
@@ -409,11 +412,39 @@ func main() {
                                                log.Fatal(err)
                                        }
                                        var theirX25519 *ecdh.PublicKey
-                                       theirX25519, err = x25519.NewPublicKey(
-                                               (kem.Encap)[len(kem.Encap)-32:],
-                                       )
-                                       if err != nil {
-                                               log.Fatal(err)
+                                       {
+                                               var decapKeymat []byte
+                                               decapKeymat, err = hkdf.Expand(
+                                                       cmhash.NewSHAKE256,
+                                                       keyMcEliece,
+                                                       string(append(
+                                                               []byte(cmenc.ClassicMcEliece6960119X25519DecapInfo),
+                                                               encrypted.Id[:]...)),
+                                                       chacha20poly1305.KeySize+chacha20poly1305.NonceSizeX,
+                                               )
+                                               if err != nil {
+                                                       log.Fatal(err)
+                                               }
+                                               decapKey := decapKeymat[:chacha20poly1305.KeySize]
+                                               decapNonce := decapKeymat[chacha20poly1305.KeySize:]
+                                               var aead cipher.AEAD
+                                               aead, err = chacha20poly1305.NewX(decapKey)
+                                               if err != nil {
+                                                       log.Fatal(err)
+                                               }
+                                               var theirX25519Raw []byte
+                                               theirX25519Raw, err = aead.Open(
+                                                       nil,
+                                                       decapNonce,
+                                                       theirX2559Encap,
+                                                       theirMcEliece)
+                                               if err != nil {
+                                                       log.Fatal(err)
+                                               }
+                                               theirX25519, err = x25519.NewPublicKey(theirX25519Raw)
+                                               if err != nil {
+                                                       log.Fatal(err)
+                                               }
                                        }
                                        var keyX25519 []byte
                                        keyX25519, err = ourX25519.ECDH(theirX25519)
@@ -432,7 +463,7 @@ func main() {
                                                pkHash.Write(ourX25519.PublicKey().Bytes())
                                                ikm := bytes.Join([][]byte{
                                                        keyMcEliece, keyX25519,
-                                                       sha3.SumSHAKE256(kem.Encap, 32),
+                                                       sha3.SumSHAKE256(kem.Encap, X25519KeyLen),
                                                        pkHash.Sum(nil),
                                                }, []byte{})
                                                var prk []byte
@@ -446,8 +477,7 @@ func main() {
                                                        prk,
                                                        string(append(
                                                                []byte(cmenc.ClassicMcEliece6960119X25519Info),
-                                                               encrypted.Id[:]...,
-                                                       )),
+                                                               encrypted.Id[:]...)),
                                                        chacha20poly1305.KeySize,
                                                )
                                                if err != nil {
@@ -541,7 +571,7 @@ func main() {
                for pubId, pub := range pubs {
                        switch pub.A {
                        case sntrup4591761x25519.SNTRUP4591761X25519:
-                               if len(pub.V) != sntrup4591761.PublicKeySize+32 {
+                               if len(pub.V) != sntrup4591761.PublicKeySize+X25519KeyLen {
                                        log.Fatalln("invalid public keys len")
                                }
                                var theirSNTRUP sntrup4591761.PublicKey
@@ -610,19 +640,19 @@ func main() {
                                }
                                kems = append(kems, kem)
                        case mceliece6960119x25519.ClassicMcEliece6960119X25519:
-                               if len(pub.V) != mceliece6960119.PublicKeySize+32 {
+                               if len(pub.V) != mceliece6960119.PublicKeySize+X25519KeyLen {
                                        log.Fatalln("invalid public keys len")
                                }
                                var theirMcEliece *mceliece6960119.PublicKey
                                theirMcEliece, err = mceliece6960119.UnmarshalBinaryPublicKey(
-                                       pub.V[:len(pub.V)-32],
+                                       pub.V[:len(pub.V)-X25519KeyLen],
                                )
                                if err != nil {
                                        log.Fatal(err)
                                }
                                x25519 := ecdh.X25519()
                                var theirX25519 *ecdh.PublicKey
-                               theirX25519, err = x25519.NewPublicKey(pub.V[len(pub.V)-32:])
+                               theirX25519, err = x25519.NewPublicKey(pub.V[len(pub.V)-X25519KeyLen:])
                                if err != nil {
                                        log.Fatal(err)
                                }
@@ -638,15 +668,40 @@ func main() {
                                        log.Fatal(err)
                                }
                                ourPubX25519 := ourPrvX25519.PublicKey()
+                               kem := cmenc.KEM{
+                                       A: mceliece6960119x25519.ClassicMcEliece6960119X25519HKDFSHAKE256,
+                               }
+                               {
+                                       var decapKeymat []byte
+                                       decapKeymat, err = hkdf.Expand(
+                                               cmhash.NewSHAKE256,
+                                               keyMcEliece,
+                                               string(append(
+                                                       []byte(cmenc.ClassicMcEliece6960119X25519DecapInfo),
+                                                       id[:]...)),
+                                               chacha20poly1305.KeySize+chacha20poly1305.NonceSizeX,
+                                       )
+                                       if err != nil {
+                                               log.Fatal(err)
+                                       }
+                                       decapKey := decapKeymat[:chacha20poly1305.KeySize]
+                                       decapNonce := decapKeymat[chacha20poly1305.KeySize:]
+                                       var aead cipher.AEAD
+                                       aead, err = chacha20poly1305.NewX(decapKey)
+                                       if err != nil {
+                                               log.Fatal(err)
+                                       }
+                                       kem.Encap = aead.Seal(
+                                               ciphertext[:],
+                                               decapNonce,
+                                               ourPubX25519.Bytes(),
+                                               ciphertext)
+                               }
                                var keyX25519 []byte
                                keyX25519, err = ourPrvX25519.ECDH(theirX25519)
                                if err != nil {
                                        log.Fatal(err)
                                }
-                               kem := cmenc.KEM{
-                                       A:     mceliece6960119x25519.ClassicMcEliece6960119X25519HKDFSHAKE256,
-                                       Encap: append(ciphertext[:], ourPubX25519.Bytes()...),
-                               }
                                {
                                        ikm := bytes.Join([][]byte{
                                                keyMcEliece[:], keyX25519,
@@ -664,8 +719,7 @@ func main() {
                                                prk,
                                                string(append(
                                                        []byte(cmenc.ClassicMcEliece6960119X25519Info),
-                                                       id[:]...),
-                                               ),
+                                                       id[:]...)),
                                                chacha20poly1305.KeySize,
                                        )
                                        if err != nil {
index 7e41cad5095cd1fbcfa5e6207e25a053802766851ec059edfdb1c2e3821b02ff..a5ca55f922293e9a8a4b6b7e010ddeb97e666b51b5a482f7577e94c71e798c6b 100644 (file)
@@ -7,6 +7,8 @@ import (
 const (
        SNTRUP4591761X25519Info          = "cm/encrypted/sntrup4591761-x25519-hkdf-blake2b"
        ClassicMcEliece6960119X25519Info = "cm/encrypted/mceliece6960119-x25519-hkdf-shake256"
+
+       ClassicMcEliece6960119X25519DecapInfo = "cm/encrypted/mceliece6960119-x25519-hkdf-shake256/decap"
 )
 
 type KEM struct {
index fc88bca8918066ce41393f932c363833b52b81649934d2c23d96957f294144d6..307b8ea8961229662f955e13ee880e8e70e65067fb55ae375d09e7b3bb0f74ef 100644 (file)
@@ -11,23 +11,35 @@ Recipient public key with [cm/pub/mceliece6960119-x25519]
 algorithm must be used. It should have "kem" key usage set.
 
 "/kem/*/encap" field is a concatenation of 194 bytes of
-Classic McEliece 6960-119 ciphertext, containing ephemeral key,
-with 32 bytes of ephemeral X25519 public key.
+Classic McEliece 6960-119 ciphertext, with XChaCha20-Poly1305-encrypted
+32 bytes ephemeral X25519 public key:
 
-Recipient performs X25519 and Classic McEliece computations to
-derive/decapsulate two 32-byte shared keys. Then it combines
-them to get the KEK decryption key of the CEK.
+    mceliece-ciphertext || XChaCha20-Poly1305(
+        key=decapKey,
+        nonce=decapNonce,
+        data=e-x25519-sender-public-key,
+        ad=mceliece-ciphertext)
+
+Recipient performs Classic McEliece decapsulation, decrypts ephemeral
+X25519 public key, computes shared secrets, combines them and derives KEK.
 
     H = SHAKE256
+    mceliece-ciphertext, mceliece-shared-key = KEM-Encap(mceliece-recipient-public-key)
+    mceliece-shared-key = KEM-Decap(mceliece-recipient-private-key, mceliece-ciphertext)
+
+    decapKey, decapNonce = HKDF-Expand(H, prk=mceliece-shared-key,
+        info="cm/encrypted/mceliece6960119-x25519-hkdf-shake256/decap" || /id)
+    es-x25519-shared-key = X25519(e-x25519-sender-private-key,
+                                  s-x25519-recipient-public-key)
     PRK = HKDF-Extract(H, salt="", ikm=
-        mceliece6960119-shared-key || es-x25519-shared-key ||
-        H(mceliece6960119-sender-ciphertext || e-x25519-sender-public-key) ||
-        H(mceliece6960119-recipient-public-key || s-x25519-recipient-public-key))
+        mceliece-shared-key || es-x25519-shared-key ||
+        H(mceliece-sender-ciphertext || e-x25519-sender-public-key) ||
+        H(mceliece-recipient-public-key || s-x25519-recipient-public-key))
     if {specified sender}
+        ss-x25519-shared-key = X25519(s-x25519-sender-private-key,
+                                      s-x25519-recipient-public-key)
         PRK = HKDF-Extract(H, salt=PRK, ikm=
-            ss-x25519-shared-key ||
-            s-x25519-sender-public-key ||
-            s-x25519-recipient-public-key)
+            ss-x25519-shared-key || s-x25519-sender-public-key)
     KEK = HKDF-Expand(H, prk=PRK,
         info="cm/encrypted/mceliece6960119-x25519-hkdf-shake256" || /id)