/* gohpenc -- Go high-performance encryption utility 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 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ // Go high-performance encryption utility package main import ( "bufio" "crypto/cipher" "crypto/rand" "encoding/base32" "encoding/binary" "flag" "fmt" "io" "os" "runtime" "sync" "golang.org/x/crypto/chacha20poly1305" "golang.org/x/crypto/poly1305" ) const ( LenSize = 4 SaltSize = 16 ) 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 bs int wg sync.WaitGroup ) type Task struct { ctr uint64 size int } type Worker struct { aead cipher.AEAD nonce []byte input []byte ready chan struct{} task chan Task output chan []byte written chan struct{} } 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 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.input, uint32(task.size)) output = w.aead.Seal( w.input[:LenSize], w.nonce, w.input[LenSize:LenSize+task.size], w.input[:LenSize], ) } w.output <- output <-w.written wg.Done() } } func main() { flag.Parse() if *doPSK { key := make([]byte, chacha20poly1305.KeySize) if _, err := io.ReadFull(rand.Reader, key); err != nil { panic(err) } fmt.Println(Base32Codec.EncodeToString(key)) return } var err error key, err = Base32Codec.DecodeString(*keyB32) if err != nil { panic(err) } if len(key) != chacha20poly1305.KeySize { panic("Invalid key size") } 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) } } bs = *blockSize * (1 << 10) stdin := bufio.NewReaderSize(os.Stdin, LenSize+bs+poly1305.TagSize) workers := make([]*Worker, *threads) for i := 0; i < *threads; i++ { workers[i] = NewWorker(key, salt) } go func() { var ctr uint64 for { w := workers[ctr%uint64(len(workers))] if _, err := os.Stdout.Write(<-w.output); err != nil { panic(err) } w.written <- struct{}{} ctr++ } }() var ctr uint64 var size int for { w := workers[ctr%uint64(len(workers))] <-w.ready if *decrypt { _, err = io.ReadFull(stdin, w.input[:LenSize]) if err != nil { if err == io.EOF { break } panic(err) } 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 { size, err = stdin.Read(w.input[LenSize : LenSize+bs]) if err != nil { if err == io.EOF { break } panic(err) } } wg.Add(1) w.task <- Task{ctr, size} ctr++ } wg.Wait() }