From: Sergey Matveev Date: Fri, 30 May 2025 19:27:26 +0000 (+0300) Subject: Encapsulated ephemeral X25519 key X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=631490af9feae2356fe85835c14e544f964a6908f14721d2f19c5e3e0279e69a;p=keks.git Encapsulated ephemeral X25519 key --- diff --git a/go/cm/cmd/cmenctool/main.go b/go/cm/cmd/cmenctool/main.go index 3b6efde..fd6028c 100644 --- a/go/cm/cmd/cmenctool/main.go +++ b/go/cm/cmd/cmenctool/main.go @@ -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 { diff --git a/go/cm/enc/kem.go b/go/cm/enc/kem.go index 7e41cad..a5ca55f 100644 --- a/go/cm/enc/kem.go +++ b/go/cm/enc/kem.go @@ -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 { diff --git a/spec/cm/kem/mceliece6960119-x25519-hkdf-shake256 b/spec/cm/kem/mceliece6960119-x25519-hkdf-shake256 index fc88bca..307b8ea 100644 --- a/spec/cm/kem/mceliece6960119-x25519-hkdf-shake256 +++ b/spec/cm/kem/mceliece6960119-x25519-hkdf-shake256 @@ -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)