"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"
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 {
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
}
}
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
}
}
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)
}
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:")
*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)
}
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
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
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)
}
}
-// enctool -- dealing with KEKS-encoded cm-encrypted utility
+// GoKEKS/CM -- KEKS-encoded cryptographic messages
// Copyright (C) 2024-2025 Sergey Matveev <stargrave@stargrave.org>
//
// This program is free software: you can redistribute it and/or modify
)
const (
- ChaPolyChunkLen = 128 * 1024
- ChaPolyPadLen = 32
+ ChunkLen = 128 * 1024
+ PadLen = 32
+ KeyLen = chacha20poly1305.KeySize + chacha20poly1305.NonceSize
)
func incr(data []byte) {
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
{
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() {
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{}),
}
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 {
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)
}
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)
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
}
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)
}
+++ /dev/null
-// GoKEKS/CM -- KEKS-encoded cryptographic messages
-// Copyright (C) 2024-2025 Sergey Matveev <stargrave@stargrave.org>
-//
-// 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 <http://www.gnu.org/licenses/>.
-
-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
-}
@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.
@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
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,
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
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,
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.