Skip to content

Commit 3d794c2

Browse files
authored
perf: Add AES key caching (#189)
* test: Add AES password benchmark * perf: Add AES key caching * style: Lint fixes
1 parent 31f77ef commit 3d794c2

File tree

8 files changed

+77
-23
lines changed

8 files changed

+77
-23
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ require (
77
github.com/bodgit/plumbing v1.3.0
88
github.com/bodgit/windows v1.0.1
99
github.com/hashicorp/go-multierror v1.1.1
10+
github.com/hashicorp/golang-lru/v2 v2.0.7
1011
github.com/klauspost/compress v1.17.7
1112
github.com/pierrec/lz4/v4 v4.1.21
1213
github.com/stretchr/testify v1.9.0

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+l
6666
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
6767
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
6868
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
69+
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
70+
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
6971
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
7072
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
7173
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=

internal/aes7z/key.go

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,47 @@ import (
44
"bytes"
55
"crypto/sha256"
66
"encoding/binary"
7+
"encoding/hex"
78

9+
lru "github.com/hashicorp/golang-lru/v2"
10+
"go4.org/syncutil"
811
"golang.org/x/text/encoding/unicode"
912
"golang.org/x/text/transform"
1013
)
1114

12-
func calculateKey(password string, cycles int, salt []byte) []byte {
15+
type cacheKey struct {
16+
password string
17+
cycles int
18+
salt string // []byte isn't comparable
19+
}
20+
21+
const cacheSize = 10
22+
23+
//nolint:gochecknoglobals
24+
var (
25+
once syncutil.Once
26+
cache *lru.Cache[cacheKey, []byte]
27+
)
28+
29+
func calculateKey(password string, cycles int, salt []byte) ([]byte, error) {
30+
if err := once.Do(func() (err error) {
31+
cache, err = lru.New[cacheKey, []byte](cacheSize)
32+
33+
return
34+
}); err != nil {
35+
return nil, err
36+
}
37+
38+
ck := cacheKey{
39+
password: password,
40+
cycles: cycles,
41+
salt: hex.EncodeToString(salt),
42+
}
43+
44+
if key, ok := cache.Get(ck); ok {
45+
return key, nil
46+
}
47+
1348
b := bytes.NewBuffer(salt)
1449

1550
// Convert password to UTF-16LE
@@ -30,5 +65,7 @@ func calculateKey(password string, cycles int, salt []byte) []byte {
3065
copy(key, h.Sum(nil))
3166
}
3267

33-
return key
68+
_ = cache.Add(ck, key)
69+
70+
return key, nil
3471
}

internal/aes7z/reader.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,12 @@ func (rc *readCloser) Close() error {
2929
}
3030

3131
func (rc *readCloser) Password(p string) error {
32-
block, err := aes.NewCipher(calculateKey(p, rc.cycles, rc.salt))
32+
key, err := calculateKey(p, rc.cycles, rc.salt)
33+
if err != nil {
34+
return err
35+
}
36+
37+
block, err := aes.NewCipher(key)
3338
if err != nil {
3439
return err
3540
}

internal/zstd/reader.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ func NewReader(_ []byte, _ uint64, readers []io.ReadCloser) (io.ReadCloser, erro
5252
if r, err = zstd.NewReader(readers[0]); err != nil {
5353
return nil, err
5454
}
55+
5556
runtime.SetFinalizer(r, (*zstd.Decoder).Close)
5657
}
5758

reader.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -544,7 +544,7 @@ func toValidName(name string) string {
544544
return p
545545
}
546546

547-
//nolint:cyclop
547+
//nolint:cyclop,funlen
548548
func (z *Reader) initFileList() {
549549
z.fileListOnce.Do(func() {
550550
files := make(map[string]int)
@@ -583,12 +583,14 @@ func (z *Reader) initFileList() {
583583
isDir: isDir,
584584
}
585585
z.fileList = append(z.fileList, entry)
586+
586587
if isDir {
587588
knownDirs[name] = idx
588589
} else {
589590
files[name] = idx
590591
}
591592
}
593+
592594
for dir := range dirs {
593595
if _, ok := knownDirs[dir]; !ok {
594596
if idx, ok := files[dir]; ok {

reader_test.go

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ func TestOpenReader(t *testing.T) {
188188

189189
t.Run(table.name, func(t *testing.T) {
190190
t.Parallel()
191+
191192
r, err := sevenzip.OpenReader(filepath.Join("testdata", table.file))
192193
if err != nil {
193194
assert.ErrorIs(t, err, table.err)
@@ -243,6 +244,7 @@ func TestOpenReaderWithPassword(t *testing.T) {
243244

244245
t.Run(table.name, func(t *testing.T) {
245246
t.Parallel()
247+
246248
r, err := sevenzip.OpenReaderWithPassword(filepath.Join("testdata", table.file), table.password)
247249
if err != nil {
248250
t.Fatal(err)
@@ -362,13 +364,13 @@ func benchmarkArchiveNaiveParallel(b *testing.B, file string, workers int) {
362364
}
363365
}
364366

365-
func benchmarkArchive(b *testing.B, file string, optimised bool) {
367+
func benchmarkArchive(b *testing.B, file, password string, optimised bool) {
366368
b.Helper()
367369

368370
h := crc32.NewIEEE()
369371

370372
for n := 0; n < b.N; n++ {
371-
r, err := sevenzip.OpenReader(filepath.Join("testdata", file))
373+
r, err := sevenzip.OpenReaderWithPassword(filepath.Join("testdata", file), password)
372374
if err != nil {
373375
b.Fatal(err)
374376
}
@@ -382,56 +384,60 @@ func benchmarkArchive(b *testing.B, file string, optimised bool) {
382384
}
383385
}
384386

387+
func BenchmarkAES7z(b *testing.B) {
388+
benchmarkArchive(b, "aes7z.7z", "password", true)
389+
}
390+
385391
func BenchmarkBzip2(b *testing.B) {
386-
benchmarkArchive(b, "bzip2.7z", true)
392+
benchmarkArchive(b, "bzip2.7z", "", true)
387393
}
388394

389395
func BenchmarkCopy(b *testing.B) {
390-
benchmarkArchive(b, "copy.7z", true)
396+
benchmarkArchive(b, "copy.7z", "", true)
391397
}
392398

393399
func BenchmarkDeflate(b *testing.B) {
394-
benchmarkArchive(b, "deflate.7z", true)
400+
benchmarkArchive(b, "deflate.7z", "", true)
395401
}
396402

397403
func BenchmarkDelta(b *testing.B) {
398-
benchmarkArchive(b, "delta.7z", true)
404+
benchmarkArchive(b, "delta.7z", "", true)
399405
}
400406

401407
func BenchmarkLZMA(b *testing.B) {
402-
benchmarkArchive(b, "lzma.7z", true)
408+
benchmarkArchive(b, "lzma.7z", "", true)
403409
}
404410

405411
func BenchmarkLZMA2(b *testing.B) {
406-
benchmarkArchive(b, "lzma2.7z", true)
412+
benchmarkArchive(b, "lzma2.7z", "", true)
407413
}
408414

409415
func BenchmarkBCJ2(b *testing.B) {
410-
benchmarkArchive(b, "bcj2.7z", true)
416+
benchmarkArchive(b, "bcj2.7z", "", true)
411417
}
412418

413419
func BenchmarkComplex(b *testing.B) {
414-
benchmarkArchive(b, "lzma1900.7z", true)
420+
benchmarkArchive(b, "lzma1900.7z", "", true)
415421
}
416422

417423
func BenchmarkLZ4(b *testing.B) {
418-
benchmarkArchive(b, "lz4.7z", true)
424+
benchmarkArchive(b, "lz4.7z", "", true)
419425
}
420426

421427
func BenchmarkBrotli(b *testing.B) {
422-
benchmarkArchive(b, "brotli.7z", true)
428+
benchmarkArchive(b, "brotli.7z", "", true)
423429
}
424430

425431
func BenchmarkZstandard(b *testing.B) {
426-
benchmarkArchive(b, "zstd.7z", true)
432+
benchmarkArchive(b, "zstd.7z", "", true)
427433
}
428434

429435
func BenchmarkNaiveReader(b *testing.B) {
430-
benchmarkArchive(b, "lzma1900.7z", false)
436+
benchmarkArchive(b, "lzma1900.7z", "", false)
431437
}
432438

433439
func BenchmarkOptimisedReader(b *testing.B) {
434-
benchmarkArchive(b, "lzma1900.7z", true)
440+
benchmarkArchive(b, "lzma1900.7z", "", true)
435441
}
436442

437443
func BenchmarkNaiveParallelReader(b *testing.B) {
@@ -447,17 +453,17 @@ func BenchmarkParallelReader(b *testing.B) {
447453
}
448454

449455
func BenchmarkBCJ(b *testing.B) {
450-
benchmarkArchive(b, "bcj.7z", true)
456+
benchmarkArchive(b, "bcj.7z", "", true)
451457
}
452458

453459
func BenchmarkPPC(b *testing.B) {
454-
benchmarkArchive(b, "ppc.7z", true)
460+
benchmarkArchive(b, "ppc.7z", "", true)
455461
}
456462

457463
func BenchmarkARM(b *testing.B) {
458-
benchmarkArchive(b, "arm.7z", true)
464+
benchmarkArchive(b, "arm.7z", "", true)
459465
}
460466

461467
func BenchmarkSPARC(b *testing.B) {
462-
benchmarkArchive(b, "sparc.7z", true)
468+
benchmarkArchive(b, "sparc.7z", "", true)
463469
}

testdata/aes7z.7z

35.7 KB
Binary file not shown.

0 commit comments

Comments
 (0)