github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/ccl/storageccl/encryption.go (about)

     1  // Copyright 2020 The Cockroach Authors.
     2  //
     3  // Licensed as a CockroachDB Enterprise file under the Cockroach Community
     4  // License (the "License"); you may not use this file except in compliance with
     5  // the License. You may obtain a copy of the License at
     6  //
     7  //     https://github.com/cockroachdb/cockroach/blob/master/licenses/CCL.txt
     8  
     9  package storageccl
    10  
    11  import (
    12  	"bytes"
    13  	"crypto/aes"
    14  	"crypto/cipher"
    15  	crypto_rand "crypto/rand"
    16  	"crypto/sha256"
    17  
    18  	"github.com/cockroachdb/errors"
    19  	"golang.org/x/crypto/pbkdf2"
    20  )
    21  
    22  // The following helpers are intended for use in creating and reading encrypted
    23  // files in BACKUPs. Encryption is done using AES-GCM with a key derived from
    24  // the provided passphrase. Individual files are always written with a random IV
    25  // which is prefixed to the ciphertext for retrieval and use by decrypt. Helpers
    26  // are included for deriving a key from a salt and passphrase though the caller
    27  // is responsible for remembering the salt to rederive that key later.
    28  
    29  // encryptionPreamble is a constant string prepended in cleartext to ciphertexts
    30  // allowing them to be easily recognized by sight and allowing some basic sanity
    31  // checks when trying to open them (e.g. error if incorrectly using encryption
    32  // on an unencrypted file of vice-versa).
    33  var encryptionPreamble = []byte("encrypt")
    34  
    35  const encryptionSaltSize = 16
    36  const encryptionVersionIVPrefix = 1
    37  
    38  // GenerateSalt generates a 16 byte random salt.
    39  func GenerateSalt() ([]byte, error) {
    40  	// Pick a unique salt for this file.
    41  	salt := make([]byte, encryptionSaltSize)
    42  	if _, err := crypto_rand.Read(salt); err != nil {
    43  		return nil, err
    44  	}
    45  	return salt, nil
    46  }
    47  
    48  // GenerateKey generates a key for the supplied passphrase and salt.
    49  func GenerateKey(passphrase, salt []byte) []byte {
    50  	return pbkdf2.Key(passphrase, salt, 64000, 32, sha256.New)
    51  }
    52  
    53  // AppearsEncrypted checks if passed bytes begin with an encryption preamble.
    54  func AppearsEncrypted(text []byte) bool {
    55  	return bytes.HasPrefix(text, encryptionPreamble)
    56  }
    57  
    58  // EncryptFile encrypts a file with the supplied key and a randomly chosen IV
    59  // which is prepended in a header on the returned ciphertext. It is intended for
    60  // use on collections of separate data files that are all encrypted/decrypted
    61  // with the same key, such as BACKUP data files, and notably does _not_ include
    62  // information for key derivation as that is _not_ done for individual files in
    63  // such cases. See EncryptFileStoringSalt for a function that produces a
    64  // ciphertext that also includes the salt and thus supports decrypting with only
    65  // the passphrase (at the cost in including key derivation).
    66  func EncryptFile(plaintext, key []byte) ([]byte, error) {
    67  	gcm, err := aesgcm(key)
    68  	if err != nil {
    69  		return nil, err
    70  	}
    71  
    72  	// Allocate our output buffer: preamble + 1B version + iv, plus additional
    73  	// pre-allocated capacity for the ciphertext.
    74  	headerSize := len(encryptionPreamble) + 1 + gcm.NonceSize()
    75  	ciphertext := make([]byte, headerSize, headerSize+len(plaintext)+gcm.Overhead())
    76  
    77  	// Write our header (preamble+version+IV) to the ciphertext buffer.
    78  	copy(ciphertext, encryptionPreamble)
    79  	ciphertext[len(encryptionPreamble)] = encryptionVersionIVPrefix
    80  	// Pick a unique IV for this file and write it in the header.
    81  	iv := ciphertext[len(encryptionPreamble)+1:]
    82  	if _, err := crypto_rand.Read(iv); err != nil {
    83  		return nil, err
    84  	}
    85  
    86  	// Generate and write the actual ciphertext.
    87  	return gcm.Seal(ciphertext, iv, plaintext, nil), nil
    88  }
    89  
    90  // DecryptFile decrypts a file encrypted by EncryptFile, using the supplied key
    91  // and reading the IV from a prefix of the file. See comments on EncryptFile
    92  // for intended usage, and see DecryptFile
    93  func DecryptFile(ciphertext, key []byte) ([]byte, error) {
    94  	if !AppearsEncrypted(ciphertext) {
    95  		return nil, errors.New("file does not appear to be encrypted")
    96  	}
    97  	ciphertext = ciphertext[len(encryptionPreamble):]
    98  
    99  	if len(ciphertext) < 1 {
   100  		return nil, errors.New("invalid encryption header")
   101  	}
   102  	version := ciphertext[0]
   103  	ciphertext = ciphertext[1:]
   104  
   105  	if version != encryptionVersionIVPrefix {
   106  		return nil, errors.Errorf("unexpected encryption scheme/config version %d", version)
   107  	}
   108  	gcm, err := aesgcm(key)
   109  	if err != nil {
   110  		return nil, err
   111  	}
   112  
   113  	ivSize := gcm.NonceSize()
   114  	if len(ciphertext) < ivSize {
   115  		return nil, errors.New("invalid encryption header: missing IV")
   116  	}
   117  	iv := ciphertext[:ivSize]
   118  	ciphertext = ciphertext[ivSize:]
   119  	plaintext, err := gcm.Open(ciphertext[:0], iv, ciphertext, nil)
   120  	return plaintext, err
   121  }
   122  
   123  func aesgcm(key []byte) (cipher.AEAD, error) {
   124  	block, err := aes.NewCipher(key)
   125  	if err != nil {
   126  		return nil, err
   127  	}
   128  	return cipher.NewGCM(block)
   129  }