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

     1  package aes7z
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/aes"
     6  	"crypto/cipher"
     7  	"errors"
     8  	"io"
     9  )
    10  
    11  var errProperties = errors.New("aes7z: not enough properties")
    12  
    13  type readCloser struct {
    14  	rc       io.ReadCloser
    15  	salt, iv []byte
    16  	cycles   int
    17  	cbc      cipher.BlockMode
    18  	buf      bytes.Buffer
    19  }
    20  
    21  func (rc *readCloser) Close() error {
    22  	var err error
    23  	if rc.rc != nil {
    24  		err = rc.rc.Close()
    25  		rc.rc = nil
    26  	}
    27  
    28  	return err
    29  }
    30  
    31  func (rc *readCloser) Password(p string) error {
    32  	key, err := calculateKey(p, rc.cycles, rc.salt)
    33  	if err != nil {
    34  		return err
    35  	}
    36  
    37  	block, err := aes.NewCipher(key)
    38  	if err != nil {
    39  		return err
    40  	}
    41  
    42  	rc.cbc = cipher.NewCBCDecrypter(block, rc.iv)
    43  
    44  	return nil
    45  }
    46  
    47  func (rc *readCloser) Read(p []byte) (int, error) {
    48  	if rc.rc == nil {
    49  		return 0, errors.New("aes7z: Read after Close")
    50  	}
    51  
    52  	if rc.cbc == nil {
    53  		return 0, errors.New("aes7z: no password set")
    54  	}
    55  
    56  	var block [aes.BlockSize]byte
    57  
    58  	for rc.buf.Len() < len(p) {
    59  		if _, err := io.ReadFull(rc.rc, block[:]); err != nil {
    60  			if errors.Is(err, io.EOF) {
    61  				break
    62  			}
    63  
    64  			return 0, err
    65  		}
    66  
    67  		rc.cbc.CryptBlocks(block[:], block[:])
    68  
    69  		_, _ = rc.buf.Write(block[:])
    70  	}
    71  
    72  	return rc.buf.Read(p)
    73  }
    74  
    75  // NewReader returns a new AES-256-CBC & SHA-256 io.ReadCloser. The Password
    76  // method must be called before attempting to call Read so that the block
    77  // cipher is correctly initialised.
    78  func NewReader(p []byte, _ uint64, readers []io.ReadCloser) (io.ReadCloser, error) {
    79  	if len(readers) != 1 {
    80  		return nil, errors.New("aes7z: need exactly one reader")
    81  	}
    82  
    83  	// Need at least two bytes initially
    84  	if len(p) < 2 {
    85  		return nil, errProperties
    86  	}
    87  
    88  	if p[0]&0xc0 == 0 {
    89  		return nil, errors.New("aes7z: unsupported compression method")
    90  	}
    91  
    92  	rc := new(readCloser)
    93  
    94  	salt := p[0]>>7&1 + p[1]>>4
    95  	iv := p[0]>>6&1 + p[1]&0x0f
    96  
    97  	if len(p) != int(2+salt+iv) {
    98  		return nil, errProperties
    99  	}
   100  
   101  	rc.salt = p[2 : 2+salt]
   102  	rc.iv = make([]byte, aes.BlockSize)
   103  	copy(rc.iv, p[2+salt:])
   104  
   105  	rc.cycles = int(p[0] & 0x3f)
   106  	rc.rc = readers[0]
   107  
   108  	return rc, nil
   109  }