From 614ab2fcc000b763d05c77ac85652fb494472523839f37001276ab3839f99eda Mon Sep 17 00:00:00 2001 From: Sergey Matveev Date: Tue, 11 Feb 2025 19:04:54 +0300 Subject: [PATCH] mceliece6960119-x25519 --- go/pki/algo.go | 25 +++-- go/pki/av.go | 2 + go/pki/cmd/certool/main.go | 4 + go/pki/cmd/enctool/main.go | 149 +++++++++++++++++++++++++- go/pki/hash/algo.go | 5 + go/pki/mceliece6960119-x25519/SOURCE | 3 + go/pki/mceliece6960119-x25519/algo.go | 6 ++ go/pki/mceliece6960119-x25519/kp.go | 51 +++++++++ spec/format/cer.texi | 15 ++- spec/format/encrypted.cddl | 12 ++- spec/format/encrypted.texi | 143 ++++++++++++++---------- spec/format/hashed.texi | 2 +- spec/format/private-key.texi | 32 +++--- spec/format/registry.texi | 2 + 14 files changed, 364 insertions(+), 87 deletions(-) create mode 100644 go/pki/mceliece6960119-x25519/SOURCE create mode 100644 go/pki/mceliece6960119-x25519/algo.go create mode 100644 go/pki/mceliece6960119-x25519/kp.go diff --git a/go/pki/algo.go b/go/pki/algo.go index 69b769a..4b5f209 100644 --- a/go/pki/algo.go +++ b/go/pki/algo.go @@ -4,21 +4,24 @@ import ( "go.cypherpunks.su/keks" ed25519blake2b "go.cypherpunks.su/keks/pki/ed25519-blake2b" "go.cypherpunks.su/keks/pki/gost" + mceliece6960119x25519 "go.cypherpunks.su/keks/pki/mceliece6960119-x25519" sntrup4591761x25519 "go.cypherpunks.su/keks/pki/sntrup4591761-x25519" ) const ( - Ed25519BLAKE2b = ed25519blake2b.Ed25519BLAKE2b - Ed25519PhBLAKE2b = ed25519blake2b.Ed25519PhBLAKE2b - Ed25519PhBLAKE2bMerkle = ed25519blake2b.Ed25519PhBLAKE2bMerkle - GOST3410256A = gost.GOST3410256A - GOST3410256AMerkle = gost.GOST3410256AMerkle - GOST3410512C = gost.GOST3410512C - GOST3410512CMerkle = gost.GOST3410512CMerkle - SNTRUP4591761X25519 = sntrup4591761x25519.SNTRUP4591761X25519 - SNTRUP4591761X25519HKDFBLAKE2b = sntrup4591761x25519.SNTRUP4591761X25519HKDFBLAKE2b - BalloonBLAKE2bHKDF = "balloon-blake2b-hkdf" - ChaCha20Poly1305 = "chacha20poly1305" + BalloonBLAKE2bHKDF = "balloon-blake2b-hkdf" + ChaCha20Poly1305 = "chacha20poly1305" + ClassicMcEliece6960119X25519 = mceliece6960119x25519.ClassicMcEliece6960119X25519 + ClassicMcEliece6960119X25519HKDFSHAKE256 = mceliece6960119x25519.ClassicMcEliece6960119X25519HKDFSHAKE256 + Ed25519BLAKE2b = ed25519blake2b.Ed25519BLAKE2b + Ed25519PhBLAKE2b = ed25519blake2b.Ed25519PhBLAKE2b + Ed25519PhBLAKE2bMerkle = ed25519blake2b.Ed25519PhBLAKE2bMerkle + GOST3410256A = gost.GOST3410256A + GOST3410256AMerkle = gost.GOST3410256AMerkle + GOST3410512C = gost.GOST3410512C + GOST3410512CMerkle = gost.GOST3410512CMerkle + SNTRUP4591761X25519 = sntrup4591761x25519.SNTRUP4591761X25519 + SNTRUP4591761X25519HKDFBLAKE2b = sntrup4591761x25519.SNTRUP4591761X25519HKDFBLAKE2b EncryptedMagic = keks.Magic("pki/encryptd") HashedMagic = keks.Magic("pki/hashed") diff --git a/go/pki/av.go b/go/pki/av.go index e9cb679..b267941 100644 --- a/go/pki/av.go +++ b/go/pki/av.go @@ -41,6 +41,8 @@ func (av *AV) Id() (id uuid.UUID) { hasher = pkihash.ByName(pkihash.BLAKE2b256) case GOST3410256A, GOST3410512C: hasher = pkihash.ByName(pkihash.Streebog256) + case ClassicMcEliece6960119X25519: + hasher = pkihash.ByName(pkihash.SHAKE128) default: id = uuid.Nil return diff --git a/go/pki/cmd/certool/main.go b/go/pki/cmd/certool/main.go index c437c73..ccd11dd 100644 --- a/go/pki/cmd/certool/main.go +++ b/go/pki/cmd/certool/main.go @@ -30,6 +30,7 @@ import ( "go.cypherpunks.su/keks/pki" ed25519blake2b "go.cypherpunks.su/keks/pki/ed25519-blake2b" "go.cypherpunks.su/keks/pki/gost" + mceliece6960119x25519 "go.cypherpunks.su/keks/pki/mceliece6960119-x25519" "go.cypherpunks.su/keks/pki/sign" sntrup4591761x25519 "go.cypherpunks.su/keks/pki/sntrup4591761-x25519" "go.cypherpunks.su/keks/pki/utils" @@ -89,6 +90,7 @@ func main() { pki.GOST3410256A, pki.GOST3410512C, pki.SNTRUP4591761X25519, + pki.ClassicMcEliece6960119X25519, } sort.Strings(algos) for _, s := range algos { @@ -170,6 +172,8 @@ func main() { _, prvRaw, pub, err = gost.NewKeypair(*algo) case pki.SNTRUP4591761X25519: prvRaw, pub, err = sntrup4591761x25519.NewKeypair() + case pki.ClassicMcEliece6960119X25519: + prvRaw, pub, err = mceliece6960119x25519.NewKeypair() default: err = errors.New("unknown -algo specified") } diff --git a/go/pki/cmd/enctool/main.go b/go/pki/cmd/enctool/main.go index 0d6d7da..ca1ec8f 100644 --- a/go/pki/cmd/enctool/main.go +++ b/go/pki/cmd/enctool/main.go @@ -27,12 +27,15 @@ import ( "log" "os" + circlkem "github.com/cloudflare/circl/kem" + "github.com/cloudflare/circl/kem/mceliece/mceliece6960119" "github.com/companyzero/sntrup4591761" "github.com/google/uuid" "go.cypherpunks.su/balloon/v3" "golang.org/x/crypto/blake2b" "golang.org/x/crypto/chacha20poly1305" "golang.org/x/crypto/hkdf" + "golang.org/x/crypto/sha3" "golang.org/x/term" "go.cypherpunks.su/keks" @@ -42,9 +45,10 @@ import ( ) const ( - BalloonSaltLen = 8 - BalloonHKDFSalt = "keks/pki/encrypted/balloon-blake2b-hkdf" - SNTRUP4591761X25519Salt = "keks/pki/encrypted/sntrup4591761-x25519-hkdf-blake2b" + BalloonSaltLen = 8 + BalloonHKDFSalt = "keks/pki/encrypted/balloon-blake2b-hkdf" + SNTRUP4591761X25519Salt = "keks/pki/encrypted/sntrup4591761-x25519-hkdf-blake2b" + ClassicMcEliece6960119X25519Salt = "keks/pki/encrypted/mceliece6960119-x25519-hkdf-shake256" BindFdNum = 3 + 1 ) @@ -64,7 +68,6 @@ type KEM struct { Cost *BalloonCost `keks:"cost,omitempty"` Salt *[]byte `keks:"salt,omitempty"` - // sntrup4591761-x25519-hkdf-blake2b related Encap *[]byte `keks:"encap,omitempty"` } @@ -88,6 +91,10 @@ func blake2b256() hash.Hash { return h } +func shake256() hash.Hash { + return sha3.NewShake256() +} + func readPasswd(prompt string) (passwd []byte) { tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0) if err != nil { @@ -286,6 +293,85 @@ func main() { cek = cekp } } + case pki.ClassicMcEliece6960119X25519HKDFSHAKE256: + if len(prvs) == 0 { + log.Println(kemIdx, kem.A, "skipping because no -prv") + continue + } + if kem.Encap == nil { + log.Fatalln("missing encap") + } + scheme := mceliece6960119.Scheme() + if len(*kem.Encap) != scheme.CiphertextSize()+32 { + log.Fatalln("invalid encap len") + } + for _, prv := range prvs { + if len(prv.V) != scheme.PrivateKeySize()+32 { + log.Fatalln("invalid private keys len") + } + var ourMcEliece circlkem.PrivateKey + ourMcEliece, err = scheme.UnmarshalBinaryPrivateKey( + prv.V[:len(prv.V)-32], + ) + if err != nil { + log.Fatal(err) + } + + x25519 := ecdh.X25519() + var ourX25519 *ecdh.PrivateKey + ourX25519, err = x25519.NewPrivateKey(prv.V[len(prv.V)-32:]) + if err != nil { + log.Fatal(err) + } + theirMcEliece := (*kem.Encap)[:len(*kem.Encap)-32] + var keyMcEliece []byte + keyMcEliece, err = scheme.Decapsulate(ourMcEliece, theirMcEliece) + if err != nil { + log.Fatal(err) + } + var theirX25519 *ecdh.PublicKey + theirX25519, err = x25519.NewPublicKey( + (*kem.Encap)[len(*kem.Encap)-32:], + ) + if err != nil { + log.Fatal(err) + } + var keyX25519 []byte + keyX25519, err = ourX25519.ECDH(theirX25519) + if err != nil { + log.Fatal(err) + } + { + ourMcEliecePub := ourMcEliece.Public() + var ourMcEliecePubRaw []byte + ourMcEliecePubRaw, err = ourMcEliecePub.MarshalBinary() + if err != nil { + log.Fatal(err) + } + pub := append( + ourMcEliecePubRaw, + ourX25519.PublicKey().Bytes()..., + ) + ikm := bytes.Join([][]byte{ + encrypted.Bind[:], + *kem.Encap, pub, + keyMcEliece, keyX25519, + }, []byte{}) + kek := hkdf.Extract(shake256, + ikm, []byte(ClassicMcEliece6960119X25519Salt)) + var cekp []byte + cekp, err = kemChaPolyOpen( + kek[:chacha20poly1305.KeySize], + kem.CEK, + chacha20poly1305.KeySize, + ) + if err != nil { + log.Println(kemIdx, kem.A, err, ", skipping") + continue + } + cek = cekp + } + } default: log.Println("unsupported KEM:", kem.A) continue @@ -410,6 +496,61 @@ func main() { kem.To = &pub.Id } kems = append(kems, kem) + case pki.ClassicMcEliece6960119X25519: + scheme := mceliece6960119.Scheme() + if len(pub.V) != scheme.PublicKeySize()+32 { + log.Fatalln("invalid public keys len") + } + var theirMcEliece circlkem.PublicKey + theirMcEliece, err = scheme.UnmarshalBinaryPublicKey( + pub.V[:len(pub.V)-32], + ) + if err != nil { + log.Fatal(err) + } + x25519 := ecdh.X25519() + var theirX25519 *ecdh.PublicKey + theirX25519, err = x25519.NewPublicKey(pub.V[len(pub.V)-32:]) + if err != nil { + log.Fatal(err) + } + var ciphertext []byte + var keyMcEliece []byte + ciphertext, keyMcEliece, err = scheme.Encapsulate(theirMcEliece) + if err != nil { + log.Fatal(err) + } + var ourPrvX25519 *ecdh.PrivateKey + ourPrvX25519, err = ecdh.X25519().GenerateKey(rand.Reader) + if err != nil { + log.Fatal(err) + } + ourPubX25519 := ourPrvX25519.PublicKey() + var keyX25519 []byte + keyX25519, err = ourPrvX25519.ECDH(theirX25519) + if err != nil { + log.Fatal(err) + } + kem := KEM{A: pki.ClassicMcEliece6960119X25519HKDFSHAKE256} + encap := append(ciphertext[:], ourPubX25519.Bytes()...) + kem.Encap = &encap + { + ikm := bytes.Join([][]byte{ + binding[:], + encap, pub.V, + keyMcEliece[:], keyX25519, + }, []byte{}) + kek := hkdf.Extract(shake256, + ikm, []byte(ClassicMcEliece6960119X25519Salt)) + kem.CEK, err = kemChaPolySeal(kek[:chacha20poly1305.KeySize], cek) + if err != nil { + log.Fatal(err) + } + } + if *includeTo { + kem.To = &pub.Id + } + kems = append(kems, kem) default: log.Fatalln("unsupported KEM:", pub.A) } diff --git a/go/pki/hash/algo.go b/go/pki/hash/algo.go index 8fb809f..b2e4cba 100644 --- a/go/pki/hash/algo.go +++ b/go/pki/hash/algo.go @@ -22,6 +22,7 @@ import ( "go.cypherpunks.su/gogost/v6/gost34112012256" "go.cypherpunks.su/gogost/v6/gost34112012512" "golang.org/x/crypto/blake2b" + "golang.org/x/crypto/sha3" ed25519blake2b "go.cypherpunks.su/keks/pki/ed25519-blake2b" "go.cypherpunks.su/keks/pki/gost" @@ -71,6 +72,10 @@ func ByName(name string) hash.Hash { panic(err) } return h + case SHAKE128: + return sha3.NewShake128() + case SHAKE256: + return sha3.NewShake256() case SHAKE128Merkle: return NewSHAKE128MerkleHasher( merkle.DefaultChunkLen, runtime.NumCPU()) diff --git a/go/pki/mceliece6960119-x25519/SOURCE b/go/pki/mceliece6960119-x25519/SOURCE new file mode 100644 index 0000000..4fa5970 --- /dev/null +++ b/go/pki/mceliece6960119-x25519/SOURCE @@ -0,0 +1,3 @@ +https://github.com/cloudflare/circl/pull/378 +https://github.com/cloudflare/circl.git +7dfc396c96830ed3601ace705e1612b9bcc447f9 diff --git a/go/pki/mceliece6960119-x25519/algo.go b/go/pki/mceliece6960119-x25519/algo.go new file mode 100644 index 0000000..852bb58 --- /dev/null +++ b/go/pki/mceliece6960119-x25519/algo.go @@ -0,0 +1,6 @@ +package mceliece6960119x25519 + +const ( + ClassicMcEliece6960119X25519 = "mceliece6960119-x25519" + ClassicMcEliece6960119X25519HKDFSHAKE256 = "mceliece6960119-x25519-hkdf-shake256" +) diff --git a/go/pki/mceliece6960119-x25519/kp.go b/go/pki/mceliece6960119-x25519/kp.go new file mode 100644 index 0000000..9c311b3 --- /dev/null +++ b/go/pki/mceliece6960119-x25519/kp.go @@ -0,0 +1,51 @@ +// GoKEKS/PKI -- PKI-related capabilities based on KEKS encoded formats +// Copyright (C) 2024-2025 Sergey Matveev +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation, version 3 of the License. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this program. If not, see . + +package mceliece6960119x25519 + +import ( + "crypto/ecdh" + "crypto/rand" + + "github.com/cloudflare/circl/kem" + "github.com/cloudflare/circl/kem/mceliece/mceliece6960119" +) + +func NewKeypair() (prv, pub []byte, err error) { + var pubMcEliece kem.PublicKey + var prvMcEliece kem.PrivateKey + scheme := mceliece6960119.Scheme() + pubMcEliece, prvMcEliece, err = scheme.GenerateKeyPair() + if err != nil { + return + } + var prvX25519 *ecdh.PrivateKey + prvX25519, err = ecdh.X25519().GenerateKey(rand.Reader) + if err != nil { + return + } + pubX25519 := prvX25519.PublicKey() + raw, err := prvMcEliece.MarshalBinary() + if err != nil { + return + } + prv = append(raw, prvX25519.Bytes()...) + raw, err = pubMcEliece.MarshalBinary() + if err != nil { + return + } + pub = append(raw, pubX25519.Bytes()...) + return +} diff --git a/spec/format/cer.texi b/spec/format/cer.texi index a34f244..8bbde02 100644 --- a/spec/format/cer.texi +++ b/spec/format/cer.texi @@ -116,7 +116,7 @@ using BLAKE2b hash with 128 or 256 bit output length specified. Algorithm identifiers for the public key: @code{ed25519ph-blake2b}, @node cer-sntrup4591761-x25519 -@subsection cer with SNTRUP4591761-X25519 +@subsection cer with SNTRUP4591761+X25519 Certificate with combined Streamlined NTRU Prime 4591^761 and Curve25519 public keys is used for KEM purposes, so should have "kem" key usage set. @@ -127,3 +127,16 @@ value is a concatenation of 1218-byte SNTRUP4591761 public key and Public key's identifier and @code{cid} should be calculated using BLAKE2b hash with 128 or 256 bit output length specified. + +@node cer-mceliece6960119-x25519 +@subsection cer with Classic McEliece 6960119+x25519 + +Certificate with combined Classic McEliece 6960119 and Curve25519 +public keys is used for KEM purposes, so should have "kem" key usage set. + +Its algorithm identifier is @code{mceliece6960119-x25519}. Its public key +value is a concatenation of 1047319-byte mceliece6960119 public key and +32-byte Curve25519 one. + +Public key's identifier and @code{cid} should be calculated +using either SHAKE128 or SHAKE256 hash. diff --git a/spec/format/encrypted.cddl b/spec/format/encrypted.cddl index 0122491..c575450 100644 --- a/spec/format/encrypted.cddl +++ b/spec/format/encrypted.cddl @@ -20,7 +20,8 @@ dem-kuznechik-ctracpkm-hmac-hkdf = { kem = kem-generic / kem-balloon-blake2b-hkdf / kem-gost3410-hkdf-kexp15 / - kem-sntrup4591761-x25519-hkdf-blake2b + kem-sntrup4591761-x25519-hkdf-blake2b / + kem-mceliece6960119-x25519-hkdf-shake256 kem-generic = { a: ai, @@ -49,7 +50,14 @@ kem-gost3410-hkdf-kexp15 = { } kem-sntrup4591761-x25519-hkdf-blake2b = { - a: ai, + a: "sntrup4591761-x25519-hkdf-blake2b", + cek: bytes, + encap: bytes, + ? to: uuid, +} + +kem-mceliece6960119-x25519-hkdf-shake256 = { + a: "mceliece6960119-x25519-hkdf-shake256 ", cek: bytes, encap: bytes, ? to: uuid, diff --git a/spec/format/encrypted.texi b/spec/format/encrypted.texi index db0bd0c..aca04bf 100644 --- a/spec/format/encrypted.texi +++ b/spec/format/encrypted.texi @@ -33,8 +33,8 @@ Either UUIDv4 or UUIDv7 are recommended. @node pki-encrypted-chacha20poly1305 @subsection pki-encrypted with ChaCha20-Poly1305 DEM -@code{/dem/a} equals to "chacha20poly1305". Data is split on 64 KiB -chunks which are encrypted the following way: + @code{/dem/a} equals to "chacha20poly1305". Data is split on 64 KiB + chunks which are encrypted the following way: @verbatim ChaCha20-Poly1305( @@ -42,19 +42,19 @@ ChaCha20-Poly1305( data=16*0x00 || chunk, ad="") @end verbatim -where @code{counter} starts at zero and incremented with each chunk. -@code{tail-flag} is a byte indicating if that is the last chunk in the -payload. It equals to 0x01 for the last chunk and to 0x00 for other ones. -Last chunk should be smaller than previous ones, maybe even empty. + where @code{counter} starts at zero and incremented with each chunk. + @code{tail-flag} is a byte indicating if that is the last chunk in the + payload. It equals to 0x01 for the last chunk and to 0x00 for other ones. + Last chunk should be smaller than previous ones, maybe even empty. -@code{/ciphertext}'s chunk length equals to 16+64KiB+16 bytes. + @code{/ciphertext}'s chunk length equals to 16+64KiB+16 bytes. @node pki-encrypted-kuznechik-ctracpkm-hmac-hkdf @subsection pki-encrypted-kuznechik-ctracpkm-hmac-hkdf -@code{/dem/a} equals to "kuznechik-ctracpkm-hmac-hkdf". -@code{/dem/seed} contains 16 bytes for the HKDF invocation below. -@code{/dem/iv} contains 8 bytes of initialisation vector. + @code{/dem/a} equals to "kuznechik-ctracpkm-hmac-hkdf". + @code{/dem/seed} contains 16 bytes for the HKDF invocation below. + @code{/dem/iv} contains 8 bytes of initialisation vector. @verbatim Kenc, Kauth = HKDF-Extract(Streebog-512, @@ -62,34 +62,34 @@ Kenc, Kauth = HKDF-Extract(Streebog-512, secret=seed || CEK) @end verbatim -Encryption is performed with Kuznechik (ГОСТ Р 34.12-2015) block cipher -in CTR-ACPKM mode of operation (Р 1323565.1.017) with 256KiB section -size. Authentication of ciphertext is performed with Streebog-512 (ГОСТ -Р 34.11-2012) in HMAC mode. + Encryption is performed with Kuznechik (ГОСТ Р 34.12-2015) block cipher + in CTR-ACPKM mode of operation (Р 1323565.1.017) with 256KiB section + size. Authentication of ciphertext is performed with Streebog-512 (ГОСТ + Р 34.11-2012) in HMAC mode. -@code{/ciphertext}'s chunk length equals to 64KiB bytes. + @code{/ciphertext}'s chunk length equals to 64KiB bytes. @node pki-encrypted-balloon-blake2b-hkdf @subsection pki-encrypted with Balloon-BLAKE2b+HKDF-BLAKE2b KEM -@code{/kem/*/a} equals to "balloon-blake2b-hkdf". -Recipient map must also contain additional fields: + @code{/kem/*/a} equals to "balloon-blake2b-hkdf". + Recipient map must also contain additional fields: -@table @code -@item /to/*/cost/s: uint64 - Balloon's space cost (buffer size, number of hash-output sized blocks). -@item /to/*/cost/t: uint64 - Balloon's time cost (number of rounds). -@item /to/*/cost/p: uint64 - Balloon's parallel cost (number of threads). -@item /to/*/salt: bin - Salt. -@end table + @table @code + @item /to/*/cost/s: uint64 + Balloon's space cost (buffer size, number of hash-output sized blocks). + @item /to/*/cost/t: uint64 + Balloon's time cost (number of rounds). + @item /to/*/cost/p: uint64 + Balloon's parallel cost (number of threads). + @item /to/*/salt: bin + Salt. + @end table -@url{https://crypto.stanford.edu/balloon/, Balloon} memory-hardened -password hasher must be used with BLAKE2b-256 hash. + @url{https://crypto.stanford.edu/balloon/, Balloon} memory-hardened + password hasher must be used with BLAKE2b-256 hash. -@code{/kem/*/cek} is encrypted the following way: + @code{/kem/*/cek} is encrypted the following way: @verbatim KEK = HKDF-Extract(BLAKE2b-256, @@ -101,22 +101,22 @@ ChaCha20-Poly1305(data=16*0x00 || CEK, key=KEK, nonce=12*0x00, ad="") @node pki-encrypted-gost3410-hkdf-kexp15 @subsection pki-encrypted-gost3410-hkdf-kexp15 -@code{/kem/*/a} equals to "gost3410-hkdf-kexp15". -Recipient map must also contain additional fields: + @code{/kem/*/a} equals to "gost3410-hkdf-kexp15". + Recipient map must also contain additional fields: -@table @code -@item /to/*/ukm: bytes - Additional 16-bytes keying material. -@item /to/*/pub: bytes - Sender's ephemeral 512-bit public key. -@item /to/*/iv: bytes - 8-byte initialisation vector for KExp15. -@end table + @table @code + @item /to/*/ukm: bytes + Additional 16-bytes keying material. + @item /to/*/pub: bytes + Sender's ephemeral 512-bit public key. + @item /to/*/iv: bytes + 8-byte initialisation vector for KExp15. + @end table -ГОСТ Р 34.10-2012 VKO parameter set A/C ("gost3410-256A", "gost3410-512C") -must be used for DH operation, with UKM taken from the structure. VKO's -output is 512- or 1024-bit @code{BE(X)||BE(Y)} point. It is used in HKDF -and KExp15 (Р 1323565.1.017) key wrapping algorithm: + ГОСТ Р 34.10-2012 VKO parameter set A/C ("gost3410-256A", "gost3410-512C") + must be used for DH operation, with UKM taken from the structure. VKO's + output is 512- or 1024-bit @code{BE(X)||BE(Y)} point. It is used in HKDF + and KExp15 (Р 1323565.1.017) key wrapping algorithm: @verbatim KEKenv, KEKauth = HKDF-Extract(Streebog-512, @@ -129,20 +129,20 @@ KExp15(KEKenc, KEKauth, IV, CEK): @node pki-encrypted-sntrup4591761-x25519-hkdf-blake2b @subsection pki-encrypted with SNTRUP4591761+x25519+HKDF-BLAKE2b KEM -@code{/kem/*/a} equals to "sntrup4591761-x25519-hkdf-blake2b". -Recipient certificate with -@ref{cer-sntrup4591761-x25519, @code{sntrup4591761-x25519}} public key -must be used. It should have "kem" key usage set. + @code{/kem/*/a} equals to "sntrup4591761-x25519-hkdf-blake2b". + Recipient certificate with + @ref{cer-sntrup4591761-x25519, @code{sntrup4591761-x25519}} public key + must be used. It should have "kem" key usage set. -Recipient map must also contain additional field: -@code{/kem/*/encap: bytes} -- concatenation of 1047 bytes of Streamlined -NTRU Prime 4591^761's ciphertext with 32 bytes of ephemeral -Curve25519 public key. + Recipient map must also contain additional field: + @code{/kem/*/encap: bytes} -- concatenation of 1047 bytes of Streamlined + NTRU Prime 4591^761's ciphertext with 32 bytes of ephemeral + Curve25519 public key. -Recipient performs Curve25519 and SNTRUP computation to -derive/decapsulate two 32-byte shared keys. Then it combines -them to get the decryption key of the CEK. -@code{/kem/*/cek} is encrypted the following way: + Recipient performs Curve25519 and SNTRUP computation to + derive/decapsulate two 32-byte shared keys. Then it combines + them to get the decryption key of the CEK. + @code{/kem/*/cek} is encrypted the following way: @verbatim KEK = HKDF-Extract(BLAKE2b-256, @@ -156,3 +156,34 @@ KEK = HKDF-Extract(BLAKE2b-256, x25519-shared-key) ChaCha20-Poly1305(data=16*0x00 || CEK, key=KEK, nonce=12*0x00, ad="") @end verbatim + +@node pki-encrypted-mceliece6960119-x25519-hkdf-shake256 +@subsection pki-encrypted with Classic McEliece 6960119+x25519+HKDF-SHAKE256 KEM + + @code{/kem/*/a} equals to "mceliece6960119-x25519-hkdf-shake256". + Recipient certificate with + @ref{cer-mceliece6960119-x25519, @code{mceliece6960119-x25519}} public key + must be used. It should have "kem" key usage set. + + Recipient map must also contain additional field: + @code{/kem/*/encap: bytes} -- concatenation of 194 bytes of + Classic McEliece 6960119 ciphertext with 32 bytes of ephemeral + Curve25519 public key. + + Recipient performs Curve25519 and Classic McEliece computation to + derive/decapsulate two 32-byte shared keys. Then it combines + them to get the decryption key of the CEK. + @code{/kem/*/cek} is encrypted the following way: + +@verbatim +KEK = HKDF-Extract(SHAKE256, + salt="keks/pki/encrypted/mceliece6960119-x25519-hkdf-shake256", + secret=bind || + mceliece6960119-sender-ciphertext || + x25519-sender-public-key || + mceliece6960119-recipient-public-key || + x25519-recipient-public-key || + mceliece6960119-shared-key || + x25519-shared-key)[:32] +ChaCha20-Poly1305(data=16*0x00 || CEK, key=KEK, nonce=12*0x00, ad="") +@end verbatim diff --git a/spec/format/hashed.texi b/spec/format/hashed.texi index ab81187..1a616d1 100644 --- a/spec/format/hashed.texi +++ b/spec/format/hashed.texi @@ -22,7 +22,7 @@ algorithms. @node Merkle hashing @cindex Merkle tree @cindex Merkle hashing -@section Merkle-tree based hashing +@subsection Merkle-tree based hashing Merkle trees are very convenient way to parallelise data hashing. @url{https://datatracker.ietf.org/doc/html/rfc9162, RFC 9162} is used as diff --git a/spec/format/private-key.texi b/spec/format/private-key.texi index d41ce92..80c6ddc 100644 --- a/spec/format/private-key.texi +++ b/spec/format/private-key.texi @@ -9,25 +9,33 @@ Stored in a file, it should begin with "pki/prvkey" @ref{Magic, magic}. @node private-key-gost3410 @subsection private-key with GOST R 34.10-2012 -Big-endian private key representation must be used. + Big-endian private key representation must be used. -Following algorithm identifiers are used: -@code{gost3410-256A}, @code{gost3410-512C}. + Following algorithm identifiers are used: + @code{gost3410-256A}, @code{gost3410-512C}. @node private-key-ed25519-blake2b @subsection private-key with Ed25519-BLAKE2b -32-byte Ed25519 private key is used, as described in -@url{https://datatracker.ietf.org/doc/html/rfc8032, EdDSA} RFC. -In many libraries it is called "seed". + 32-byte Ed25519 private key is used, as described in + @url{https://datatracker.ietf.org/doc/html/rfc8032, EdDSA} RFC. + In many libraries it is called "seed". -@code{ed25519-blake2b} algorithm identifier is used, however actually no -hash is involved in private key storage. + @code{ed25519-blake2b} algorithm identifier is used, however actually no + hash is involved in private key storage. @node private-key-sntrup4591761-x25519 -@subsection private-key with SNTRUP4591761-X25519 +@subsection private-key with SNTRUP4591761+X25519 -Concatenation of Streamlined NTRU Prime 4591^761's 1600-byte private key -and Curve25519's 32-byte one. + Concatenation of Streamlined NTRU Prime 4591^761's 1600-byte private key + and Curve25519's 32-byte one. -@code{sntrup4591761-x25519} algorithm identifier is used. + @code{sntrup4591761-x25519} algorithm identifier is used. + +@node private-key-mceliece6960119-x25519 +@subsection private-key with Classic McEliece 6960119+X25519 + + Concatenation of Classic McEliece 6960119 13948-byte private key + and Curve25519's 32-byte one. + + @code{mceliece6960119-x25519} algorithm identifier is used. diff --git a/spec/format/registry.texi b/spec/format/registry.texi index 66f793b..9b9d337 100644 --- a/spec/format/registry.texi +++ b/spec/format/registry.texi @@ -64,6 +64,8 @@ There is example registry of known algorithm identifiers. @code{@ref{pki-encrypted-balloon-blake2b-hkdf}} @item gost3410-hkdf-kexp15 @code{@ref{pki-encrypted-gost3410-hkdf-kexp15}} +@item mceliece6960119-x25519-hkdf-shake256 + @code{@ref{pki-encrypted-mceliece6960119-x25519-hkdf-shake256}} @item mlkem768-x25519 @item sntrup761-x25519 @item sntrup4591761-x25519 -- 2.48.1