/* gohpenc -- Go high-performance encryption utility Copyright (C) 2017-2019 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/rand" "encoding/binary" "flag" "fmt" "io" "os" "runtime" "sync" "golang.org/x/crypto/blake2b" "golang.org/x/crypto/chacha20poly1305" ) const ( LenSize = 4 SaltSize = 32 ) type WorkerTask struct { key []byte n int } type Worker struct { wg *sync.WaitGroup buf []byte ready chan struct{} task chan WorkerTask done chan []byte written chan struct{} } func (w *Worker) Thread(doDecrypt bool) { var task WorkerTask nonce := make([]byte, chacha20poly1305.NonceSize) var done []byte 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]) if err != nil { panic(err) } } 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]) } w.done <- done <-w.written w.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 { panic(err) } fmt.Println(ToBase32(key)) return } if len(*key) != 52 { panic("Invalid key size") } keyDecoded, err := FromBase32(*key) if err != nil { panic(err) } tmpAEAD, err := chacha20poly1305.New(make([]byte, chacha20poly1305.KeySize)) if err != nil { panic(err) } keys, err := blake2b.NewXOF(blake2b.OutputLengthUnknown, keyDecoded) if err != nil { panic(err) } var wg sync.WaitGroup workers := make([]*Worker, 0, *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) } go func() { i := 0 var w *Worker for { w = workers[i%len(workers)] if _, err = os.Stdout.Write(<-w.done); err != nil { panic(err) } w.written <- struct{}{} i++ } }() 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 for { key := make([]byte, chacha20poly1305.KeySize) if _, err = io.ReadFull(keys, key); err != nil { panic(err) } w = workers[i%len(workers)] <-w.ready if *doDecrypt { _, err = io.ReadFull(stdin, w.buf[: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 { panic(err) } } else { n, err = stdin.Read(w.buf[LenSize : LenSize+bs]) if err != nil { if err == io.EOF { break } panic(err) } } wg.Add(1) w.task <- WorkerTask{key, n} i++ } wg.Wait() }