}
// Write an encoded BLOB atom.
+func BlobAtomEncode(w io.Writer, chunkLen int64) (written int64, err error) {
+ l := make([]byte, 9)
+ l[0] = byte(AtomBLOB)
+ be.Put(l[1:], uint64(chunkLen-1))
+ return io.Copy(w, bytes.NewReader(l))
+}
+
+// Write an encoded BLOB.
func BlobEncode(w io.Writer, chunkLen int64, r io.Reader) (written int64, err error) {
if chunkLen == 0 {
return 0, errors.New("zero chunkLen specified")
}
- {
- l := make([]byte, 9)
- l[0] = byte(AtomBLOB)
- be.Put(l[1:], uint64(chunkLen-1))
- written, err = io.Copy(w, bytes.NewReader(l))
- }
+ written, err = BlobAtomEncode(w, chunkLen)
if err != nil {
return
}
d.d.deTail()
d.d.strs = d.d.strs[:len(d.d.strs)-1]
chunk = unsafe.Slice(unsafe.StringData(s), len(s))
+ d.d.readBuf = d.d.readBuf[:max(d.ChunkLen, 8)]
if int64(len(s)) == d.ChunkLen {
return chunk, nil
}
+++ /dev/null
-// enctool -- dealing with KEKS-encoded cm-encrypted utility
-// 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 main
-
-import (
- "bufio"
- "crypto/subtle"
- "errors"
- "io"
- "os"
-
- "go.cypherpunks.su/keks"
- "golang.org/x/crypto/chacha20poly1305"
-)
-
-const ChaPolyChunkLen = 128 * 1024
-
-var ChaPolyPad = make([]byte, 32)
-
-func incr(data []byte) {
- for i := len(data) - 1; i >= 0; i-- {
- data[i]++
- if data[i] != 0 {
- return
- }
- }
-}
-
-func demChaPolySeal(cek []byte) error {
- if len(cek) != chacha20poly1305.KeySize+chacha20poly1305.NonceSize {
- return errors.New("wrong CEK len")
- }
- key, iv := cek[:chacha20poly1305.KeySize], cek[chacha20poly1305.KeySize:]
- ciph, err := chacha20poly1305.New(key)
- if err != nil {
- return err
- }
- in := make([]byte, len(ChaPolyPad)+ChaPolyChunkLen)
- out := make([]byte, len(ChaPolyPad)+ChaPolyChunkLen+ciph.Overhead())
- br := bufio.NewReaderSize(os.Stdin, ChaPolyChunkLen)
- bw := bufio.NewWriterSize(os.Stdout, len(out))
- pr, pw := io.Pipe()
- blobErr := make(chan error)
- go func() {
- _, e := keks.BlobEncode(bw, int64(len(out)), pr)
- blobErr <- e
- }()
- ctr := make([]byte, ciph.NonceSize())
- nonce := make([]byte, ciph.NonceSize())
- var n int
- var eof bool
- for !eof {
- n, err = io.ReadFull(br, in[len(ChaPolyPad):])
- if err != nil {
- if err != io.ErrUnexpectedEOF {
- return err
- }
- eof = true
- }
- incr(ctr[:len(ctr)-1])
- if n != ChaPolyChunkLen {
- ctr[len(ctr)-1] = 0x01
- }
- subtle.XORBytes(nonce, ctr, iv)
- _, err = pw.Write(ciph.Seal(out[:0], nonce, in[:len(ChaPolyPad)+n], nil))
- if err != nil {
- return err
- }
- }
- if err = pw.Close(); err != nil {
- return err
- }
- if err = <-blobErr; err != nil {
- return err
- }
- return bw.Flush()
-}
-
-func demChaPolyOpen(cek []byte) error {
- if len(cek) != chacha20poly1305.KeySize+chacha20poly1305.NonceSize {
- return errors.New("wrong CEK len")
- }
- key, iv := cek[:chacha20poly1305.KeySize], cek[chacha20poly1305.KeySize:]
- ciph, err := chacha20poly1305.New(key)
- if err != nil {
- return err
- }
- in := make([]byte, len(ChaPolyPad)+ChaPolyChunkLen+ciph.Overhead())
- pr, pw := io.Pipe()
- blobErr := make(chan error)
- go func() {
- d, e := keks.NewBlobDecoder(
- bufio.NewReaderSize(os.Stdin, len(in)),
- int64(len(in)),
- )
- defer func() {
- pw.Close()
- blobErr <- e
- }()
- if e != nil {
- return
- }
- var chunk []byte
- for {
- chunk, e = d.Next()
- if e != nil {
- if e == io.EOF {
- e = nil
- break
- }
- return
- }
- if _, e = pw.Write(chunk); e != nil {
- return
- }
- }
- }()
- out := make([]byte, len(ChaPolyPad)+ChaPolyChunkLen)
- var n int
- var eof bool
- var chunk []byte
- bw := bufio.NewWriterSize(os.Stdout, ChaPolyChunkLen)
- ctr := make([]byte, ciph.NonceSize())
- nonce := make([]byte, ciph.NonceSize())
- for !eof {
- n, err = io.ReadFull(pr, in)
- if err != nil {
- if err != io.ErrUnexpectedEOF {
- return err
- }
- eof = true
- }
- incr(ctr[:len(ctr)-1])
- if n != len(in) {
- ctr[len(ctr)-1] = 0x01
- }
- subtle.XORBytes(nonce, ctr, iv)
- chunk, err = ciph.Open(out[:0], nonce, in[:n], nil)
- if err != nil {
- return err
- }
- if subtle.ConstantTimeCompare(chunk[:len(ChaPolyPad)], ChaPolyPad) != 1 {
- return errors.New("bad pad")
- }
- if _, err = bw.Write(chunk[len(ChaPolyPad):]); err != nil {
- return err
- }
- }
- if err = <-blobErr; err != nil {
- return err
- }
- return bw.Flush()
-}
-
-func kemChaPolySeal(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 kemChaPolyOpen(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
-}
"io"
"log"
"os"
+ "runtime"
"github.com/companyzero/sntrup4591761"
"github.com/google/uuid"
"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"
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"
balloonT := flag.Int("balloon-t", 3, "Balloon's time cost")
balloonP := flag.Int("balloon-p", 2, "Balloon's number of threads")
doDecrypt := flag.Bool("d", false, "Decrypt")
+ parallel := flag.Int("parallel", runtime.NumCPU(), "Parallel cryptors")
var pubs []*sign.Pub
flag.Func("pub", "Path to public key to encrypt to", func(v string) error {
signed, err := sign.PubParse(mustReadFile(v))
log.Fatal(err)
}
var cekp []byte
- cekp, err = kemChaPolyOpen(
+ cekp, err = chaPolyKEM.Open(
kek, kem.CEK,
chacha20poly1305.KeySize+chacha20poly1305.NonceSize,
)
log.Fatal(err)
}
var cekp []byte
- cekp, err = kemChaPolyOpen(
+ cekp, err = chaPolyKEM.Open(
kek, kem.CEK,
chacha20poly1305.KeySize+chacha20poly1305.NonceSize,
)
log.Fatal(err)
}
var cekp []byte
- cekp, err = kemChaPolyOpen(
+ cekp, err = chaPolyKEM.Open(
kek[:chacha20poly1305.KeySize],
kem.CEK,
chacha20poly1305.KeySize+chacha20poly1305.NonceSize,
if cek == nil {
log.Fatal("no KEMs processed")
}
- err = demChaPolyOpen(cek)
+ _, err = chaPolyDEM.Open(os.Stdout, os.Stdin, cek, *parallel)
if err != nil {
log.Fatal(err)
}
if err != nil {
log.Fatal(err)
}
- kem.CEK, err = kemChaPolySeal(kek, cek)
+ kem.CEK, err = chaPolyKEM.Seal(kek, cek)
if err != nil {
log.Fatal(err)
}
if err != nil {
log.Fatal(err)
}
- kem.CEK, err = kemChaPolySeal(kek, cek)
+ kem.CEK, err = chaPolyKEM.Seal(kek, cek)
if err != nil {
log.Fatal(err)
}
if err != nil {
log.Fatal(err)
}
- kem.CEK, err = kemChaPolySeal(kek[:chacha20poly1305.KeySize], cek)
+ kem.CEK, err = chaPolyKEM.Seal(
+ kek[:chacha20poly1305.KeySize], cek,
+ )
if err != nil {
log.Fatal(err)
}
log.Fatal(err)
}
}
- if err = demChaPolySeal(cek); err != nil {
+ if _, err = chaPolyDEM.Seal(os.Stdout, os.Stdin, cek, *parallel); err != nil {
log.Fatal(err)
}
}
--- /dev/null
+// enctool -- dealing with KEKS-encoded cm-encrypted utility
+// 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 dem
+
+import (
+ "bytes"
+ "crypto/cipher"
+ "crypto/subtle"
+ "errors"
+ "io"
+
+ "go.cypherpunks.su/keks"
+ "golang.org/x/crypto/chacha20poly1305"
+)
+
+const (
+ ChaPolyChunkLen = 128 * 1024
+ ChaPolyPadLen = 32
+)
+
+func incr(data []byte) {
+ for i := len(data) - 1; i >= 0; i-- {
+ data[i]++
+ if data[i] != 0 {
+ return
+ }
+ }
+}
+
+type job struct {
+ bufReady chan struct{}
+ processed chan struct{}
+ buf []byte
+ ctr []byte
+}
+
+func do(
+ w io.Writer,
+ seal bool,
+ r io.Reader,
+ cek []byte,
+ procs int,
+) (total int64, err error) {
+ if len(cek) != 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 ctr []byte
+ var overhead int
+ {
+ var ciph cipher.AEAD
+ ciph, err = chacha20poly1305.New(key)
+ if err != nil {
+ return 0, err
+ }
+ ctr = make([]byte, ciph.NonceSize())
+ overhead = ciph.Overhead()
+ }
+ chunkLen := ChaPolyPadLen + ChaPolyChunkLen + overhead
+ var blobDecoder *keks.BlobDecoder
+ if seal {
+ total, err = keks.BlobAtomEncode(w, int64(chunkLen))
+ } else {
+ blobDecoder, err = keks.NewBlobDecoder(r, int64(chunkLen))
+ }
+ if err != nil {
+ return
+ }
+ chaPolyPad := make([]byte, ChaPolyPadLen)
+ var errUnauth error
+ for range procs {
+ go func() {
+ var ciph cipher.AEAD
+ ciph, err = chacha20poly1305.New(key)
+ if err != nil {
+ panic(err)
+ }
+ j := job{
+ buf: make([]byte, chunkLen),
+ ctr: make([]byte, ciph.NonceSize()),
+ bufReady: make(chan struct{}),
+ processed: make(chan struct{}),
+ }
+ nonce := make([]byte, ciph.NonceSize())
+ ready <- &j
+ 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 {
+ errUnauth = errors.New("bad pad")
+ }
+ } else {
+ errUnauth = errOpen
+ }
+ }
+ j.processed <- struct{}{}
+ }
+ }()
+ }
+ finished := make(chan struct{})
+ var errW error
+ go func() {
+ var n int64
+ for j := range dones {
+ <-j.processed
+ if seal {
+ n, errW = keks.BinEncode(w, j.buf)
+ } else {
+ if len(j.buf) == 0 {
+ n = 0
+ } else {
+ n, errW = io.Copy(w, bytes.NewReader(j.buf[ChaPolyPadLen:]))
+ }
+ }
+ total += n
+ if errW != nil {
+ break
+ }
+ j.buf = j.buf[:chunkLen]
+ ready <- j
+ }
+ close(finished)
+ }()
+ var chunk []byte
+ var errR error
+ var eof bool
+ var n int
+ var j *job
+ for !eof {
+ if errUnauth != nil {
+ err = errUnauth
+ close(dones)
+ <-finished
+ return
+ }
+ j = <-ready
+ if seal {
+ n, errR = io.ReadFull(r, j.buf[ChaPolyPadLen:ChaPolyPadLen+ChaPolyChunkLen])
+ } else {
+ chunk, errR = blobDecoder.Next()
+ copy(j.buf, chunk)
+ n = len(chunk)
+ j.buf = j.buf[:n]
+ }
+ total += int64(n)
+ if errR != nil {
+ if !(errR == io.ErrUnexpectedEOF || errR == io.EOF) {
+ err = errR
+ close(dones)
+ <-finished
+ return
+ }
+ errR = nil
+ eof = true
+ if seal {
+ j.buf = j.buf[:ChaPolyPadLen+n+overhead]
+ }
+ }
+ if seal {
+ clear(j.buf[:ChaPolyPadLen])
+ } else if n == 0 {
+ break
+ }
+ if (seal && n < ChaPolyChunkLen) || (!seal && n < chunkLen) {
+ ctr[len(ctr)-1] = 0x01
+ }
+ incr(ctr[:len(ctr)-1])
+ copy(j.ctr, ctr)
+ j.bufReady <- struct{}{}
+ dones <- j
+ }
+ close(dones)
+ <-finished
+ if errUnauth == nil {
+ err = errW
+ } else {
+ err = errUnauth
+ }
+ 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 Open(w io.Writer, r io.Reader, cek []byte, procs int) (total int64, err error) {
+ return do(w, false, r, cek, 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
+}