forked from TrueCloudLab/restic
crypto: Add buffer management to writer
benchmarks: benchmark old ns/op new ns/op delta BenchmarkEncryptWriter 37032949 37754353 +1.95% BenchmarkEncrypt 35989611 36344593 +0.99% BenchmarkDecryptReader 38274466 38282979 +0.02% BenchmarkEncryptDecryptReader 77506506 77088612 -0.54% BenchmarkDecrypt 36219298 36128875 -0.25% benchmark old MB/s new MB/s speedup BenchmarkEncryptWriter 226.52 222.19 0.98x BenchmarkEncrypt 233.08 230.81 0.99x BenchmarkDecryptReader 219.17 219.12 1.00x BenchmarkEncryptDecryptReader 108.23 108.82 1.01x BenchmarkDecrypt 231.61 232.19 1.00x benchmark old allocs new allocs delta BenchmarkEncryptWriter 20 16 -20.00% BenchmarkEncrypt 12 12 +0.00% BenchmarkDecryptReader 13 13 +0.00% BenchmarkEncryptDecryptReader 32 27 -15.62% BenchmarkDecrypt 10 10 +0.00% benchmark old bytes new bytes delta BenchmarkEncryptWriter 1020064 170331 -83.30% BenchmarkEncrypt 1600 1600 +0.00% BenchmarkDecryptReader 841070 842094 +0.12% BenchmarkEncryptDecryptReader 3027433 845129 -72.08% BenchmarkDecrypt 1573 1573 +0.00%
This commit is contained in:
parent
387082f983
commit
9c43688d1a
2 changed files with 44 additions and 70 deletions
|
@ -2,7 +2,7 @@ package crypto
|
||||||
|
|
||||||
import "sync"
|
import "sync"
|
||||||
|
|
||||||
const defaultBufSize = 2048
|
const defaultBufSize = 32 * 1024 // 32KiB
|
||||||
|
|
||||||
var bufPool = sync.Pool{
|
var bufPool = sync.Pool{
|
||||||
New: func() interface{} {
|
New: func() interface{} {
|
||||||
|
|
104
crypto/writer.go
104
crypto/writer.go
|
@ -1,110 +1,84 @@
|
||||||
package crypto
|
package crypto
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"crypto/aes"
|
"crypto/aes"
|
||||||
"crypto/cipher"
|
"crypto/cipher"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"sync"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type encryptWriter struct {
|
type encryptWriter struct {
|
||||||
iv []byte
|
data []byte
|
||||||
wroteIV bool
|
|
||||||
data *bytes.Buffer
|
|
||||||
key *Key
|
key *Key
|
||||||
s cipher.Stream
|
s cipher.Stream
|
||||||
w io.Writer
|
w io.Writer
|
||||||
origWr io.Writer
|
closed bool
|
||||||
err error // remember error writing iv
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *encryptWriter) Close() error {
|
func (e *encryptWriter) Close() error {
|
||||||
// write mac
|
if e.closed {
|
||||||
mac := poly1305Sign(e.data.Bytes()[ivSize:], e.data.Bytes()[:ivSize], &e.key.Sign)
|
return errors.New("Close() called on already closed writer")
|
||||||
_, err := e.origWr.Write(mac)
|
}
|
||||||
|
e.closed = true
|
||||||
|
|
||||||
|
// encrypt everything
|
||||||
|
iv, c := e.data[:ivSize], e.data[ivSize:]
|
||||||
|
e.s.XORKeyStream(c, c)
|
||||||
|
|
||||||
|
// compute mac
|
||||||
|
mac := poly1305Sign(c, iv, &e.key.Sign)
|
||||||
|
e.data = append(e.data, mac...)
|
||||||
|
|
||||||
|
// write everything
|
||||||
|
n, err := e.w.Write(e.data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// return buffer
|
if n != len(e.data) {
|
||||||
bufPool.Put(e.data.Bytes())
|
return errors.New("not all bytes written")
|
||||||
|
}
|
||||||
|
|
||||||
|
// return buffer to pool
|
||||||
|
freeBuffer(e.data)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
const encryptWriterChunkSize = 512 * 1024 // 512 KiB
|
|
||||||
var encryptWriterBufPool = sync.Pool{
|
|
||||||
New: func() interface{} {
|
|
||||||
return make([]byte, encryptWriterChunkSize)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *encryptWriter) Write(p []byte) (int, error) {
|
func (e *encryptWriter) Write(p []byte) (int, error) {
|
||||||
// write iv first
|
// if e.data is too small, return it to the buffer and create new slice
|
||||||
if !e.wroteIV {
|
if cap(e.data) < len(e.data)+len(p) {
|
||||||
_, e.err = e.origWr.Write(e.iv[:])
|
b := make([]byte, len(e.data), len(e.data)*2)
|
||||||
e.wroteIV = true
|
copy(b, e.data)
|
||||||
|
freeBuffer(e.data)
|
||||||
|
e.data = b
|
||||||
}
|
}
|
||||||
|
|
||||||
if e.err != nil {
|
// copy new data to e.data
|
||||||
return 0, e.err
|
e.data = append(e.data, p...)
|
||||||
}
|
return len(p), nil
|
||||||
|
|
||||||
buf := encryptWriterBufPool.Get().([]byte)
|
|
||||||
defer encryptWriterBufPool.Put(buf)
|
|
||||||
|
|
||||||
written := 0
|
|
||||||
for len(p) > 0 {
|
|
||||||
max := len(p)
|
|
||||||
if max > encryptWriterChunkSize {
|
|
||||||
max = encryptWriterChunkSize
|
|
||||||
}
|
|
||||||
|
|
||||||
e.s.XORKeyStream(buf, p[:max])
|
|
||||||
n, err := e.w.Write(buf[:max])
|
|
||||||
if n != max {
|
|
||||||
if err == nil { // should never happen
|
|
||||||
err = io.ErrShortWrite
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
written += n
|
|
||||||
p = p[n:]
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
e.err = err
|
|
||||||
return written, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return written, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// EncryptTo buffers data written to the returned io.WriteCloser. When Close()
|
// EncryptTo buffers data written to the returned io.WriteCloser. When Close()
|
||||||
// is called, the data is encrypted an written to the underlying writer.
|
// is called, the data is encrypted and written to the underlying writer.
|
||||||
func EncryptTo(ks *Key, wr io.Writer) io.WriteCloser {
|
func EncryptTo(ks *Key, wr io.Writer) io.WriteCloser {
|
||||||
ew := &encryptWriter{
|
ew := &encryptWriter{
|
||||||
iv: newIV(),
|
data: getBuffer(),
|
||||||
data: bytes.NewBuffer(getBuffer()[:0]),
|
|
||||||
key: ks,
|
key: ks,
|
||||||
origWr: wr,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// buffer iv for mac
|
// buffer iv for mac
|
||||||
_, err := ew.data.Write(ew.iv[:])
|
ew.data = ew.data[:ivSize]
|
||||||
if err != nil {
|
copy(ew.data, newIV())
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
c, err := aes.NewCipher(ks.Encrypt[:])
|
c, err := aes.NewCipher(ks.Encrypt[:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(fmt.Sprintf("unable to create cipher: %v", err))
|
panic(fmt.Sprintf("unable to create cipher: %v", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
ew.s = cipher.NewCTR(c, ew.iv[:])
|
ew.s = cipher.NewCTR(c, ew.data[:ivSize])
|
||||||
ew.w = io.MultiWriter(ew.data, wr)
|
ew.w = wr
|
||||||
|
|
||||||
return ew
|
return ew
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue