github.com/grailbio/base@v0.0.11/crypto/encryption/encryption.go (about)

     1  // Copyright 2017 GRAIL, Inc. All rights reserved.
     2  // Use of this source code is governed by the Apache-2.0
     3  // license that can be found in the LICENSE file.
     4  
     5  package encryption
     6  
     7  import (
     8  	"crypto/cipher"
     9  	"crypto/hmac"
    10  	"crypto/rand"
    11  	"encoding/hex"
    12  	"fmt"
    13  	"hash"
    14  	"io"
    15  )
    16  
    17  // KeyID represents the ID used to identify a particular key.
    18  type KeyID []byte
    19  
    20  // MarshalJSON marshals a KeyID as a hex encoded string.
    21  func (id KeyID) MarshalJSON() ([]byte, error) {
    22  	if len(id) == 0 {
    23  		return []byte(`""`), nil
    24  	}
    25  	dst := make([]byte, hex.EncodedLen(len(id))+2)
    26  	hex.Encode(dst[1:], id)
    27  	// need to supply leading/trailing double quotes.
    28  	dst[0], dst[len(dst)-1] = '"', '"'
    29  	return dst, nil
    30  }
    31  
    32  // UnmarshalJSON unmarshals a hex encoded string into a KeyID.
    33  func (id *KeyID) UnmarshalJSON(data []byte) error {
    34  	// need to strip leading and trailing double quotes
    35  	if data[0] != '"' || data[len(data)-1] != '"' {
    36  		return fmt.Errorf("KeyID is not quoted")
    37  	}
    38  	data = data[1 : len(data)-1]
    39  	*id = make([]byte, hex.DecodedLen(len(data)))
    40  	_, err := hex.Decode(*id, data)
    41  	return err
    42  }
    43  
    44  // KeyDescriptor represents a given key and any associated options.
    45  type KeyDescriptor struct {
    46  	Registry string                 `json:"registry"`
    47  	ID       KeyID                  `json:"keyid"`
    48  	Options  map[string]interface{} `json:"options,omitempty"`
    49  }
    50  
    51  // Encrypter defines encryption methods.
    52  type Encrypter interface {
    53  	// CipherrextSize returns the size of the ciphertext that
    54  	// that will result from supplied plaintext. It should be used
    55  	// to size the slice supplied to Encrypt.
    56  	CiphertextSize(plaintext []byte) int
    57  
    58  	// CiphertextSizeSlices returns the size of the ciphertext that
    59  	// will result from the supplied plaintext slices. It should be used
    60  	// to size the slice supplied to EncryptSlices.
    61  	CiphertextSizeSlices(plaintexts ...[]byte) int
    62  
    63  	// Encrypt encrypts the plaintext in src into ciphertext in
    64  	// dst. dst must be at least CiphertextSize() bytes large.
    65  	Encrypt(src, dst []byte) error
    66  
    67  	// EncryptSlices encrypts the plaintext slices as a single
    68  	// block. It is intended to avoid the need for an external copy
    69  	// to obtain a single buffer for use with Encrypt. The slices
    70  	// will be decrypted as a single block.
    71  	EncryptSlices(dst []byte, src ...[]byte) error
    72  
    73  	// TODO: AEAD method.
    74  	//	Seal(header []byte, p []byte) (int, err)
    75  }
    76  
    77  // Decrypter defines decryption methods.
    78  type Decrypter interface {
    79  	// PlaintextSize returns the size of the decrypted plaintext
    80  	// that will result from decrypting the supplied ciphertext.
    81  	// Note that this size will include any checksums enrypted
    82  	// with the original plaintext.
    83  	PlaintextSize(ciphertext []byte) int
    84  	// Decrypt decrypts the ciphertext in src into plaintext stored in dst and
    85  	// returns slices that contain the checksum of the original plaintext
    86  	// and the plaintext. dst should be at least PlainTextSize() bytes big.
    87  	Decrypt(src, dst []byte) (sum, plaintext []byte, err error)
    88  }
    89  
    90  type engine struct {
    91  	reg KeyRegistry
    92  	kd  KeyDescriptor
    93  }
    94  
    95  var randomSource = rand.Reader
    96  
    97  func (e *engine) initIV(b []byte) (iv, buf []byte, err error) {
    98  	bs := e.reg.BlockSize()
    99  	iv, buf = b[:bs], b[bs:]
   100  	n, err := io.ReadFull(randomSource, iv)
   101  	if err != nil {
   102  		err = fmt.Errorf("failed to read %d bytes of random data: %v", len(iv), err)
   103  		return
   104  	}
   105  	if n != len(iv) {
   106  		err = fmt.Errorf("failed to generate complete iv: %d < %d", n, len(b))
   107  	}
   108  	return iv, buf, err
   109  }
   110  
   111  func (e *engine) readIV(b []byte) (iv, buf []byte, err error) {
   112  	bs := e.reg.BlockSize()
   113  	if len(b) < bs {
   114  		return nil, nil, fmt.Errorf("failed to read IV")
   115  	}
   116  	return b[:bs], b[bs:], nil
   117  }
   118  
   119  // CiphertextSize implements Encrypter.
   120  func (e *engine) CiphertextSize(plaintext []byte) int {
   121  	return e.reg.BlockSize() + e.reg.HMACSize() + len(plaintext)
   122  }
   123  
   124  // CiphertextSizeSlices implements Encrypter.
   125  func (e *engine) CiphertextSizeSlices(plaintext ...[]byte) int {
   126  	total := e.reg.BlockSize() + e.reg.HMACSize()
   127  	for _, p := range plaintext {
   128  		total += len(p)
   129  	}
   130  	return total
   131  }
   132  
   133  // PlaintextSize impementes Decrypter.
   134  func (e *engine) PlaintextSize(ciphertext []byte) int {
   135  	return e.reg.HMACSize() + len(ciphertext)
   136  }
   137  
   138  func (e *engine) setup(dst []byte) ([]byte, hash.Hash, cipher.Stream, error) {
   139  	iv, buf, err := e.initIV(dst)
   140  	if err != nil {
   141  		return nil, nil, nil, err
   142  	}
   143  	// Obtain an hmac hash and cipher.Block from the registry using
   144  	// the specified key ID.
   145  	hmacSum, block, err := e.reg.NewBlock(e.kd.ID)
   146  	if err != nil {
   147  		return nil, nil, nil, err
   148  	}
   149  	stream := cipher.NewCFBEncrypter(block, iv)
   150  	// Generate and encrypt the sum.
   151  	hmacSum.Reset()
   152  	return buf, hmacSum, stream, nil
   153  }
   154  
   155  // Encrypt implements Encrypter.
   156  // Encrypt can be used concurrently.
   157  func (e *engine) Encrypt(src, dst []byte) error {
   158  	if len(dst) < e.CiphertextSize(src) {
   159  		return fmt.Errorf("dst is too small, size it using CiphertextSize()")
   160  	}
   161  	buf, hmacSum, stream, err := e.setup(dst)
   162  	if err != nil {
   163  		return err
   164  	}
   165  	hmacSum.Write(src)
   166  	stream.XORKeyStream(buf, hmacSum.Sum(nil))
   167  	// Encrypt plaintext
   168  	stream.XORKeyStream(buf[e.reg.HMACSize():], src)
   169  	return nil
   170  }
   171  
   172  // EncryptSlices implements Encrypter.
   173  // EncryptSlices can be used concurrently.
   174  func (e *engine) EncryptSlices(dst []byte, src ...[]byte) error {
   175  	if len(dst) < e.CiphertextSizeSlices(src...) {
   176  		return fmt.Errorf("dst is too small, size it using CiphertextSizeSlices()")
   177  	}
   178  	buf, hmacSum, stream, err := e.setup(dst)
   179  	if err != nil {
   180  		return err
   181  	}
   182  	for _, s := range src {
   183  		hmacSum.Write(s)
   184  	}
   185  	stream.XORKeyStream(buf, hmacSum.Sum(nil))
   186  	buf = buf[e.reg.HMACSize():]
   187  	for _, s := range src {
   188  		stream.XORKeyStream(buf, s)
   189  		buf = buf[len(s):]
   190  	}
   191  	return nil
   192  }
   193  
   194  func newEngine(kd KeyDescriptor) (*engine, error) {
   195  	reg, err := Lookup(kd.Registry)
   196  	if err != nil {
   197  		return nil, err
   198  	}
   199  	return &engine{
   200  		reg: reg,
   201  		kd:  kd,
   202  	}, nil
   203  }
   204  
   205  // NewEncrypter returns a new encrypter.
   206  // The implementation it returns uses an encrypted HMAC/SHA512 checksum of
   207  // the plaintext to ensure integrity. The format of a block is:
   208  // Initialization Vector (IV)
   209  // encrypted(HMAC(plaintext) + plaintext)
   210  func NewEncrypter(kd KeyDescriptor) (Encrypter, error) {
   211  	return newEngine(kd)
   212  }
   213  
   214  // NewDecrypter returns a new decrypter.
   215  func NewDecrypter(kd KeyDescriptor) (Decrypter, error) {
   216  	return newEngine(kd)
   217  }
   218  
   219  // Decrypt implements Decrypter.
   220  // Decrypt can be used concurrently.
   221  func (e *engine) Decrypt(src, dst []byte) (sum, plaintext []byte, err error) {
   222  	if len(dst) < e.PlaintextSize(src) {
   223  		return nil, nil, fmt.Errorf("dst is too small, size it using PlaintextSize()")
   224  	}
   225  	// Obtain a cipher.Block from the registry using the specified key ID.
   226  	hmacSum, block, err := e.reg.NewBlock(e.kd.ID)
   227  	if err != nil {
   228  		return nil, nil, err
   229  	}
   230  
   231  	iv, buf, err := e.readIV(src)
   232  	if err != nil {
   233  		return nil, nil, err
   234  	}
   235  	stream := cipher.NewCFBDecrypter(block, iv)
   236  
   237  	// Decrypt bytes from ciphertext, size buf to the length of the ciphertext
   238  	// including the checksum. Always make sure there is enough room for the
   239  	// checksum, if the buffer is short then we'll get a checksum error.
   240  	sumSize := e.reg.HMACSize()
   241  	if len(buf) >= sumSize {
   242  		dst = dst[:len(buf)]
   243  	}
   244  	stream.XORKeyStream(dst, buf)
   245  	got := dst[:sumSize]
   246  	hmacSum.Reset()
   247  	hmacSum.Write(dst[sumSize:])
   248  	want := hmacSum.Sum(nil)
   249  	if !hmac.Equal(got[:], want) {
   250  		return nil, nil, fmt.Errorf("mismatched checksums: %v != %v ", got, want)
   251  	}
   252  	sum = dst[:sumSize]
   253  	plaintext = dst[sumSize:]
   254  	return
   255  }