github.com/blend/go-sdk@v1.20220411.3/crypto/stream.go (about)

     1  /*
     2  
     3  Copyright (c) 2022 - Present. Blend Labs, Inc. All rights reserved
     4  Use of this source code is governed by a MIT license that can be found in the LICENSE file.
     5  
     6  */
     7  
     8  package crypto
     9  
    10  import (
    11  	"crypto/aes"
    12  	"crypto/cipher"
    13  	"crypto/hmac"
    14  	"crypto/rand"
    15  	"crypto/sha256"
    16  	"hash"
    17  	"io"
    18  
    19  	"github.com/blend/go-sdk/ex"
    20  )
    21  
    22  // NewStreamEncrypter creates a new stream encrypter
    23  func NewStreamEncrypter(encKey, macKey []byte, plainText io.Reader) (*StreamEncrypter, error) {
    24  	block, err := aes.NewCipher(encKey)
    25  	if err != nil {
    26  		return nil, ex.New(err)
    27  	}
    28  	iv := make([]byte, block.BlockSize())
    29  	_, err = rand.Read(iv)
    30  	if err != nil {
    31  		return nil, ex.New(err)
    32  	}
    33  	stream := cipher.NewCTR(block, iv)
    34  	mac := hmac.New(sha256.New, macKey)
    35  	return &StreamEncrypter{
    36  		Source: plainText,
    37  		Block:  block,
    38  		Stream: stream,
    39  		Mac:    mac,
    40  		IV:     iv,
    41  	}, nil
    42  }
    43  
    44  // NewStreamDecrypter creates a new stream decrypter
    45  func NewStreamDecrypter(encKey, macKey []byte, meta StreamMeta, cipherText io.Reader) (*StreamDecrypter, error) {
    46  	block, err := aes.NewCipher(encKey)
    47  	if err != nil {
    48  		return nil, ex.New(err)
    49  	}
    50  	stream := cipher.NewCTR(block, meta.IV)
    51  	mac := hmac.New(sha256.New, macKey)
    52  	return &StreamDecrypter{
    53  		Source: cipherText,
    54  		Block:  block,
    55  		Stream: stream,
    56  		Mac:    mac,
    57  		Meta:   meta,
    58  	}, nil
    59  }
    60  
    61  // StreamEncrypter is an encrypter for a stream of data with authentication
    62  type StreamEncrypter struct {
    63  	Source io.Reader
    64  	Block  cipher.Block
    65  	Stream cipher.Stream
    66  	Mac    hash.Hash
    67  	IV     []byte
    68  }
    69  
    70  // StreamDecrypter is a decrypter for a stream of data with authentication
    71  type StreamDecrypter struct {
    72  	Source io.Reader
    73  	Block  cipher.Block
    74  	Stream cipher.Stream
    75  	Mac    hash.Hash
    76  	Meta   StreamMeta
    77  }
    78  
    79  // Read encrypts the bytes of the inner reader and places them into p
    80  func (s *StreamEncrypter) Read(p []byte) (int, error) {
    81  	n, readErr := s.Source.Read(p)
    82  	if n > 0 {
    83  		s.Stream.XORKeyStream(p[:n], p[:n])
    84  		err := writeHash(s.Mac, p[:n])
    85  		if err != nil {
    86  			return n, ex.New(err)
    87  		}
    88  		return n, readErr
    89  	}
    90  	return 0, io.EOF
    91  }
    92  
    93  // Meta returns the encrypted stream metadata for use in decrypting. This should only be called after the stream is finished
    94  func (s *StreamEncrypter) Meta() StreamMeta {
    95  	return StreamMeta{IV: s.IV, Hash: s.Mac.Sum(nil)}
    96  }
    97  
    98  // Read reads bytes from the underlying reader and then decrypts them
    99  func (s *StreamDecrypter) Read(p []byte) (int, error) {
   100  	n, readErr := s.Source.Read(p)
   101  	if n > 0 {
   102  		err := writeHash(s.Mac, p[:n])
   103  		if err != nil {
   104  			return n, ex.New(err)
   105  		}
   106  		s.Stream.XORKeyStream(p[:n], p[:n])
   107  		return n, readErr
   108  	}
   109  	return 0, io.EOF
   110  }
   111  
   112  // Authenticate verifys that the hash of the stream is correct. This should only be called after processing is finished
   113  func (s *StreamDecrypter) Authenticate() error {
   114  	if !hmac.Equal(s.Meta.Hash, s.Mac.Sum(nil)) {
   115  		return ex.New("authentication failed")
   116  	}
   117  	return nil
   118  }
   119  
   120  func writeHash(mac hash.Hash, p []byte) error {
   121  	m, err := mac.Write(p)
   122  	if err != nil {
   123  		return ex.New(err)
   124  	}
   125  	if m != len(p) {
   126  		return ex.New("could not write all bytes to hmac")
   127  	}
   128  	return nil
   129  }
   130  
   131  func checkedWrite(dst io.Writer, p []byte) (int, error) {
   132  	n, err := dst.Write(p)
   133  	if err != nil {
   134  		return n, ex.New(err)
   135  	}
   136  	if n != len(p) {
   137  		return n, ex.New("unable to write all bytes")
   138  	}
   139  	return len(p), nil
   140  }
   141  
   142  // StreamMeta is metadata about an encrypted stream
   143  type StreamMeta struct {
   144  	// IV is the initial value for the crypto function
   145  	IV []byte
   146  	// Hash is the sha256 hmac of the stream
   147  	Hash []byte
   148  }