From 4195b339e308e8e17d39d5d2fb027cd10f4f0500c26ec0082f719b2757f293e0 Mon Sep 17 00:00:00 2001 From: Sergey Matveev Date: Sat, 15 Feb 2025 11:13:00 +0300 Subject: [PATCH] Do not differentiate KEM and DEM ChaPoly usage --- go/cm/cmd/enctool/main.go | 76 ++++++++++++++++-------------- go/cm/enc/chapoly/{dem => }/dem.go | 56 +++++++++++----------- go/cm/enc/chapoly/kem/kem.go | 48 ------------------- spec/cm/encrypted.texi | 23 +++++---- 4 files changed, 81 insertions(+), 122 deletions(-) rename go/cm/enc/chapoly/{dem => }/dem.go (72%) delete mode 100644 go/cm/enc/chapoly/kem/kem.go diff --git a/go/cm/cmd/enctool/main.go b/go/cm/cmd/enctool/main.go index c067f9c..3c1aaa2 100644 --- a/go/cm/cmd/enctool/main.go +++ b/go/cm/cmd/enctool/main.go @@ -33,14 +33,12 @@ import ( "github.com/google/uuid" "go.cypherpunks.su/balloon/v3" "golang.org/x/crypto/blake2b" - "golang.org/x/crypto/chacha20poly1305" "golang.org/x/term" "go.cypherpunks.su/keks" "go.cypherpunks.su/keks/cm" cmenc "go.cypherpunks.su/keks/cm/enc" - chaPolyDEM "go.cypherpunks.su/keks/cm/enc/chapoly/dem" - chaPolyKEM "go.cypherpunks.su/keks/cm/enc/chapoly/kem" + chaPoly "go.cypherpunks.su/keks/cm/enc/chapoly" mceliece6960119x25519 "go.cypherpunks.su/keks/cm/enc/mceliece6960119-x25519" mceliece6960119 "go.cypherpunks.su/keks/cm/enc/mceliece6960119-x25519/mceliece6960119" sntrup4591761x25519 "go.cypherpunks.su/keks/cm/enc/sntrup4591761-x25519" @@ -192,21 +190,22 @@ func main() { int(kem.Cost.S), int(kem.Cost.T), int(kem.Cost.P), ), cmenc.BalloonHKDFInfo, - chacha20poly1305.KeySize, + chaPoly.KeyLen, ) if err != nil { log.Fatal(err) } - var cekp []byte - cekp, err = chaPolyKEM.Open( - kek, kem.CEK, - chacha20poly1305.KeySize+chacha20poly1305.NonceSize, - ) + var cekp bytes.Buffer + _, err = chaPoly.Open(&cekp, bytes.NewReader(kem.CEK), kek, 1) if err != nil { log.Println(kemIdx, kem.A, err, ", skipping") continue } - cek = cekp + if cekp.Len() != chaPoly.KeyLen { + log.Println(kemIdx, kem.A, "wrong key len, skipping") + continue + } + cek = cekp.Bytes() } case sntrup4591761x25519.SNTRUP4591761X25519HKDFBLAKE2b: if len(prvs) == 0 { @@ -274,21 +273,22 @@ func main() { blake2bHash, prk, cmenc.SNTRUP4591761X25519Info, - chacha20poly1305.KeySize, + chaPoly.KeyLen, ) if err != nil { log.Fatal(err) } - var cekp []byte - cekp, err = chaPolyKEM.Open( - kek, kem.CEK, - chacha20poly1305.KeySize+chacha20poly1305.NonceSize, - ) + var cekp bytes.Buffer + _, err = chaPoly.Open(&cekp, bytes.NewReader(kem.CEK), kek, 1) if err != nil { log.Println(kemIdx, kem.A, err, ", skipping") continue } - cek = cekp + if cekp.Len() != chaPoly.KeyLen { + log.Println(kemIdx, kem.A, "wrong key len, skipping") + continue + } + cek = cekp.Bytes() break } } @@ -367,22 +367,22 @@ func main() { cmhash.NewSHAKE256, prk, cmenc.ClassicMcEliece6960119X25519Info, - chacha20poly1305.KeySize, + chaPoly.KeyLen, ) if err != nil { log.Fatal(err) } - var cekp []byte - cekp, err = chaPolyKEM.Open( - kek[:chacha20poly1305.KeySize], - kem.CEK, - chacha20poly1305.KeySize+chacha20poly1305.NonceSize, - ) + var cekp bytes.Buffer + _, err = chaPoly.Open(&cekp, bytes.NewReader(kem.CEK), kek, 1) if err != nil { log.Println(kemIdx, kem.A, err, ", skipping") continue } - cek = cekp + if cekp.Len() != chaPoly.KeyLen { + log.Println(kemIdx, kem.A, "wrong key len, skipping") + continue + } + cek = cekp.Bytes() break } } @@ -397,7 +397,7 @@ func main() { if cek == nil { log.Fatal("no KEMs processed") } - _, err = chaPolyDEM.Open(os.Stdout, os.Stdin, cek, *parallel) + _, err = chaPoly.Open(os.Stdout, os.Stdin, cek, *parallel) if err != nil { log.Fatal(err) } @@ -412,7 +412,7 @@ func main() { log.Fatal(err) } var kems []cmenc.KEM - cek = make([]byte, chacha20poly1305.KeySize+chacha20poly1305.NonceSize) + cek = make([]byte, chaPoly.KeyLen) rand.Read(cek) if *passphrase { passwd := readPasswd("Passphrase:") @@ -443,15 +443,17 @@ func main() { *balloonS, *balloonT, *balloonP, ), cmenc.BalloonHKDFInfo, - chacha20poly1305.KeySize, + chaPoly.KeyLen, ) if err != nil { log.Fatal(err) } - kem.CEK, err = chaPolyKEM.Seal(kek, cek) + var cekp bytes.Buffer + _, err = chaPoly.Seal(&cekp, bytes.NewReader(cek), kek, 1) if err != nil { log.Fatal(err) } + kem.CEK = cekp.Bytes() } kems = append(kems, kem) } @@ -508,15 +510,17 @@ func main() { blake2bHash, prk, cmenc.SNTRUP4591761X25519Info, - chacha20poly1305.KeySize, + chaPoly.KeyLen, ) if err != nil { log.Fatal(err) } - kem.CEK, err = chaPolyKEM.Seal(kek, cek) + var cekp bytes.Buffer + _, err = chaPoly.Seal(&cekp, bytes.NewReader(cek), kek, 1) if err != nil { log.Fatal(err) } + kem.CEK = cekp.Bytes() } if *includeTo { kem.To = &pub.Id @@ -574,17 +578,17 @@ func main() { cmhash.NewSHAKE256, prk, cmenc.ClassicMcEliece6960119X25519Info, - chacha20poly1305.KeySize, + chaPoly.KeyLen, ) if err != nil { log.Fatal(err) } - kem.CEK, err = chaPolyKEM.Seal( - kek[:chacha20poly1305.KeySize], cek, - ) + var cekp bytes.Buffer + _, err = chaPoly.Seal(&cekp, bytes.NewReader(cek), kek, 1) if err != nil { log.Fatal(err) } + kem.CEK = cekp.Bytes() } if *includeTo { kem.To = &pub.Id @@ -613,7 +617,7 @@ func main() { log.Fatal(err) } } - if _, err = chaPolyDEM.Seal(os.Stdout, os.Stdin, cek, *parallel); err != nil { + if _, err = chaPoly.Seal(os.Stdout, os.Stdin, cek, *parallel); err != nil { log.Fatal(err) } } diff --git a/go/cm/enc/chapoly/dem/dem.go b/go/cm/enc/chapoly/dem.go similarity index 72% rename from go/cm/enc/chapoly/dem/dem.go rename to go/cm/enc/chapoly/dem.go index c8a9033..c2ec534 100644 --- a/go/cm/enc/chapoly/dem/dem.go +++ b/go/cm/enc/chapoly/dem.go @@ -1,4 +1,4 @@ -// enctool -- dealing with KEKS-encoded cm-encrypted utility +// GoKEKS/CM -- KEKS-encoded cryptographic messages // Copyright (C) 2024-2025 Sergey Matveev // // This program is free software: you can redistribute it and/or modify @@ -27,8 +27,9 @@ import ( ) const ( - ChaPolyChunkLen = 128 * 1024 - ChaPolyPadLen = 32 + ChunkLen = 128 * 1024 + PadLen = 32 + KeyLen = chacha20poly1305.KeySize + chacha20poly1305.NonceSize ) func incr(data []byte) { @@ -44,22 +45,23 @@ type job struct { bufReady chan struct{} processed chan struct{} buf []byte - ctr []byte + nonce []byte } func do( w io.Writer, seal bool, r io.Reader, - cek []byte, + key []byte, procs int, ) (total int64, err error) { - if len(cek) != chacha20poly1305.KeySize+chacha20poly1305.NonceSize { + if len(key) != chacha20poly1305.KeySize+chacha20poly1305.NonceSize { return 0, errors.New("wrong CEK len") } ready := make(chan *job, procs) dones := make(chan *job, procs) - key, iv := cek[:chacha20poly1305.KeySize], cek[chacha20poly1305.KeySize:] + var iv []byte + key, iv = key[:chacha20poly1305.KeySize], key[chacha20poly1305.KeySize:] var ctr []byte var overhead int { @@ -71,17 +73,17 @@ func do( ctr = make([]byte, ciph.NonceSize()) overhead = ciph.Overhead() } - chunkLen := ChaPolyPadLen + ChaPolyChunkLen + overhead + blobChunkLen := PadLen + ChunkLen + overhead var blobDecoder *keks.BlobDecoder if seal { - total, err = keks.BlobAtomEncode(w, int64(chunkLen)) + total, err = keks.BlobAtomEncode(w, int64(blobChunkLen)) } else { - blobDecoder, err = keks.NewBlobDecoder(r, int64(chunkLen)) + blobDecoder, err = keks.NewBlobDecoder(r, int64(blobChunkLen)) } if err != nil { return } - chaPolyPad := make([]byte, ChaPolyPadLen) + chaPolyPad := make([]byte, PadLen) var errUnauth error for range procs { go func() { @@ -91,8 +93,8 @@ func do( panic(err) } j := job{ - buf: make([]byte, chunkLen), - ctr: make([]byte, ciph.NonceSize()), + buf: make([]byte, blobChunkLen), + nonce: make([]byte, ciph.NonceSize()), bufReady: make(chan struct{}), processed: make(chan struct{}), } @@ -101,16 +103,12 @@ func do( var errOpen error for { <-j.bufReady - subtle.XORBytes(nonce, j.ctr, iv) if seal { ciph.Seal(j.buf[:0], nonce, j.buf[:len(j.buf)-overhead], nil) } else { j.buf, errOpen = ciph.Open(j.buf[:0], nonce, j.buf, nil) if errOpen == nil { - if subtle.ConstantTimeCompare( - j.buf[:ChaPolyPadLen], - chaPolyPad, - ) != 1 { + if subtle.ConstantTimeCompare(j.buf[:PadLen], chaPolyPad) != 1 { errUnauth = errors.New("bad pad") } } else { @@ -133,14 +131,14 @@ func do( if len(j.buf) == 0 { n = 0 } else { - n, errW = io.Copy(w, bytes.NewReader(j.buf[ChaPolyPadLen:])) + n, errW = io.Copy(w, bytes.NewReader(j.buf[PadLen:])) } } total += n if errW != nil { break } - j.buf = j.buf[:chunkLen] + j.buf = j.buf[:blobChunkLen] ready <- j } close(finished) @@ -159,7 +157,7 @@ func do( } j = <-ready if seal { - n, errR = io.ReadFull(r, j.buf[ChaPolyPadLen:ChaPolyPadLen+ChaPolyChunkLen]) + n, errR = io.ReadFull(r, j.buf[PadLen:PadLen+ChunkLen]) } else { chunk, errR = blobDecoder.Next() copy(j.buf, chunk) @@ -177,19 +175,19 @@ func do( errR = nil eof = true if seal { - j.buf = j.buf[:ChaPolyPadLen+n+overhead] + j.buf = j.buf[:PadLen+n+overhead] } } if seal { - clear(j.buf[:ChaPolyPadLen]) + clear(j.buf[:PadLen]) } else if n == 0 { break } - if (seal && n < ChaPolyChunkLen) || (!seal && n < chunkLen) { + if (seal && n < ChunkLen) || (!seal && n < blobChunkLen) { ctr[len(ctr)-1] = 0x01 } + subtle.XORBytes(j.nonce, ctr, iv) incr(ctr[:len(ctr)-1]) - copy(j.ctr, ctr) j.bufReady <- struct{}{} dones <- j } @@ -203,10 +201,10 @@ func do( return } -func Seal(w io.Writer, r io.Reader, cek []byte, procs int) (total int64, err error) { - return do(w, true, r, cek, procs) +func Seal(w io.Writer, r io.Reader, key []byte, procs int) (total int64, err error) { + return do(w, true, r, key, procs) } -func Open(w io.Writer, r io.Reader, cek []byte, procs int) (total int64, err error) { - return do(w, false, r, cek, procs) +func Open(w io.Writer, r io.Reader, key []byte, procs int) (total int64, err error) { + return do(w, false, r, key, procs) } diff --git a/go/cm/enc/chapoly/kem/kem.go b/go/cm/enc/chapoly/kem/kem.go deleted file mode 100644 index c400ba6..0000000 --- a/go/cm/enc/chapoly/kem/kem.go +++ /dev/null @@ -1,48 +0,0 @@ -// GoKEKS/CM -- KEKS-encoded cryptographic messages -// 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 kem - -import ( - "errors" - - "golang.org/x/crypto/chacha20poly1305" -) - -func Seal(kek, cek []byte) ([]byte, error) { - ciph, err := chacha20poly1305.New(kek) - if err != nil { - return nil, err - } - nonce := make([]byte, ciph.NonceSize()) - return ciph.Seal(nil, nonce, cek, nil), nil -} - -func Open(kek, ciphertext []byte, cekLenExpected int) ([]byte, error) { - ciph, err := chacha20poly1305.New(kek) - if err != nil { - return nil, err - } - nonce := make([]byte, ciph.NonceSize()) - var cek []byte - cek, err = ciph.Open(nil, nonce, ciphertext, nil) - if err != nil { - return nil, err - } - if len(cek) != cekLenExpected { - return nil, errors.New("invalid CEK len") - } - return cek, nil -} diff --git a/spec/cm/encrypted.texi b/spec/cm/encrypted.texi index cd2d056..c32f5c6 100644 --- a/spec/cm/encrypted.texi +++ b/spec/cm/encrypted.texi @@ -52,7 +52,7 @@ ChaCha20-Poly1305(key=KEY, ad="", @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. + Last chunk should be smaller than previous ones, maybe (payload) even empty. @code{/ciphertext}'s chunk length equals to 32+128KiB+16 bytes. @@ -97,13 +97,14 @@ Kenc || IV || Kauth = CEK @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 with + @ref{cm-encrypted-chacha20poly1305} algorithm, where counter is + zero, tail-flag is set and CEK is KEK: @verbatim KEK = HKDF-Expand(BLAKE2b, prk=balloon(BLAKE2b, passphrase, /salt || /kem/salt, s, t, p), info="keks/cm/encrypted/balloon-blake2b-hkdf") -ChaCha20-Poly1305(data=CEK, key=KEK, nonce=12*0x00, ad="") @end verbatim @node cm-encrypted-gost3410-hkdf-kexp15 @@ -150,8 +151,7 @@ KExp15(KEKenc, KEKauth, IV, CEK) = CTR(Kenc, CEK || CMAC(Kauth, IV || CEK), IV=I 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: + them to get the KEK decryption key of the CEK. @verbatim PRK = HKDF-Extract(BLAKE2b, salt=/salt, @@ -164,9 +164,12 @@ PRK = HKDF-Extract(BLAKE2b, salt=/salt, x25519-shared-key) KEK = HKDF-Expand(BLAKE2b, prk=PRK, info="keks/cm/encrypted/sntrup4591761-x25519-hkdf-blake2b") -ChaCha20-Poly1305(data=CEK, key=KEK, nonce=12*0x00, ad="") @end verbatim + @code{/kem/*/cek} is encrypted with + @ref{cm-encrypted-chacha20poly1305} algorithm, where counter is + zero, tail-flag is set and CEK is KEK. + @node cm-encrypted-mceliece6960119-x25519-hkdf-shake256 @cindex cm-encrypted-mceliece6960119-x25519-hkdf-shake256 @nodedescription cm/encrypted with Classic McEliece 6960-119+Curve25519+HKDF-SHAKE256 KEM @@ -184,8 +187,7 @@ ChaCha20-Poly1305(data=CEK, key=KEK, nonce=12*0x00, ad="") 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: + them to get the KEK decryption key of the CEK. @verbatim PRK = HKDF-Extract(SHAKE256, salt=/salt, @@ -198,5 +200,8 @@ PRK = HKDF-Extract(SHAKE256, salt=/salt, x25519-shared-key)[:32] KEK = HKDF-Expand(SHAKE256, prk=PRK, info="keks/cm/encrypted/mceliece6960119-x25519-hkdf-shake256") -ChaCha20-Poly1305(data=CEK, key=KEK, nonce=12*0x00, ad="") @end verbatim + + @code{/kem/*/cek} is encrypted with + @ref{cm-encrypted-chacha20poly1305} algorithm, where counter is + zero, tail-flag is set and CEK is KEK. -- 2.48.1