]> Cypherpunks.ru repositories - gohpenc.git/blob - main.go
More secure and faster version
[gohpenc.git] / main.go
1 /*
2 gohpenc -- Go high-performance encryption utility
3 Copyright (C) 2017-2022 Sergey Matveev <stargrave@stargrave.org>
4
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.
8
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.
13
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/>.
16 */
17
18 // Go high-performance encryption utility
19 package main
20
21 import (
22         "bufio"
23         "crypto/rand"
24         "crypto/sha512"
25         "encoding/base32"
26         "encoding/binary"
27         "flag"
28         "fmt"
29         "io"
30         "log"
31         "os"
32         "runtime"
33         "sync"
34
35         "golang.org/x/crypto/chacha20"
36         "golang.org/x/crypto/chacha20poly1305"
37         "golang.org/x/crypto/hkdf"
38         "golang.org/x/crypto/poly1305"
39 )
40
41 const (
42         Magic    = "GOHPENC\n"
43         SaltSize = 16
44 )
45
46 var Base32Codec *base32.Encoding = base32.StdEncoding.WithPadding(base32.NoPadding)
47
48 type Worker struct {
49         ctr      uint64
50         buf      []byte
51         readyIn  chan struct{}
52         readyOut chan struct{}
53         last     bool
54 }
55
56 func readBuf(dst []byte, src io.Reader) ([]byte, error) {
57         var n, full int
58         var err error
59         for full < len(dst) {
60                 n, err = src.Read(dst[full:])
61                 full += n
62                 if err != nil {
63                         if err == io.EOF {
64                                 break
65                         }
66                         return nil, err
67                 }
68         }
69         return dst[:full], err
70 }
71
72 type DummyReader struct{}
73
74 func (r *DummyReader) Read(b []byte) (int, error) {
75         return len(b), nil
76 }
77
78 func main() {
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")
85         flag.Parse()
86         log.SetFlags(log.Ldate | log.Lmicroseconds | log.Lshortfile)
87
88         var err error
89         key := make([]byte, chacha20poly1305.KeySize)
90         if *doGen || *doRNG {
91                 if _, err := io.ReadFull(rand.Reader, key); err != nil {
92                         log.Fatalln(err)
93                 }
94                 if *doGen {
95                         fmt.Println(Base32Codec.EncodeToString(key))
96                         return
97                 }
98         } else {
99                 key, err = Base32Codec.DecodeString(*keyB32)
100                 if err != nil {
101                         log.Fatalln(err)
102                 }
103                 if len(key) != chacha20poly1305.KeySize {
104                         log.Fatalln("invalid key size")
105                 }
106         }
107
108         salt := make([]byte, SaltSize)
109         if *doDec {
110                 if _, err = io.ReadFull(os.Stdin, salt[:len(Magic)]); err != nil {
111                         log.Fatalln(err)
112                 }
113                 if string(salt[:len(Magic)]) != Magic {
114                         log.Fatalln("invalid magic")
115                 }
116                 if _, err = io.ReadFull(os.Stdin, salt[:4]); err != nil {
117                         log.Fatalln(err)
118                 }
119                 *bs = int(binary.BigEndian.Uint32(salt[:4]))
120                 if _, err = io.ReadFull(os.Stdin, salt); err != nil {
121                         log.Fatalln(err)
122                 }
123         } else {
124                 if _, err = os.Stdout.WriteString(Magic); err != nil {
125                         log.Fatalln(err)
126                 }
127                 *bs = *bs * 1024
128                 binary.BigEndian.PutUint32(salt, uint32(*bs))
129                 if _, err = os.Stdout.Write(salt[:4]); err != nil {
130                         log.Fatalln(err)
131                 }
132                 if _, err = io.ReadFull(rand.Reader, salt); err != nil {
133                         log.Fatalln(err)
134                 }
135                 if _, err = os.Stdout.Write(salt); err != nil {
136                         log.Fatalln(err)
137                 }
138         }
139
140         kdf := hkdf.New(sha512.New, key, salt, []byte(Magic))
141         if _, err = io.ReadFull(kdf, key); err != nil {
142                 log.Fatalln(err)
143         }
144
145         var wg sync.WaitGroup
146         var lastMet bool
147         workers := make([]*Worker, 0, *jobs)
148         for i := 0; i < *jobs; i++ {
149                 w := Worker{
150                         buf:      make([]byte, *bs+chacha20poly1305.Overhead),
151                         readyIn:  make(chan struct{}),
152                         readyOut: make(chan struct{}),
153                 }
154                 go func() {
155                         ciph, err := chacha20poly1305.New(key)
156                         if err != nil {
157                                 log.Fatalln(err)
158                         }
159                         nonce := make([]byte, chacha20poly1305.NonceSize)
160                         var ciphertext, tag []byte
161                         var s *chacha20.Cipher
162                         var p *poly1305.MAC
163                         for {
164                                 w.readyIn <- struct{}{}
165                                 <-w.readyIn
166                                 binary.BigEndian.PutUint64(nonce, w.ctr)
167                                 if *doDec {
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)
171                                         if err != nil {
172                                                 log.Fatalln(err)
173                                         }
174                                         var polyKey [32]byte
175                                         s.XORKeyStream(polyKey[:], polyKey[:])
176                                         s.SetCounter(1)
177                                         p = poly1305.New(&polyKey)
178                                         writeWithPadding(p, nil)
179                                         writeWithPadding(p, ciphertext)
180                                         writeUint64(p, 0)
181                                         writeUint64(p, len(ciphertext))
182                                         if p.Verify(tag) {
183                                                 w.buf = ciphertext
184                                                 s.XORKeyStream(ciphertext, ciphertext)
185                                         } else {
186                                                 lastMet = true
187                                                 if _, err = io.ReadFull(kdf, key); err != nil {
188                                                         log.Fatalln(err)
189                                                 }
190                                                 ciph, err = chacha20poly1305.New(key)
191                                                 if err != nil {
192                                                         log.Fatalln(err)
193                                                 }
194                                                 w.buf, err = ciph.Open(w.buf[:0], nonce, w.buf, nil)
195                                                 if err != nil {
196                                                         log.Fatalln(err)
197                                                 }
198                                                 lastMet = true
199                                         }
200                                 } else {
201                                         if w.last {
202                                                 if _, err = io.ReadFull(kdf, key); err != nil {
203                                                         log.Fatalln(err)
204                                                 }
205                                                 ciph, err = chacha20poly1305.New(key)
206                                                 if err != nil {
207                                                         log.Fatalln(err)
208                                                 }
209                                         }
210                                         w.buf = ciph.Seal(w.buf[:0], nonce, w.buf, nil)
211                                 }
212                                 w.readyOut <- struct{}{}
213                                 <-w.readyOut
214                                 wg.Done()
215                         }
216                 }()
217                 workers = append(workers, &w)
218         }
219
220         go func() {
221                 var ctr int64
222                 var w *Worker
223                 var err error
224                 for {
225                         w = workers[ctr%int64(len(workers))]
226                         <-w.readyOut
227                         if _, err = os.Stdout.Write(w.buf); err != nil {
228                                 log.Fatalln(err)
229                         }
230                         w.readyOut <- struct{}{}
231                         ctr++
232                 }
233         }()
234
235         var stdin io.Reader
236         if *doRNG {
237                 stdin = &DummyReader{}
238         } else {
239                 stdin = bufio.NewReaderSize(os.Stdin, *bs+chacha20poly1305.Overhead)
240         }
241         var ctr uint64
242         var w *Worker
243         for {
244                 w = workers[ctr%uint64(len(workers))]
245                 <-w.readyIn
246                 if *doDec {
247                         w.buf, err = readBuf(w.buf[:*bs+chacha20poly1305.Overhead], stdin)
248                 } else {
249                         w.buf, err = readBuf(w.buf[:*bs], stdin)
250                 }
251                 if err != nil && err != io.EOF {
252                         log.Fatalln(err)
253                 }
254                 if *doDec && len(w.buf) < chacha20poly1305.Overhead {
255                         break
256                 }
257                 w.ctr = ctr
258                 wg.Add(1)
259                 if err == io.EOF {
260                         w.last = true
261                 }
262                 w.readyIn <- struct{}{}
263                 if err == io.EOF {
264                         break
265                 }
266                 ctr++
267         }
268         wg.Wait()
269         if *doDec && !lastMet {
270                 log.Fatalln("did not meet explicit last block")
271         }
272 }