1 // gohpenc -- Go high-performance encryption utility
2 // Copyright (C) 2017-2024 Sergey Matveev <stargrave@stargrave.org>
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, version 3 of the License.
8 // This program is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 // GNU General Public License for more details.
13 // You should have received a copy of the GNU General Public License
14 // along with this program. If not, see <http://www.gnu.org/licenses/>.
16 // Go high-performance encryption utility
33 "golang.org/x/crypto/chacha20"
34 "golang.org/x/crypto/chacha20poly1305"
35 "golang.org/x/crypto/hkdf"
36 "golang.org/x/crypto/poly1305"
44 var Base32Codec *base32.Encoding = base32.StdEncoding.WithPadding(base32.NoPadding)
50 readyOut chan struct{}
54 func readBuf(dst []byte, src io.Reader) ([]byte, error) {
58 n, err = src.Read(dst[full:])
67 return dst[:full], err
70 type DummyReader struct{}
72 func (r *DummyReader) Read(b []byte) (int, error) {
77 doRNG := flag.Bool("r", false, "Random number generator")
78 doGen := flag.Bool("psk", false, "Generate key")
79 doDec := flag.Bool("d", false, "Decrypt, instead of encrypt")
80 bs := flag.Int("b", 1<<10, "Blocksize, KiB")
81 jobs := flag.Int("c", runtime.NumCPU(), "Number of parallel threads")
82 keyB32 := flag.String("k", "", "Encryption key")
84 log.SetFlags(log.Ldate | log.Lmicroseconds | log.Lshortfile)
87 key := make([]byte, chacha20poly1305.KeySize)
89 if _, err := io.ReadFull(rand.Reader, key); err != nil {
93 fmt.Println(Base32Codec.EncodeToString(key))
97 key, err = Base32Codec.DecodeString(*keyB32)
101 if len(key) != chacha20poly1305.KeySize {
102 log.Fatalln("invalid key size")
106 salt := make([]byte, SaltSize)
108 if _, err = io.ReadFull(os.Stdin, salt[:len(Magic)]); err != nil {
111 if string(salt[:len(Magic)]) != Magic {
112 log.Fatalln("invalid magic")
114 if _, err = io.ReadFull(os.Stdin, salt[:4]); err != nil {
117 *bs = int(binary.BigEndian.Uint32(salt[:4]))
118 if _, err = io.ReadFull(os.Stdin, salt); err != nil {
122 if _, err = os.Stdout.WriteString(Magic); err != nil {
126 binary.BigEndian.PutUint32(salt, uint32(*bs))
127 if _, err = os.Stdout.Write(salt[:4]); err != nil {
130 if _, err = io.ReadFull(rand.Reader, salt); err != nil {
133 if _, err = os.Stdout.Write(salt); err != nil {
138 kdf := hkdf.New(sha512.New, key, salt, []byte(Magic))
139 if _, err = io.ReadFull(kdf, key); err != nil {
143 var wg sync.WaitGroup
145 workers := make([]*Worker, 0, *jobs)
146 for i := 0; i < *jobs; i++ {
148 buf: make([]byte, *bs+chacha20poly1305.Overhead),
149 readyIn: make(chan struct{}),
150 readyOut: make(chan struct{}),
153 ciph, err := chacha20poly1305.New(key)
157 nonce := make([]byte, chacha20poly1305.NonceSize)
158 var ciphertext, tag []byte
159 var s *chacha20.Cipher
162 w.readyIn <- struct{}{}
164 binary.BigEndian.PutUint64(nonce, w.ctr)
166 tag = w.buf[len(w.buf)-poly1305.TagSize:]
167 ciphertext = w.buf[:len(w.buf)-poly1305.TagSize]
168 s, err = chacha20.NewUnauthenticatedCipher(key, nonce)
173 s.XORKeyStream(polyKey[:], polyKey[:])
175 p = poly1305.New(&polyKey)
176 writeWithPadding(p, nil)
177 writeWithPadding(p, ciphertext)
179 writeUint64(p, len(ciphertext))
182 s.XORKeyStream(ciphertext, ciphertext)
185 if _, err = io.ReadFull(kdf, key); err != nil {
188 ciph, err = chacha20poly1305.New(key)
192 w.buf, err = ciph.Open(w.buf[:0], nonce, w.buf, nil)
200 if _, err = io.ReadFull(kdf, key); err != nil {
203 ciph, err = chacha20poly1305.New(key)
208 w.buf = ciph.Seal(w.buf[:0], nonce, w.buf, nil)
210 w.readyOut <- struct{}{}
215 workers = append(workers, &w)
223 w = workers[ctr%int64(len(workers))]
225 if _, err = os.Stdout.Write(w.buf); err != nil {
228 w.readyOut <- struct{}{}
235 stdin = &DummyReader{}
237 stdin = bufio.NewReaderSize(os.Stdin, *bs+chacha20poly1305.Overhead)
242 w = workers[ctr%uint64(len(workers))]
245 w.buf, err = readBuf(w.buf[:*bs+chacha20poly1305.Overhead], stdin)
247 w.buf, err = readBuf(w.buf[:*bs], stdin)
249 if err != nil && err != io.EOF {
252 if *doDec && len(w.buf) < chacha20poly1305.Overhead {
260 w.readyIn <- struct{}{}
267 if *doDec && !lastMet {
268 log.Fatalln("did not meet explicit last block")