信封加解密(大量数据加解密)
信封加密(Envelope Encryption)是一种应对海量数据的高性能加解密方案。本地随机生成一次性数据密钥 DEK,用它秒级加解密海量数据,DEK 立刻被 CMK 包成密文存盘;用时先调 KMS 把 DEK 密文解成明文放内存,全程明文不落盘,加解密性能接近本地 AES,密钥安全托管在 KMS。
加密方案对比
对比项 |
敏感信息加密 |
信封加密 |
---|---|---|
密钥 |
CMK |
CMK、DEK |
性能 |
对称加密,远程调用 |
少量远程对称加密,海量本地对称加密 |
主要场景 |
密钥、证书、小型数据,适用于调用频率较低的场景 |
海量大型数据,适用于对性能要求较高的场景 |
云 API 调用 |
每次加解密都需要调用云 API |
进程启动后调用1次API,对DEK密文进行解密 |
加密和解密原理
- 大量数据加密
图1 加密本地文件
说明如下:
- 用户需要在KMS中创建一个用户主密钥。
- 用户调用KMS的“create-datakey”接口创建数据加密密钥。用户得到一个明文的数据加密密钥和一个密文的数据加密密钥。其中密文的数据加密密钥是由指定的用户主密钥加密明文的数据加密密钥生成的。
- 用户使用明文的数据加密密钥来加密明文文件,生成密文文件。
- 用户将密文的数据加密密钥和密文文件一同存储到持久化存储设备或服务中。
- 大量数据解密
图2 解密本地文件说明如下:
- 用户从持久化存储设备或服务中读取密文的数据加密密钥和密文文件。
- 用户调用KMS的“decrypt-datakey”接口,使用对应的用户主密钥(即生成密文的数据加密密钥时所使用的用户主密钥)来解密密文的数据加密密钥,取得明文的数据加密密钥。
如果对应的用户主密钥被误删除,会导致解密失败。因此,需要妥善管理好用户主密钥。
- 用户使用明文的数据加密密钥来解密密文文件。
信封加密使用的相关API
您可以调用以下API,在本地对数据进行加解密。
操作指导
- 通过华为云控制台,创建用户主密钥,请参见创建密钥。
- 请准备基础认证信息。
- 加解密本地文件。
GO 代码示例
package main import ( "crypto/aes" "crypto/cipher" "crypto/sha256" "encoding/hex" "io" "io/ioutil" "log" "math/rand" "os" "time" "github.com/huaweicloud/huaweicloud-sdk-go-v3/core/auth/basic" "github.com/huaweicloud/huaweicloud-sdk-go-v3/core/config" "github.com/huaweicloud/huaweicloud-sdk-go-v3/core/region" kms "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/kms/v2" kmsModel "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/kms/v2/model" ) const ( AadData = "Demo for aad" ) // 认证用的ak和sk直接写到代码中有很大的安全风险,建议在配置文件或者环境变量中密文存放,使用时解密,确保安全; // 本示例以ak和sk保存在环境变量中来实现身份验证为例,运行本示例前请先在本地环境中设置环境变量HUAWEICLOUD_SDK_AK和HUAWEICLOUD_SDK_SK。 var ak = os.Getenv("HUAWEICLOUD_SDK_AK") var sk = os.Getenv("HUAWEICLOUD_SDK_SK") var iamEndpoint = "https://<IAM_ENDPOINT>" var endpoint = "<ENDPOINT>" var regionID = "<REGION_ID>" func main() { // 用户主密钥ID keyId := "xxxxxxxx-xxx-xxx-xxxx-xxxxxxxxxxx3" // 1.准备认证信息 auth := basic.NewCredentialsBuilder(). WithAk(ak). WithSk(sk). WithIamEndpointOverride(iamEndpoint). Build() httpConfig := config.DefaultHttpConfig() httpConfig.WithIgnoreSSLVerification(true) // 2.初始化SDK,传入认证信息与KMS的终端信息 client := kms.NewKmsClient( kms.KmsClientBuilder(). WithRegion(region.NewRegion(regionID, endpoint)). WithHttpConfig(httpConfig). WithCredential(auth). Build()) // 3.创建数据密钥 aesDataKeyLength := "256" createDataKeyRequest := kmsModel.CreateDatakeyRequest{ Body: &kmsModel.CreateDatakeyRequestBody{KeyId: keyId, DatakeyLength: &aesDataKeyLength}, } createDataKeyResponse, err := client.CreateDatakey(&createDataKeyRequest) if err != nil { log.Fatal("Create data key occur error.") } // 读取待加密文件,GCM加密后存储 readData, err := FileReader("FirstPlainFile.jpg") if err != nil { log.Fatal("Read file occur error.") return } nonce := GenRandomBytes(12) cryptData, err := GcmCrypt(readData, *createDataKeyResponse.PlainText, nonce, 0) err = FileWriter(cryptData, "SecondEncryptFile.jpg") if err != nil { log.Fatal("Write encrypt file occur error.") return } // 4.解密数据密钥 decryptDataKeyRequest := kmsModel.DecryptDatakeyRequest{ Body: &kmsModel.DecryptDatakeyRequestBody{KeyId: keyId, CipherText: *createDataKeyResponse.CipherText, DatakeyCipherLength: "32"}, } decryptDataKeyResponse, err := client.DecryptDatakey(&decryptDataKeyRequest) if err != nil { log.Fatal("Decrypt data key occur error.") } // 5.读取加密后的文件,使用解密的数据密钥,GCM解密后存储 readData, err = FileReader("SecondEncryptFile.jpg") if err != nil { log.Fatal("Read file occur error.") return } decryptData, err := GcmCrypt(readData, *decryptDataKeyResponse.DataKey, nonce, 1) err = FileWriter(decryptData, "ThirdDecryptFile.jpg") if err != nil { log.Fatal("Write encrypt file occur error.") return } // 6.比如源文件与加密后再解密的文件摘要是否一致 log.Printf("result is %t", FileSha256("FirstPlainFile.jpg") == FileSha256("ThirdDecryptFile.jpg")) } // GcmCrypt AES-GCM加解密数据流 // data: 待加密或解密的文件 // plainKey: 明文密钥 // nonce: 初始化向量 // mode: 0标识加密,1标识解密 func GcmCrypt(data []byte, plainKey string, nonce []byte, mode int) ([]byte, error) { plainKeyByte, err := hex.DecodeString(plainKey) if err != nil { log.Fatal("Invalid aes key.") return nil, err } block, err := aes.NewCipher(plainKeyByte) if err != nil { log.Fatal("Invalid init aes.") return nil, err } gcm, err := cipher.NewGCM(block) if err != nil { log.Fatal("Invalid init gcm.") return nil, err } if mode == 0 { return gcm.Seal(nil, nonce, data, []byte(AadData)), nil } else { return gcm.Open(nil, nonce, data, []byte(AadData)) } } func FileReader(path string) ([]byte, error) { file, err := ioutil.ReadFile(path) if err != nil { log.Fatal("Read file failed.") return nil, err } return file, nil } func FileWriter(data []byte, path string) error { err := ioutil.WriteFile(path, data, 0600) if err != nil { log.Fatal("Write file failed.") return err } return nil } func GenRandomBytes(size int) (randomBytes []byte) { rand.Seed(time.Now().UnixNano()) randomBytes = make([]byte, size) _, err := rand.Read(randomBytes) if err != nil { log.Fatal("Read data failed.") return } return randomBytes } func FileSha256(filePath string) string { file, err := os.Open(filePath) if err != nil { return "" } defer file.Close() hash := sha256.New() _, err = io.Copy(hash, file) if err != nil { log.Fatal("Sha256 file data failed.") return "" } return hex.EncodeToString(hash.Sum(nil)) }