
# 信封加解密（大量数据加解密）
信封加密（Envelope Encryption）是一种应对海量数据的高性能加解密方案。本地随机生成一次性数据密钥 DEK，用它秒级加解密海量数据，DEK 立刻被 CMK 包成密文存盘；用时先调 KMS 把 DEK 密文解成明文放内存，全程明文不落盘，加解密性能接近本地 AES，密钥安全托管在 KMS。
#### 加密方案对比
| 对比项      | 敏感信息加密                  | 信封加密                    |
|:---|:---|:---|
| 密钥       | CMK                     | CMK、DEK                 |
| 性能       | 对称加密，远程调用               | 少量远程对称加密，海量本地对称加密       |
| 主要场景     | 密钥、证书、小型数据，适用于调用频率较低的场景 | 海量大型数据，适用于对性能要求较高的场景    |
| 云 API 调用 | 每次加解密都需要调用云 API         | 进程启动后调用1次API，对DEK密文进行解密 |
   
#### 加密和解密原理
- 大量数据加密
  图1加密本地文件   
  ![](https://support.huaweicloud.com/bestpractice-dew/zh-cn_image_0000002294322321.png "点击放大")
  
  说明如下：
  1. 用户需要在KMS中创建一个**用户主密钥**。
  
  2. 用户调用KMS的"create-datakey"接口创建**数据加密密钥** 。用户得到一个**明文的数据加密密钥** 和一个**密文的数据加密密钥** 。其中**密文的数据加密密钥** 是由指定的**用户主密钥** 加密**明文的数据加密密钥**生成的。
  
  3. 用户使用**明文的数据加密密钥**来加密明文文件，生成密文文件。
  
  4. 用户将**密文的数据加密密钥** 和**密文文件**一同存储到持久化存储设备或服务中。
   
- 大量数据解密
  图2解密本地文件   
  ![](https://support.huaweicloud.com/bestpractice-dew/zh-cn_image_0000002259682160.png "点击放大")
  
  说明如下：
  1. 用户从持久化存储设备或服务中读取**密文的数据加密密钥** 和**密文文件**。
  
  2. 用户调用KMS的"decrypt-datakey"接口，使用对应的**用户主密钥** （即生成密文的数据加密密钥时所使用的用户主密钥）来解密**密文的数据加密密钥** ，取得**明文的数据加密密钥** 。
     如果对应的用户主密钥被误删除，会导致解密失败。因此，需要妥善管理好用户主密钥。
     
  
  3. 用户使用**明文的数据加密密钥**来解密密文文件。
    
 
#### 信封加密使用的相关API
您可以调用以下API，在本地对数据进行加解密。
| API名称                                                                 | 说明             |
|:---|:---|
| [创建数据密钥](https://support.huaweicloud.com/api-dew/CreateDatakey.html)  | 创建数据密钥。        |
| [加密数据密钥](https://support.huaweicloud.com/api-dew/EncryptDatakey.html) | 用指定的主密钥加密数据密钥。 |
| [解密数据密钥](https://support.huaweicloud.com/api-dew/DecryptDatakey.html) | 用指定的主密钥解密数据密钥。 |
   
#### 操作指导
1. 通过华为云控制台，创建用户主密钥，请参见[创建密钥](https://support.huaweicloud.com/usermanual-dew/dew_01_0178.html)。
2. 请准备基础认证信息。 
   - ACCESS_KEY：华为账号Access Key
   
   - SECRET_ACCESS_KEY：华为账号Secret Access Key
   
   - PROJECT_ID：华为云局点项目ID，请参见[华为云局点项目](https://support.huaweicloud.com/api-iam/iam_17_0002.html)。
   
   - KMS_ENDPOINT：华为云KMS服务访问终端地址，请参见[终端节点](https://support.huaweicloud.com/api-dew/dew_02_0052.html#section2)。
   
   - 认证用的ak和sk直接写到代码中有很大的安全风险，建议在配置文件或者环境变量中密文存放，使用时解密，确保安全。
   
   - 本示例以ak和sk保存在环境变量中来实现身份验证为例，运行本示例前请先在本地环境中设置环境变量HUAWEICLOUD_SDK_AK和HUAWEICLOUD_SDK_SK。
   
   
   ![](https://support.huaweicloud.com/bestpractice-dew/public_sys-resources/note_3.0-zh-cn.png)
   华为云SDK，提供统一的SDK使用方式。通过添加依赖或下载的方式调用华为云API，访问华为云应用、资源和数据。如果您需要，请参见[华为云SDK](https://console.huaweicloud.com/apiexplorer/#/sdkcenter/KMS?lang=Java)。
   
   
3. 加解密本地文件。 
   **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))
   }
   ```
   
   
 
