From: Sergey Matveev Date: Wed, 1 Jan 2020 22:31:30 +0000 (+0300) Subject: Huge simplifications X-Git-Tag: v2.0.0^0 X-Git-Url: http://www.git.cypherpunks.ru/?p=gohpenc.git;a=commitdiff_plain;h=009e6a235129f367b3534fa2346af8020b619e09 Huge simplifications * Fixed workability * Base32 code is simplified with base32.NoPadding * No XOF, key derivation, just simple 24-byte nonce with 16-byte salt and a counter * Updated dependencies * Raised copyright years --- diff --git a/README b/README index d16cceb..bacd9b6 100644 --- a/README +++ b/README @@ -17,11 +17,11 @@ language, widening supported platforms. gohpenc is incompatible with hpenc and much simpler: -* it uses only ChaCha20-Poly1305 algorithm +* it uses only XChaCha20-Poly1305 algorithm * no random data generation mode * no metadata in output stream and no structure validation. Only blocks authentication -* simpler key derivation -- new key for each block +* no key derivation -- new key for each block But it still satisfies most of hpenc aims: @@ -42,13 +42,10 @@ Usage is very simple: How encryption/authentication is performed: -* First 32 bytes of the stream contain random data -- salt -* BLAKE2X is initialized: unknown length, PSK key as a MAC key. It - creates XOF that will be used as a KDF -* Salt is fed into that XOF -* All data is processed block by block -* New key is derived for each block by reading it from the XOF -* ChaCha20-Poly1305 algorithm is initialized with that key +* First 16 bytes of the stream contain random data -- nonce salt +* XChaCha20-Poly1305 algorithm is initialized with the key and 24-byte + nonce, where 16 bytes is the salt, and 8 bytes is 64-bit unsigned + big-endian block number * 32-bit big-endian value with the length of the block is outputted, then an encrypted and authenticated block goes further, with authenticated data containing that 32-bit length value diff --git a/base32.go b/base32.go deleted file mode 100644 index f84388f..0000000 --- a/base32.go +++ /dev/null @@ -1,23 +0,0 @@ -package main - -import ( - "encoding/base32" - "strings" -) - -func ToBase32(data []byte) string { - return strings.TrimRight(base32.StdEncoding.EncodeToString(data), "=") -} - -func FromBase32(data string) ([]byte, error) { - padSize := len(data) % 8 - if padSize != 0 { - padSize = 8 - padSize - pad := make([]byte, 0, padSize) - for i := 0; i < padSize; i++ { - pad = append(pad, '=') - } - data += string(pad) - } - return base32.StdEncoding.DecodeString(data) -} diff --git a/go.mod b/go.mod index 888bc05..fe390d9 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,4 @@ module go.cypherpunks.ru/gohpenc go 1.12 -require golang.org/x/crypto v0.0.0-20191001170739-f9e2070545dc +require golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876 diff --git a/go.sum b/go.sum index 596f9c8..452e5b0 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,6 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191001170739-f9e2070545dc h1:KyTYo8xkh/2WdbFLUyQwBS0Jfn3qfZ9QmuPbok2oENE= -golang.org/x/crypto v0.0.0-20191001170739-f9e2070545dc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876 h1:sKJQZMuxjOAR/Uo2LBfU90onWEf1dF4C+0hPJCc9Mpc= +golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= diff --git a/main.go b/main.go index 9793cf1..a054328 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,6 @@ /* gohpenc -- Go high-performance encryption utility -Copyright (C) 2017-2019 Sergey Matveev +Copyright (C) 2017-2020 Sergey Matveev This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -20,7 +20,9 @@ package main import ( "bufio" + "crypto/cipher" "crypto/rand" + "encoding/base32" "encoding/binary" "flag" "fmt" @@ -29,160 +31,172 @@ import ( "runtime" "sync" - "golang.org/x/crypto/blake2b" "golang.org/x/crypto/chacha20poly1305" + "golang.org/x/crypto/poly1305" ) const ( LenSize = 4 - SaltSize = 32 + SaltSize = 16 ) -type WorkerTask struct { +var ( + doPSK = flag.Bool("psk", false, "Generate PSK") + keyB32 = flag.String("k", "", "Encryption key") + decrypt = flag.Bool("d", false, "Decrypt, instead of encrypt") + blockSize = flag.Int("b", 1<<10, "Blocksize, in KiB") + threads = flag.Int("c", runtime.NumCPU(), "Number of threads") + + Base32Codec *base32.Encoding = base32.StdEncoding.WithPadding(base32.NoPadding) + key []byte - n int + bs int + wg sync.WaitGroup +) + +type Task struct { + ctr uint64 + size int } type Worker struct { - wg *sync.WaitGroup - buf []byte + aead cipher.AEAD + nonce []byte + input []byte ready chan struct{} - task chan WorkerTask - done chan []byte + task chan Task + output chan []byte written chan struct{} } -func (w *Worker) Thread(doDecrypt bool) { - var task WorkerTask - nonce := make([]byte, chacha20poly1305.NonceSize) - var done []byte +func NewWorker(key, salt []byte) *Worker { + aead, err := chacha20poly1305.NewX(key) + if err != nil { + panic(err) + } + w := Worker{ + aead: aead, + nonce: make([]byte, chacha20poly1305.NonceSizeX), + input: make([]byte, LenSize+bs+poly1305.TagSize), + ready: make(chan struct{}), + task: make(chan Task), + output: make(chan []byte), + written: make(chan struct{}), + } + copy(w.nonce, salt) + go w.Run() + return &w +} + +func (w *Worker) Run() { + var output []byte + var err error for { w.ready <- struct{}{} - task = <-w.task - aead, err := chacha20poly1305.New(task.key) - if err != nil { - panic(err) - } - if doDecrypt { - done, err = aead.Open(w.buf[:0], nonce, w.buf[LenSize:LenSize+task.n], w.buf[:LenSize]) + task := <-w.task + binary.BigEndian.PutUint64(w.nonce[SaltSize:], task.ctr) + if *decrypt { + output, err = w.aead.Open( + w.input[:LenSize], + w.nonce, + w.input[LenSize:LenSize+task.size+poly1305.TagSize], + w.input[:LenSize], + ) if err != nil { panic(err) } + output = output[LenSize:] } else { - binary.BigEndian.PutUint32(w.buf, uint32(task.n)) - done = aead.Seal(w.buf[:LenSize], nonce, w.buf[LenSize:LenSize+task.n], w.buf[:LenSize]) + binary.BigEndian.PutUint32(w.input, uint32(task.size)) + output = w.aead.Seal( + w.input[:LenSize], + w.nonce, + w.input[LenSize:LenSize+task.size], + w.input[:LenSize], + ) } - w.done <- done + w.output <- output <-w.written - w.wg.Done() + wg.Done() } } func main() { - var ( - doPSK = flag.Bool("psk", false, "Generate PSK") - key = flag.String("k", "", "Encryption key") - doDecrypt = flag.Bool("d", false, "Decrypt, instead of encrypt") - blockSize = flag.Int("b", 1<<10, "Blocksize, in KiB") - threads = flag.Int("c", runtime.NumCPU(), "Number of threads") - ) flag.Parse() - bs := *blockSize * 1 << 10 if *doPSK { - key := make([]byte, 32) - if _, err := rand.Read(key); err != nil { + key := make([]byte, chacha20poly1305.KeySize) + if _, err := io.ReadFull(rand.Reader, key); err != nil { panic(err) } - fmt.Println(ToBase32(key)) + fmt.Println(Base32Codec.EncodeToString(key)) return } - if len(*key) != 52 { - panic("Invalid key size") - } - keyDecoded, err := FromBase32(*key) + var err error + key, err = Base32Codec.DecodeString(*keyB32) if err != nil { panic(err) } - tmpAEAD, err := chacha20poly1305.New(make([]byte, chacha20poly1305.KeySize)) - if err != nil { - panic(err) + if len(key) != chacha20poly1305.KeySize { + panic("Invalid key size") } - keys, err := blake2b.NewXOF(blake2b.OutputLengthUnknown, keyDecoded) - if err != nil { - panic(err) + salt := make([]byte, SaltSize) + + if *decrypt { + if _, err = io.ReadFull(os.Stdin, salt); err != nil { + panic(err) + } + } else { + if _, err = io.ReadFull(rand.Reader, salt); err != nil { + panic(err) + } + if _, err = os.Stdout.Write(salt); err != nil { + panic(err) + } } - var wg sync.WaitGroup - workers := make([]*Worker, 0, *threads) + bs = *blockSize * (1 << 10) + stdin := bufio.NewReaderSize(os.Stdin, LenSize+bs+poly1305.TagSize) + + workers := make([]*Worker, *threads) for i := 0; i < *threads; i++ { - w := Worker{ - wg: &wg, - buf: make([]byte, LenSize+bs+tmpAEAD.Overhead()), - ready: make(chan struct{}), - task: make(chan WorkerTask), - done: make(chan []byte), - written: make(chan struct{}), - } - go w.Thread(*doDecrypt) - workers = append(workers, &w) + workers[i] = NewWorker(key, salt) } go func() { - i := 0 - var w *Worker + var ctr uint64 for { - w = workers[i%len(workers)] - if _, err = os.Stdout.Write(<-w.done); err != nil { + w := workers[ctr%uint64(len(workers))] + if _, err := os.Stdout.Write(<-w.output); err != nil { panic(err) } w.written <- struct{}{} - i++ + ctr++ } }() - stdin := bufio.NewReaderSize(os.Stdin, LenSize+bs) - if *doDecrypt { - if _, err = io.CopyN(keys, stdin, SaltSize); err != nil { - panic(err) - } - } else { - salt := make([]byte, SaltSize) - if _, err = rand.Read(salt); err != nil { - panic(err) - } - if _, err = keys.Write(salt); err != nil { - panic(err) - } - if _, err = os.Stdout.Write(salt); err != nil { - panic(err) - } - } - - i := 0 - var n int - var w *Worker + var ctr uint64 + var size int for { - key := make([]byte, chacha20poly1305.KeySize) - if _, err = io.ReadFull(keys, key); err != nil { - panic(err) - } - w = workers[i%len(workers)] + w := workers[ctr%uint64(len(workers))] <-w.ready - if *doDecrypt { - _, err = io.ReadFull(stdin, w.buf[:LenSize]) + if *decrypt { + _, err = io.ReadFull(stdin, w.input[:LenSize]) if err != nil { if err == io.EOF { break } panic(err) } - n = int(binary.BigEndian.Uint32(w.buf[:LenSize])) - if n, err = io.ReadFull(stdin, w.buf[LenSize:LenSize+n+tmpAEAD.Overhead()]); err != nil { + size = int(binary.BigEndian.Uint32(w.input[:LenSize])) + if _, err = io.ReadFull( + stdin, + w.input[LenSize:LenSize+size+poly1305.TagSize], + ); err != nil { panic(err) } } else { - n, err = stdin.Read(w.buf[LenSize : LenSize+bs]) + size, err = stdin.Read(w.input[LenSize : LenSize+bs]) if err != nil { if err == io.EOF { break @@ -191,8 +205,8 @@ func main() { } } wg.Add(1) - w.task <- WorkerTask{key, n} - i++ + w.task <- Task{ctr, size} + ctr++ } wg.Wait() }