2 gohpenc -- Go high-performance encryption utility
3 Copyright (C) 2017-2023 Sergey Matveev <stargrave@stargrave.org>
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, version 3 of the License.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <http://www.gnu.org/licenses/>.
18 // Go high-performance encryption utility
35 "golang.org/x/crypto/chacha20"
36 "golang.org/x/crypto/chacha20poly1305"
37 "golang.org/x/crypto/hkdf"
38 "golang.org/x/crypto/poly1305"
46 var Base32Codec *base32.Encoding = base32.StdEncoding.WithPadding(base32.NoPadding)
52 readyOut chan struct{}
56 func readBuf(dst []byte, src io.Reader) ([]byte, error) {
60 n, err = src.Read(dst[full:])
69 return dst[:full], err
72 type DummyReader struct{}
74 func (r *DummyReader) Read(b []byte) (int, error) {
79 doRNG := flag.Bool("r", false, "Random number generator")
80 doGen := flag.Bool("psk", false, "Generate key")
81 doDec := flag.Bool("d", false, "Decrypt, instead of encrypt")
82 bs := flag.Int("b", 1<<10, "Blocksize, KiB")
83 jobs := flag.Int("c", runtime.NumCPU(), "Number of parallel threads")
84 keyB32 := flag.String("k", "", "Encryption key")
86 log.SetFlags(log.Ldate | log.Lmicroseconds | log.Lshortfile)
89 key := make([]byte, chacha20poly1305.KeySize)
91 if _, err := io.ReadFull(rand.Reader, key); err != nil {
95 fmt.Println(Base32Codec.EncodeToString(key))
99 key, err = Base32Codec.DecodeString(*keyB32)
103 if len(key) != chacha20poly1305.KeySize {
104 log.Fatalln("invalid key size")
108 salt := make([]byte, SaltSize)
110 if _, err = io.ReadFull(os.Stdin, salt[:len(Magic)]); err != nil {
113 if string(salt[:len(Magic)]) != Magic {
114 log.Fatalln("invalid magic")
116 if _, err = io.ReadFull(os.Stdin, salt[:4]); err != nil {
119 *bs = int(binary.BigEndian.Uint32(salt[:4]))
120 if _, err = io.ReadFull(os.Stdin, salt); err != nil {
124 if _, err = os.Stdout.WriteString(Magic); err != nil {
128 binary.BigEndian.PutUint32(salt, uint32(*bs))
129 if _, err = os.Stdout.Write(salt[:4]); err != nil {
132 if _, err = io.ReadFull(rand.Reader, salt); err != nil {
135 if _, err = os.Stdout.Write(salt); err != nil {
140 kdf := hkdf.New(sha512.New, key, salt, []byte(Magic))
141 if _, err = io.ReadFull(kdf, key); err != nil {
145 var wg sync.WaitGroup
147 workers := make([]*Worker, 0, *jobs)
148 for i := 0; i < *jobs; i++ {
150 buf: make([]byte, *bs+chacha20poly1305.Overhead),
151 readyIn: make(chan struct{}),
152 readyOut: make(chan struct{}),
155 ciph, err := chacha20poly1305.New(key)
159 nonce := make([]byte, chacha20poly1305.NonceSize)
160 var ciphertext, tag []byte
161 var s *chacha20.Cipher
164 w.readyIn <- struct{}{}
166 binary.BigEndian.PutUint64(nonce, w.ctr)
168 tag = w.buf[len(w.buf)-poly1305.TagSize:]
169 ciphertext = w.buf[:len(w.buf)-poly1305.TagSize]
170 s, err = chacha20.NewUnauthenticatedCipher(key, nonce)
175 s.XORKeyStream(polyKey[:], polyKey[:])
177 p = poly1305.New(&polyKey)
178 writeWithPadding(p, nil)
179 writeWithPadding(p, ciphertext)
181 writeUint64(p, len(ciphertext))
184 s.XORKeyStream(ciphertext, ciphertext)
187 if _, err = io.ReadFull(kdf, key); err != nil {
190 ciph, err = chacha20poly1305.New(key)
194 w.buf, err = ciph.Open(w.buf[:0], nonce, w.buf, nil)
202 if _, err = io.ReadFull(kdf, key); err != nil {
205 ciph, err = chacha20poly1305.New(key)
210 w.buf = ciph.Seal(w.buf[:0], nonce, w.buf, nil)
212 w.readyOut <- struct{}{}
217 workers = append(workers, &w)
225 w = workers[ctr%int64(len(workers))]
227 if _, err = os.Stdout.Write(w.buf); err != nil {
230 w.readyOut <- struct{}{}
237 stdin = &DummyReader{}
239 stdin = bufio.NewReaderSize(os.Stdin, *bs+chacha20poly1305.Overhead)
244 w = workers[ctr%uint64(len(workers))]
247 w.buf, err = readBuf(w.buf[:*bs+chacha20poly1305.Overhead], stdin)
249 w.buf, err = readBuf(w.buf[:*bs], stdin)
251 if err != nil && err != io.EOF {
254 if *doDec && len(w.buf) < chacha20poly1305.Overhead {
262 w.readyIn <- struct{}{}
269 if *doDec && !lastMet {
270 log.Fatalln("did not meet explicit last block")