static char *ColourWhite = "\x1b[37m";
static char *ColourReset = "\x1b[0m";
-static size_t MaxStrLen = 40;
+static size_t MaxStrLen = 64;
static bool NoOffsets = false;
static int OffDigits = 0;
static char OffFmt[16] = {0};
"log"
"os"
"runtime"
+ "strconv"
"github.com/companyzero/sntrup4591761"
"github.com/google/uuid"
"go.cypherpunks.su/keks"
"go.cypherpunks.su/keks/cm"
cmenc "go.cypherpunks.su/keks/cm/enc"
+ cmballoon "go.cypherpunks.su/keks/cm/enc/balloon"
+ ballooncost "go.cypherpunks.su/keks/cm/enc/balloon/cost"
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"
return
}
+func readPrv(pth string) (*cm.AV, error) {
+ data := mustReadFile(pth)
+ var magic keks.Magic
+ magic, data = keks.StripMagic(data)
+ switch magic {
+ case sign.PrvMagic:
+ case cmenc.Magic:
+ var encrypted cmenc.Encrypted
+ {
+ d := keks.NewDecoderFromBytes(data, nil)
+ if err := d.DecodeStruct(&encrypted); err != nil {
+ return nil, err
+ }
+ }
+ if encrypted.DEM.A != cmenc.ChaCha20Poly1305 {
+ return nil, errors.New("unsupported prv encryption DEM")
+ }
+ if len(encrypted.KEM) != 1 ||
+ encrypted.KEM[0].A != cmballoon.BalloonBLAKE2bHKDF ||
+ len(encrypted.Payload) == 0 {
+ return nil, errors.New("wrong prv encryption KEM")
+ }
+ passwd := readPasswd("Passphrase for " + pth + ":")
+ cek, err := cmballoon.Decapsulate(
+ encrypted.KEM[0],
+ encrypted.Salt[:],
+ passwd,
+ )
+ if err != nil {
+ return nil, err
+ }
+ var buf bytes.Buffer
+ _, err = chaPoly.Open(&buf, bytes.NewReader(encrypted.Payload), cek, 1)
+ if err != nil {
+ return nil, err
+ }
+ data = buf.Bytes()
+ magic, data = keks.StripMagic(data)
+ if magic == sign.PrvMagic {
+ break
+ }
+ fallthrough
+ default:
+ return nil, errors.New("wrong magic")
+ }
+ var av cm.AV
+ d := keks.NewDecoderFromBytes(data, &keks.DecodeOpts{MaxStrLen: 1 << 16})
+ return &av, d.DecodeStruct(&av)
+}
+
func main() {
log.SetFlags(log.Lshortfile)
flag.Usage = usage
setSalt := flag.String("salt", "", "Set that /salt instead of autogeneration")
includeTo := flag.Bool("include-to", false, `Include "to" field in KEMs`)
passphrase := flag.Bool("p", false, "Use passphrase")
- balloonS := flag.Int("balloon-s", 1<<16, "Balloon's space cost")
- balloonT := flag.Int("balloon-t", 3, "Balloon's time cost")
+ balloonS := flag.Int("balloon-s", 1<<17, "Balloon's space cost")
+ balloonT := flag.Int("balloon-t", 4, "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")
+ noblob := flag.Bool("no-stream", false, "Include payload into container")
var pubs []*sign.Pub
flag.Func("pub", "Path to public key to encrypt to", func(v string) error {
signed, err := sign.PubParse(mustReadFile(v))
return err
})
var prvs []*cm.AV
- flag.Func("prv", "Our private keys for decryption", func(v string) (err error) {
- magic, data := keks.StripMagic(mustReadFile(v))
- if magic == "" || magic != sign.PrvMagic {
- return errors.New("wrong magic")
+ flag.Func("prv", "Our private keys for decryption", func(v string) error {
+ av, err := readPrv(v)
+ if err == nil {
+ prvs = append(prvs, av)
}
- var av cm.AV
- d := keks.NewDecoderFromBytes(data, &keks.DecodeOpts{MaxStrLen: 1 << 16})
- if err = d.DecodeStruct(&av); err != nil {
- return err
- }
- prvs = append(prvs, &av)
- return nil
+ return err
})
flag.Parse()
}
for kemIdx, kem := range encrypted.KEM {
switch kem.A {
- case cmenc.BalloonBLAKE2bHKDF:
+ case cmballoon.BalloonBLAKE2bHKDF:
if !*passphrase {
log.Println(kemIdx, kem.A, "skipping because no -passwd")
continue
}
- if kem.Salt == nil {
- log.Fatalln("missing salt")
- }
- if kem.Cost == nil {
- log.Fatalln("missing cost")
+ passwd := readPasswd("Passphrase for " + strconv.Itoa(kemIdx) + " KEM:")
+ cek, err = cmballoon.Decapsulate(
+ kem,
+ encrypted.Salt[:],
+ passwd,
+ )
+ if err != nil {
+ log.Print(err)
+ continue
}
- passwd := readPasswd("Passphrase:")
- {
- var kek []byte
- kek, err = hkdf.Expand(
- blake2bHash,
- balloon.H(
- blake2bHash,
- passwd,
- append(encrypted.Salt[:], *kem.Salt...),
- int(kem.Cost.S), int(kem.Cost.T), int(kem.Cost.P),
- ),
- cmenc.BalloonHKDFInfo,
- chaPoly.KeyLen,
- )
- if err != nil {
- log.Fatal(err)
- }
- 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
- }
- if cekp.Len() != chaPoly.KeyLen {
- log.Println(kemIdx, kem.A, "wrong key len, skipping")
- continue
- }
- cek = cekp.Bytes()
+ if len(cek) != chaPoly.KeyLen {
+ log.Println(kemIdx, kem.A, "wrong key len, skipping")
+ continue
}
case sntrup4591761x25519.SNTRUP4591761X25519HKDFBLAKE2b:
if len(prvs) == 0 {
if cek == nil {
log.Fatal("no KEMs processed")
}
- _, err = chaPoly.Open(os.Stdout, os.Stdin, cek, *parallel)
+ if len(encrypted.Payload) > 0 {
+ _, err = chaPoly.Open(os.Stdout, bytes.NewReader(encrypted.Payload), cek, *parallel)
+ } else {
+ _, err = chaPoly.OpenBlob(os.Stdout, os.Stdin, cek, *parallel)
+ }
if err != nil {
log.Fatal(err)
}
log.Fatal("passphrases do not match")
}
}
- bSalt := make([]byte, cmenc.BalloonSaltLen)
+ bSalt := make([]byte, cmballoon.SaltLen)
rand.Read(bSalt)
kem := cmenc.KEM{
- A: cmenc.BalloonBLAKE2bHKDF,
+ A: cmballoon.BalloonBLAKE2bHKDF,
Salt: &bSalt,
- Cost: &cmenc.BalloonCost{
+ BalloonCost: &ballooncost.Cost{
S: uint64(*balloonS),
T: uint64(*balloonT),
P: uint64(*balloonP),
append(salt[:], bSalt...),
*balloonS, *balloonT, *balloonP,
),
- cmenc.BalloonHKDFInfo,
+ cmballoon.HKDFInfo,
chaPoly.KeyLen,
)
if err != nil {
if _, err = keks.Encode(&hdr, cmenc.Magic, nil); err != nil {
log.Fatal(err)
}
- if _, err = keks.Encode(&hdr, &cmenc.Encrypted{
+ enc := cmenc.Encrypted{
Salt: salt,
KEM: kems,
DEM: cmenc.DEM{A: cmenc.ChaCha20Poly1305},
- }, nil); err != nil {
+ }
+ if *noblob {
+ var buf bytes.Buffer
+ if _, err = chaPoly.Seal(&buf, os.Stdin, cek, *parallel); err != nil {
+ log.Fatal(err)
+ }
+ enc.Payload = buf.Bytes()
+ }
+ if _, err = keks.Encode(&hdr, &enc, nil); err != nil {
log.Fatal(err)
}
if _, err = io.Copy(os.Stdout, &hdr); err != nil {
log.Fatal(err)
}
}
- if _, err = chaPoly.Seal(os.Stdout, os.Stdin, cek, *parallel); err != nil {
- log.Fatal(err)
+ if !*noblob {
+ if _, err = chaPoly.SealBlob(os.Stdout, os.Stdin, cek, *parallel); err != nil {
+ log.Fatal(err)
+ }
}
}
}
#!/bin/sh
-test_description="Check that basic passphrase encryption functionality works"
+test_description="Check passphrase encryption"
. $SHARNESS_TEST_SRCDIR/sharness.sh
TMPDIR=${TMPDIR:-/tmp}
dd if=/dev/urandom of=$TMPDIR/enc.data bs=300K count=1 2>/dev/null
export ENCTOOL_PASSPHRASE=$(dd if=/dev/urandom bs=32 count=1 2>/dev/null | xxd -p)
-test_expect_success "encrypting" "enctool -p \
+test_expect_success "encrypting" "cmenctool -p \
<$TMPDIR/enc.data >$TMPDIR/enc.enc"
-test_expect_success "decrypting" "enctool -d -p \
+test_expect_success "decrypting" "cmenctool -d -p \
<$TMPDIR/enc.enc >$TMPDIR/enc.data.got"
test_expect_success "comparing" \
"test_cmp $TMPDIR/enc.data $TMPDIR/enc.data.got"
--- /dev/null
+#!/bin/sh
+
+test_description="Check passphrase-encrypted key decryption"
+. $SHARNESS_TEST_SRCDIR/sharness.sh
+
+TMPDIR=${TMPDIR:-/tmp}
+
+cmkeytool -algo sntrup4591761-x25519 -ku kem -subj A=KEY \
+ -prv $TMPDIR/enc.prv -pub $TMPDIR/enc.pub
+dd if=/dev/urandom of=$TMPDIR/enc.data bs=12K count=1 2>/dev/null
+export ENCTOOL_PASSPHRASE=$(dd if=/dev/urandom bs=32 count=1 2>/dev/null | xxd -p)
+test_expect_success "key encrypting" "cmenctool -p -no-blob \
+ <$TMPDIR/enc.prv >$TMPDIR/enc.prv.enc"
+test_expect_success "data encrypting" "cmenctool -pub $TMPDIR/enc.pub \
+ <$TMPDIR/enc.data >$TMPDIR/enc.enc"
+test_expect_success "decrypting" "cmenctool -d -prv $TMPDIR/enc.prv.enc \
+ <$TMPDIR/enc.enc >$TMPDIR/enc.data.got"
+test_expect_success "comparing" \
+ "test_cmp $TMPDIR/enc.data $TMPDIR/enc.data.got"
+
+test_done
#!/bin/sh
-test_description="Check that basic public-key encryption functionality works"
+test_description="Check public-key encryption"
. $SHARNESS_TEST_SRCDIR/sharness.sh
TMPDIR=${TMPDIR:-/tmp}
algo=mceliece6960119-x25519
algo0=$algo
-test_expect_success "$algo: pub generation" "keytool \
+test_expect_success "$algo: pub generation" "cmkeytool \
-algo $algo -ku kem -subj A=$algo \
-prv $TMPDIR/enc.$algo.prv -pub $TMPDIR/enc.$algo.pub"
algo=sntrup4591761-x25519
algo1=$algo
-test_expect_success "$algo: pub generation" "keytool \
+test_expect_success "$algo: pub generation" "cmkeytool \
-algo $algo -ku kem -subj A=$algo \
-prv $TMPDIR/enc.$algo.prv -pub $TMPDIR/enc.$algo.pub"
-test_expect_success "encrypting" "enctool \
+test_expect_success "encrypting" "cmenctool \
-pub $TMPDIR/enc.$algo0.pub -pub $TMPDIR/enc.$algo1.pub \
<$TMPDIR/enc.data >$TMPDIR/enc.enc"
-test_expect_success "any: decrypting" "enctool -d \
+test_expect_success "any: decrypting" "cmenctool -d \
-prv $TMPDIR/enc.$algo0.prv -prv $TMPDIR/enc.$algo1.prv \
<$TMPDIR/enc.enc >$TMPDIR/enc.data.got"
test_expect_success "comparing" \
"test_cmp $TMPDIR/enc.data $TMPDIR/enc.data.got"
-test_expect_success "$algo0: decrypting" "enctool -d \
+test_expect_success "$algo0: decrypting" "cmenctool -d \
-prv $TMPDIR/enc.$algo0.prv \
<$TMPDIR/enc.enc >$TMPDIR/enc.data.got"
test_expect_success "$algo0: comparing" \
"test_cmp $TMPDIR/enc.data $TMPDIR/enc.data.got"
-test_expect_success "$algo1: decrypting" "enctool -d \
+test_expect_success "$algo1: decrypting" "cmenctool -d \
-prv $TMPDIR/enc.$algo1.prv \
<$TMPDIR/enc.enc >$TMPDIR/enc.data.got"
test_expect_success "$algo1: comparing" \
"test_cmp $TMPDIR/enc.data $TMPDIR/enc.data.got"
export ENCTOOL_PASSPHRASE=$(dd if=/dev/urandom bs=32 count=1 2>/dev/null | xxd -p)
-test_expect_success "encrypting also with passphrase" "enctool \
+test_expect_success "encrypting also with passphrase" "cmenctool \
-pub $TMPDIR/enc.$algo0.pub -pub $TMPDIR/enc.$algo1.pub -p \
<$TMPDIR/enc.data >$TMPDIR/enc.enc"
-test_expect_success "any: decrypting" "enctool -d \
+test_expect_success "any: decrypting" "cmenctool -d \
-prv $TMPDIR/enc.$algo0.prv -prv $TMPDIR/enc.$algo1.prv \
<$TMPDIR/enc.enc >$TMPDIR/enc.data.got"
test_expect_success "comparing" \
"test_cmp $TMPDIR/enc.data $TMPDIR/enc.data.got"
-test_expect_success "passphrase: decrypting" "enctool -d -p \
+test_expect_success "passphrase: decrypting" "cmenctool -d -p \
<$TMPDIR/enc.enc >$TMPDIR/enc.data.got"
test_expect_success "comparing" \
"test_cmp $TMPDIR/enc.data $TMPDIR/enc.data.got"
import (
"crypto/sha512"
- "encoding/hex"
"flag"
"fmt"
"log"
"go.cypherpunks.su/gogost/v6/gost34112012256"
"go.cypherpunks.su/gogost/v6/gost34112012512"
+
+ "go.cypherpunks.su/keks"
cmhash "go.cypherpunks.su/keks/cm/hash"
cmblake2b "go.cypherpunks.su/keks/cm/hash/blake2b"
"go.cypherpunks.su/keks/cm/hash/gost"
func main() {
workers := flag.Int("p", runtime.NumCPU(), "Parallel workers")
chunkLenK := flag.Int("c", merkle.DefaultChunkLen/1024, "Chunk size, KiB")
- algo := flag.String("a", cmhash.BLAKE2b, "Algorithm to use")
+ algo := flag.String("a", cmhash.BLAKE2bMerkle, "Algorithm to use")
list := flag.Bool("list", false, "List available algorithms")
mmap := flag.String("mmap", "", "Use that mmap-ed file instead of stdin")
flag.Parse()
if err != nil {
log.Fatal(err)
}
- fmt.Println(hex.EncodeToString(hasher.Sum(nil)))
+ s := cmhash.Hashed{
+ Algo: []string{*algo},
+ Typ: "data",
+ Hash: [][]byte{hasher.Sum(nil)},
+ }
+ keks.Encode(os.Stdout, s, nil)
}
#!/bin/sh
-test_description="Check that basic certification functionality works"
+test_description="Check certification"
. $SHARNESS_TEST_SRCDIR/sharness.sh
TMPDIR=${TMPDIR:-/tmp}
ed25519-blake2b ed25519-blake2b" | while read caAlgo eeAlgo ; do
subj="-subj CN=CA -subj C=RU"
-test_expect_success "$caAlgo: CA load generation" "keytool \
+test_expect_success "$caAlgo: CA load generation" "cmkeytool \
-algo $caAlgo \
-ku sig $subj \
-prv $TMPDIR/ca.$caAlgo.prv -pub $TMPDIR/ca.$caAlgo.pub"
-test_expect_success "$caAlgo: CA generation" "keytool \
+test_expect_success "$caAlgo: CA generation" "cmkeytool \
-pub $TMPDIR/ca.$caAlgo.pub \
-ca-prv $TMPDIR/ca.$caAlgo.prv -ca-pub $TMPDIR/ca.$caAlgo.pub"
-test_expect_success "$caAlgo: CA regeneration" "keytool \
+test_expect_success "$caAlgo: CA regeneration" "cmkeytool \
-pub $TMPDIR/ca.$caAlgo.pub \
-ca-prv $TMPDIR/ca.$caAlgo.prv -ca-pub $TMPDIR/ca.$caAlgo.pub"
-test_expect_success "$caAlgo: CA self-signature" "keytool \
+test_expect_success "$caAlgo: CA self-signature" "cmkeytool \
-ca-pub $TMPDIR/ca.$caAlgo.pub \
-pub $TMPDIR/ca.$caAlgo.pub \
-verify"
subj="-subj CN=SubCA -subj C=RU"
-test_expect_success "$eeAlgo: SubCA load generation" "keytool \
+test_expect_success "$eeAlgo: SubCA load generation" "cmkeytool \
-algo $eeAlgo \
-ku sig $subj \
-prv $TMPDIR/subca.$eeAlgo.prv -pub $TMPDIR/subca.$eeAlgo.pub"
-test_expect_success "$eeAlgo: SubCA generation" "keytool \
+test_expect_success "$eeAlgo: SubCA generation" "cmkeytool \
-pub $TMPDIR/subca.$eeAlgo.pub \
-ca-pub $TMPDIR/ca.$caAlgo.pub -ca-prv $TMPDIR/ca.$caAlgo.prv"
-test_expect_success "$eeAlgo: SubCA signature" "keytool \
+test_expect_success "$eeAlgo: SubCA signature" "cmkeytool \
-ca-pub $TMPDIR/ca.$caAlgo.pub \
-pub $TMPDIR/subca.$eeAlgo.pub \
-verify"
subj="-subj CN=EE -subj C=RU"
-test_expect_success "$eeAlgo: EE load generation" "keytool \
+test_expect_success "$eeAlgo: EE load generation" "cmkeytool \
-algo $eeAlgo $subj \
-prv $TMPDIR/ee.$eeAlgo.prv -pub $TMPDIR/ee.$eeAlgo.pub"
-test_expect_success "$eeAlgo: EE generation" "keytool \
+test_expect_success "$eeAlgo: EE generation" "cmkeytool \
-ca-prv $TMPDIR/subca.$eeAlgo.prv -ca-pub $TMPDIR/subca.$eeAlgo.pub \
-pub $TMPDIR/ee.$eeAlgo.pub"
-test_expect_success "$eeAlgo: EE chain" "keytool \
+test_expect_success "$eeAlgo: EE chain" "cmkeytool \
-ca-pub $TMPDIR/ca.$caAlgo.pub \
-ca-pub $TMPDIR/subca.$eeAlgo.pub \
-pub $TMPDIR/ee.$eeAlgo.pub \
#!/bin/sh
-test_description="Check that KEM certificates generation works"
+test_description="Check KEM certificates generation"
. $SHARNESS_TEST_SRCDIR/sharness.sh
TMPDIR=${TMPDIR:-/tmp}
echo "mceliece6960119-x25519
sntrup4591761-x25519" | while read algo ; do
-test_expect_success "$algo: generation" "keytool \
+test_expect_success "$algo: generation" "cmkeytool \
-algo $algo \
-ku kem -subj CN=DH \
-prv $TMPDIR/kem.$algo.prv -pub $TMPDIR/kem.$algo.pub"
#!/bin/sh
-test_description="Check that basic signing functionality works"
+test_description="Check signing"
. $SHARNESS_TEST_SRCDIR/sharness.sh
TMPDIR=${TMPDIR:-/tmp}
subj="-subj what=ever"
typ="some-different-type"
-test_expect_success "$keyalgo: pub generation" "keytool \
+test_expect_success "$keyalgo: pub generation" "cmkeytool \
-algo $keyalgo -ku sig $subj \
-prv $TMPDIR/sign.$keyalgo.prv -pub $TMPDIR/sign.$keyalgo.pub"
dd if=/dev/urandom of=$TMPDIR/sign.$keyalgo.data bs=300K count=1 2>/dev/null
for merkle in "" "-merkle" ; do
algo=${keyalgo}${merkle}
-test_expect_success "$algo: signing" "sigtool $merkle \
+test_expect_success "$algo: signing" "cmsigtool $merkle \
-prv $TMPDIR/sign.$keyalgo.prv -pub $TMPDIR/sign.$keyalgo.pub -type $typ \
$encTo <$TMPDIR/sign.$keyalgo.data >$TMPDIR/sign.$algo.sig"
-test_expect_success "$algo: verifying" "sigtool \
+test_expect_success "$algo: verifying" "cmsigtool \
-verify -pub $TMPDIR/sign.$keyalgo.pub -type $typ \
<$TMPDIR/sign.$algo.sig >$TMPDIR/sign.data.got"
test_expect_success "$algo: comparing" \
"test_cmp $TMPDIR/sign.$keyalgo.data $TMPDIR/sign.data.got"
-test_expect_success "$algo: differing type" "! sigtool \
+test_expect_success "$algo: differing type" "! cmsigtool \
-verify -pub $TMPDIR/sign.$keyalgo.pub <$TMPDIR/sign.$algo.sig >/dev/null"
-test_expect_success "$algo: good encTo" "! sigtool \
+test_expect_success "$algo: good encTo" "! cmsigtool \
-verify -pub $TMPDIR/sign.$keyalgo.pub $encTo <$TMPDIR/sign.$algo.sig >/dev/null"
-test_expect_success "$algo: bad encTo" "! sigtool \
+test_expect_success "$algo: bad encTo" "! cmsigtool \
-verify -pub $TMPDIR/sign.$keyalgo.pub $badEncTo <$TMPDIR/sign.$algo.sig >/dev/null"
-test_expect_success "$algo: detached signing" "sigtool -detached $merkle \
+test_expect_success "$algo: detached signing" "cmsigtool -detached $merkle \
-prv $TMPDIR/sign.$keyalgo.prv -pub $TMPDIR/sign.$keyalgo.pub -type $typ \
<$TMPDIR/sign.$keyalgo.data >$TMPDIR/sign.$algo.detached.sig"
test_expect_success "$algo: detached verifying" \
"cat $TMPDIR/sign.$algo.detached.sig $TMPDIR/sign.$keyalgo.data |
- sigtool -detached -verify -pub $TMPDIR/sign.$keyalgo.pub -type $typ"
-test_expect_success "$algo: differing type" "! sigtool -detached \
+ cmsigtool -detached -verify -pub $TMPDIR/sign.$keyalgo.pub -type $typ"
+test_expect_success "$algo: differing type" "! cmsigtool -detached \
-verify -pub $TMPDIR/sign.$keyalgo.pub <$TMPDIR/sign.$algo.detached.sig >/dev/null"
-test_expect_success "$algo: good encTo" "! sigtool -detached \
+test_expect_success "$algo: good encTo" "! cmsigtool -detached \
-verify -pub $TMPDIR/sign.$keyalgo.pub $encTo <$TMPDIR/sign.$algo.detached.sig >/dev/null"
-test_expect_success "$algo: bad encTo" "! sigtool -detached \
+test_expect_success "$algo: bad encTo" "! cmsigtool -detached \
-verify -pub $TMPDIR/sign.$keyalgo.pub $badEncTo <$TMPDIR/sign.$algo.detached.sig >/dev/null"
done
+++ /dev/null
-package encrypted
-
-const (
- BalloonSaltLen = 8
- BalloonHKDFInfo = "keks/cm/encrypted/balloon-blake2b-hkdf"
-)
-
-type BalloonCost struct {
- S uint64 `keks:"s"`
- T uint64 `keks:"t"`
- P uint64 `keks:"p"`
-}
--- /dev/null
+package cost
+
+type Cost struct {
+ S uint64 `keks:"s"`
+ T uint64 `keks:"t"`
+ P uint64 `keks:"p"`
+}
--- /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 balloon
+
+import (
+ "bytes"
+ "crypto/hkdf"
+ "errors"
+ "hash"
+
+ "go.cypherpunks.su/balloon/v3"
+ cmenc "go.cypherpunks.su/keks/cm/enc"
+ chaPoly "go.cypherpunks.su/keks/cm/enc/chapoly"
+ "golang.org/x/crypto/blake2b"
+)
+
+const (
+ BalloonBLAKE2bHKDF = "balloon-blake2b-hkdf"
+ SaltLen = 8
+ HKDFInfo = "keks/cm/encrypted/balloon-blake2b-hkdf"
+)
+
+func blake2bHash() hash.Hash {
+ h, err := blake2b.New512(nil)
+ if err != nil {
+ panic(err)
+ }
+ return h
+}
+
+func Decapsulate(kem cmenc.KEM, encSalt, passphrase []byte) (cek []byte, err error) {
+ if kem.Salt == nil {
+ return nil, errors.New("missing salt")
+ }
+ if kem.BalloonCost == nil {
+ return nil, errors.New("missing cost")
+ }
+ var kek []byte
+ kek, err = hkdf.Expand(
+ blake2bHash,
+ balloon.H(
+ blake2bHash,
+ passphrase,
+ append(encSalt, *kem.Salt...),
+ int(kem.BalloonCost.S),
+ int(kem.BalloonCost.T),
+ int(kem.BalloonCost.P),
+ ),
+ HKDFInfo,
+ chaPoly.KeyLen,
+ )
+ if err != nil {
+ return nil, err
+ }
+ var buf bytes.Buffer
+ _, err = chaPoly.Open(&buf, bytes.NewReader(kem.CEK), kek, 1)
+ return buf.Bytes(), err
+}
func do(
w io.Writer,
- seal bool,
+ blob, seal bool,
r io.Reader,
key []byte,
procs int,
blobChunkLen := PadLen + ChunkLen + overhead
var blobDecoder *keks.BlobDecoder
if seal {
- total, err = keks.BlobAtomEncode(w, int64(blobChunkLen))
+ if blob {
+ total, err = keks.BlobAtomEncode(w, int64(blobChunkLen))
+ }
} else {
- blobDecoder, err = keks.NewBlobDecoder(r, int64(blobChunkLen))
+ if blob {
+ blobDecoder, err = keks.NewBlobDecoder(r, int64(blobChunkLen))
+ }
}
if err != nil {
return
for j := range dones {
<-j.processed
if seal {
- n, errW = keks.BinEncode(w, j.buf)
+ if blob {
+ n, errW = keks.BinEncode(w, j.buf)
+ } else {
+ n, errW = io.Copy(w, bytes.NewReader(j.buf))
+ }
} else {
if len(j.buf) == 0 {
n = 0
}
}
total += n
- if errW != nil {
+ if errW != nil || n == 0 {
break
}
j.buf = j.buf[:blobChunkLen]
if seal {
n, errR = io.ReadFull(r, j.buf[PadLen:PadLen+ChunkLen])
} else {
- blobDecoder.SetReadBuf(j.buf)
- chunk, errR = blobDecoder.Next()
- n = len(chunk)
- j.buf = j.buf[:n]
+ if blobDecoder == nil {
+ n, errR = io.ReadFull(r, j.buf)
+ j.buf = j.buf[:n]
+ } else {
+ blobDecoder.SetReadBuf(j.buf)
+ chunk, errR = blobDecoder.Next()
+ n = len(chunk)
+ j.buf = j.buf[:n]
+ }
}
total += int64(n)
if errR != nil {
}
func Seal(w io.Writer, r io.Reader, key []byte, procs int) (total int64, err error) {
- return do(w, true, r, key, procs)
+ return do(w, false, true, r, key, procs)
}
func Open(w io.Writer, r io.Reader, key []byte, procs int) (total int64, err error) {
- return do(w, false, r, key, procs)
+ return do(w, false, false, r, key, procs)
+}
+
+func SealBlob(w io.Writer, r io.Reader, key []byte, procs int) (total int64, err error) {
+ return do(w, true, true, r, key, procs)
+}
+
+func OpenBlob(w io.Writer, r io.Reader, key []byte, procs int) (total int64, err error) {
+ return do(w, true, false, r, key, procs)
}
package encrypted
-const (
- BalloonBLAKE2bHKDF = "balloon-blake2b-hkdf"
- ChaCha20Poly1305 = "chacha20poly1305"
-)
+const ChaCha20Poly1305 = "chacha20poly1305"
+
+type DEM struct {
+ A string `keks:"a"`
+}
--- /dev/null
+package encrypted
+
+import "github.com/google/uuid"
+
+type Encrypted struct {
+ DEM DEM `keks:"dem"`
+ KEM []KEM `keks:"kem"`
+ Salt uuid.UUID `keks:"salt"`
+
+ Payload []byte `keks:"payload,omitempty"`
+}
import (
"github.com/google/uuid"
- "go.cypherpunks.su/keks"
+
+ balloon "go.cypherpunks.su/keks/cm/enc/balloon/cost"
)
const (
To *uuid.UUID `keks:"to,omitempty"`
// balloon-blake2b-hkdf related
- Cost *BalloonCost `keks:"cost,omitempty"`
- Salt *[]byte `keks:"salt,omitempty"`
+ BalloonCost *balloon.Cost `keks:"cost,omitempty"`
+ Salt *[]byte `keks:"salt,omitempty"`
Encap *[]byte `keks:"encap,omitempty"`
}
-
-type DEM struct {
- A string `keks:"a"`
-}
-
-type Encrypted struct {
- DEM DEM `keks:"dem"`
- KEM []KEM `keks:"kem"`
- Salt uuid.UUID `keks:"salt"`
-
- Ciphertext *keks.BlobChunked `keks:"ciphertext,omitempty"`
-}
module go.cypherpunks.su/keks/cm
-go 1.24
+go 1.24.0
require (
github.com/companyzero/sntrup4591761 v0.0.0-20220309191932-9e0f3af2f07a
--- /dev/null
+package hash
+
+type Hashed struct {
+ Algo []string `keks:"a"`
+ Typ string `keks:"t"`
+ Hash [][]byte `keks:"hash"`
+}
--- /dev/null
+#!/bin/sh -e
+
+GO_LDFLAGS="${GO_LDFLAGS:--s}"
+
+mkdir -p bin
+for cmd in enc hsh key sig ; do
+ cmd=${cmd}tool
+ go build -o bin/cm$cmd -ldflags "$GO_LDFLAGS" ./cmd/$cmd
+done
--- /dev/null
+module go.cypherpunks.su/keks/cmd/pp
+
+go 1.24.0
+
+replace go.cypherpunks.su/keks => ../../
+
+require (
+ go.cypherpunks.su/keks v0.0.0-00010101000000-000000000000
+ go.cypherpunks.su/tai64n/v4 v4.1.0
+ golang.org/x/term v0.28.0
+)
+
+require (
+ github.com/google/uuid v1.6.0 // indirect
+ github.com/mitchellh/mapstructure v1.5.0 // indirect
+ golang.org/x/sys v0.29.0 // indirect
+)
--- /dev/null
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
+github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+go.cypherpunks.su/tai64n/v4 v4.1.0 h1:jW0EyklKXpSy9DSFMcDbu7XuLlMkn6kkpNWiMG6UT5c=
+go.cypherpunks.su/tai64n/v4 v4.1.0/go.mod h1:/uKUdhLOy8UciRKpapPaFXSOoa/SiXjs3XsDDpAz7OA=
+golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
+golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
+golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
)
var (
- MaxStrLen = flag.Uint("max-str-len", 40, "Maximal string length to print")
+ MaxStrLen = flag.Uint("max-str-len", 64,
+ "Maximal string length to print")
MaxParseCycles = flag.Uint64("max-parse-cycles", 1<<63,
"Maximal number of parse cycles to perform")
)
module go.cypherpunks.su/keks
-go 1.24
+go 1.24.0
require (
github.com/google/uuid v1.6.0
github.com/mitchellh/mapstructure v1.5.0
go.cypherpunks.su/tai64n/v4 v4.1.0
- golang.org/x/term v0.28.0
)
-
-require golang.org/x/sys v0.29.0 // indirect
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
go.cypherpunks.su/tai64n/v4 v4.1.0 h1:jW0EyklKXpSy9DSFMcDbu7XuLlMkn6kkpNWiMG6UT5c=
go.cypherpunks.su/tai64n/v4 v4.1.0/go.mod h1:/uKUdhLOy8UciRKpapPaFXSOoa/SiXjs3XsDDpAz7OA=
-golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
-golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
-golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
--- /dev/null
+#!/bin/sh -e
+
+GO_LDFLAGS="${GO_LDFLAGS:--s}"
+
+mkdir -p bin
+bin=$(realpath bin)
+cd cmd/pp
+go build -o $bin/kekspp -ldflags "$GO_LDFLAGS"
dem: dem,
kem: [+ kem],
salt: uuid,
- ? ciphertext: blob,
+ ? payload: bytes,
}
dem = dem-chacha20poly1305 / dem-kuznechik-ctracpkm-hmac
@verbatiminclude cm/encrypted.cddl
-@code{/ciphertext} contains the ciphertext. It is encrypted with random
+@code{/payload} contains the ciphertext. It is encrypted with random
"content encryption key" (CEK) with an algorithm specified in
@code{/dem/a} (data encapsulation mechanism). @code{/dem} may contain
additional fields supplementing the decryption process, like
initialisation vector.
-@code{/ciphertext} is a BLOB, which chunk's length depends on DEM
-algorithm. If it is absent, then ciphertext is provided by other means,
-for example just following the @code{cm-encrypted} structure.
+If @code{/payload} is absent, then ciphertext is provided by other
+means, for example just by following the @code{cm-encrypted} structure.
+It is recommended to encode it as a BLOB, which chunk's length depends
+on DEM algorithm.
CEK is encapsulated in @code{/kem/*} entries (key encapsulation
mechanism), using @code{/kem/*/a} algorithm. @code{/kem/*/cek} field
payload. It equals to 0x01 for the last chunk and to 0x00 for other ones.
Last chunk should be smaller than previous ones, maybe (payload) even empty.
- @code{/ciphertext}'s chunk length equals to 32+128KiB+16 bytes.
+ @code{/payload}'s chunk length equals to 32+128KiB+16 bytes.
@node cm-encrypted-kuznechik-ctracpkm-hmac
@cindex cm-encrypted-kuznechik-ctracpkm-hmac
size and IV initialisation vector. Authentication of ciphertext is
performed with Streebog-512 (ГОСТ Р 34.11-2012) in HMAC mode.
- @code{/ciphertext}'s chunk length equals to 128KiB bytes.
+ @code{/payload}'s chunk length equals to 128KiB bytes.
@node cm-encrypted-balloon-blake2b-hkdf
@cindex cm-encrypted-balloon-blake2b-hkdf
cm-hashed = {
a: [+ ai],
t: text, ; type of the content
- v: bytes / text / blob / map / list, ; content itself
+ ? v: bytes / blob, ; content itself
hash: [+ bytes], ; hash values
}
@code{/t} tells the type of the data inside.
-If @code{/v} is either a MAP or LIST, then its encoded binary
-representation is hashed. If it is BIN/STR or BLOB, then its binary
-contents are hashed. So hash will stay the same even if data is
-converted from BIN to BLOB.
-
@code{/hash} contains the hash values for all corresponding @code{/a}
algorithms.