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 }