github.com/bodgit/sevenzip@v1.5.1/internal/aes7z/key.go (about)

     1  package aes7z
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/sha256"
     6  	"encoding/binary"
     7  	"encoding/hex"
     8  
     9  	lru "github.com/hashicorp/golang-lru/v2"
    10  	"go4.org/syncutil"
    11  	"golang.org/x/text/encoding/unicode"
    12  	"golang.org/x/text/transform"
    13  )
    14  
    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  
    48  	b := bytes.NewBuffer(salt)
    49  
    50  	// Convert password to UTF-16LE
    51  	utf16le := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM)
    52  	t := transform.NewWriter(b, utf16le.NewEncoder())
    53  	_, _ = t.Write([]byte(password))
    54  
    55  	key := make([]byte, sha256.Size)
    56  	if cycles == 0x3f {
    57  		copy(key, b.Bytes())
    58  	} else {
    59  		h := sha256.New()
    60  		for i := uint64(0); i < 1<<cycles; i++ {
    61  			// These will never error
    62  			_, _ = h.Write(b.Bytes())
    63  			_ = binary.Write(h, binary.LittleEndian, i)
    64  		}
    65  		copy(key, h.Sum(nil))
    66  	}
    67  
    68  	_ = cache.Add(ck, key)
    69  
    70  	return key, nil
    71  }