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