更新时间:2024-08-30 GMT+08:00

加解密大量数据

场景说明

当有大量数据(例如:照片、视频或者数据库文件等)需要加解密时,用户可采用信封加密方式加解密数据,无需通过网络传输大量数据即可完成数据加解密。

加密和解密原理

  • 大量数据加密
    图1 加密本地文件

    说明如下:

    1. 用户需要在KMS中创建一个用户主密钥。
    2. 用户调用KMS的“create-datakey”接口创建数据加密密钥。用户得到一个明文的数据加密密钥和一个密文的数据加密密钥。其中密文的数据加密密钥是由指定的用户主密钥加密明文的数据加密密钥生成的。
    3. 用户使用明文的数据加密密钥来加密明文文件,生成密文文件。
    4. 用户将密文的数据加密密钥和密文文件一同存储到持久化存储设备或服务中。
  • 大量数据解密
    图2 解密本地文件

    说明如下:
    1. 用户从持久化存储设备或服务中读取密文的数据加密密钥和密文文件。
    2. 用户调用KMS的“decrypt-datakey”接口,使用对应的用户主密钥(即生成密文的数据加密密钥时所使用的用户主密钥)来解密密文的数据加密密钥,取得明文的数据加密密钥。

      如果对应的用户主密钥被误删除,会导致解密失败。因此,需要妥善管理好用户主密钥。

    3. 用户使用明文的数据加密密钥来解密密文文件。

信封加密使用的相关API

您可以调用以下API,在本地对数据进行加解密。

API名称

说明

创建数据密钥

创建数据密钥。

加密数据密钥

用指定的主密钥加密数据密钥。

解密数据密钥

用指定的主密钥解密数据密钥。

加密本地文件

  1. 通过华为云控制台,创建用户主密钥,请参见创建密钥
  2. 请准备基础认证信息。
    • ACCESS_KEY: 华为账号Access Key
    • SECRET_ACCESS_KEY: 华为账号Secret Access Key
    • PROJECT_ID: 华为云局点项目ID,请参见华为云局点项目
    • KMS_ENDPOINT: 华为云KMS服务访问终端地址,请参见终端节点
    • 认证用的ak和sk直接写到代码中有很大的安全风险,建议在配置文件或者环境变量中密文存放,使用时解密,确保安全。
    • 本示例以ak和sk保存在环境变量中来实现身份验证为例,运行本示例前请先在本地环境中设置环境变量HUAWEICLOUD_SDK_AK和HUAWEICLOUD_SDK_SK。
  3. 加密本地文件。

    示例代码中:

    • 用户主密钥:华为云控制台创建的密钥ID。
    • 明文数据文件:FirstPlainFile.jpg。
    • 输出的密文数据文件:SecondEncryptFile.jpg。
      import com.huaweicloud.sdk.core.auth.BasicCredentials;
      import com.huaweicloud.sdk.kms.v1.KmsClient;
      import com.huaweicloud.sdk.kms.v1.model.CreateDatakeyRequest;
      import com.huaweicloud.sdk.kms.v1.model.CreateDatakeyRequestBody;
      import com.huaweicloud.sdk.kms.v1.model.CreateDatakeyResponse;
      import com.huaweicloud.sdk.kms.v1.model.DecryptDatakeyRequest;
      import com.huaweicloud.sdk.kms.v1.model.DecryptDatakeyRequestBody;
      
      import javax.crypto.Cipher;
      import javax.crypto.spec.GCMParameterSpec;
      import javax.crypto.spec.SecretKeySpec;
      import java.io.BufferedInputStream;
      import java.io.BufferedOutputStream;
      import java.io.File;
      import java.io.FileInputStream;
      import java.io.FileOutputStream;
      import java.io.IOException;
      import java.nio.file.Files;
      import java.security.SecureRandom;
      
      /**
       * 使用数据密钥(DEK)进行文件加解密
       * 激活assert语法,请在VM_OPTIONS中添加-ea
       */
      public class FileStreamEncryptionExample {
      
          private static final String ACCESS_KEY = System.getenv("HUAWEICLOUD_SDK_AK");
          private static final String SECRET_ACCESS_KEY = System.getenv("HUAWEICLOUD_SDK_SK");
          private static final String PROJECT_ID = "<ProjectID>";
          private static final String KMS_ENDPOINT = "<KmsEndpoint>";
      
      
          /**
           * AES算法相关标识:
           * - AES_KEY_BIT_LENGTH: AES256密钥比特长度
           * - AES_KEY_BYTE_LENGTH: AES256密钥字节长度
           * - AES_ALG: AES256算法,本例分组模式使用GCM,填充使用PKCS5Padding
           * - AES_FLAG: AES算法标识
           * - GCM_TAG_LENGTH: GCM TAG长度
           * - GCM_IV_LENGTH: GCM 初始向量长度
           */
          private static final String AES_KEY_BIT_LENGTH = "256";
          private static final String AES_KEY_BYTE_LENGTH = "32";
          private static final String AES_ALG = "AES/GCM/PKCS5Padding";
          private static final String AES_FLAG = "AES";
          private static final int GCM_TAG_LENGTH = 16;
          private static final int GCM_IV_LENGTH = 12;
      
          public static void main(final String[] args) {
              // 您在华为云控制台创建的用户主密钥ID
              final String keyId = args[0];
      
              encryptFile(keyId);
          }
      
          /**
           * 使用数据密钥加解密文件实例
           *
           * @param keyId 用户主密钥ID
           */
          static void encryptFile(String keyId) {
              // 1.准备访问华为云的认证信息
              final BasicCredentials auth = new BasicCredentials().withAk(ACCESS_KEY).withSk(SECRET_ACCESS_KEY)
                      .withProjectId(PROJECT_ID);
      
              // 2.初始化SDK,传入认证信息及KMS访问终端地址
              final KmsClient kmsClient = KmsClient.newBuilder().withCredential(auth).withEndpoint(KMS_ENDPOINT).build();
      
              // 3.组装创建数据密钥请求信息
              final CreateDatakeyRequest createDatakeyRequest = new CreateDatakeyRequest()
                      .withBody(new CreateDatakeyRequestBody().withKeyId(keyId).withDatakeyLength(AES_KEY_BIT_LENGTH));
      
              // 4.创建数据密钥
              final CreateDatakeyResponse createDatakeyResponse = kmsClient.createDatakey(createDatakeyRequest);
      
              // 5.接收创建的数据密钥信息
              // 密文密钥与KeyId建议保存在本地,方便解密数据时获取明文密钥
              // 明文密钥在创建后立即使用,使用前需要将16进制明文密钥转换成byte数组
              final String cipherText = createDatakeyResponse.getCipherText();
              final byte[] plainKey = hexToBytes(createDatakeyResponse.getPlainText());
      
              // 6.准备待加密的文件
              // inFile 待加密的原文件
              // outEncryptFile 加密后的文件
              
              final File inFile = new File("FirstPlainFile.jpg");
              final File outEncryptFile = new File("SecondEncryptFile.jpg");
      
              // 7.使用AES算法进行加密时,可以创建初始向量
              final byte[] iv = new byte[GCM_IV_LENGTH];
              final SecureRandom secureRandom = new SecureRandom();
              secureRandom.nextBytes(iv);
      
              // 8.对文件进行加密,并存储加密后的文件
              doFileFinal(Cipher.ENCRYPT_MODE, inFile, outEncryptFile, plainKey, iv);
      
          }
      
          /**
           * 对文件进行加解密
           *
           * @param cipherMode 加密模式,可选值为Cipher.ENCRYPT_MODE或者Cipher.DECRYPT_MODE
           * @param infile     待加解密的文件
           * @param outFile    加解密后的文件
           * @param keyPlain   明文密钥
           * @param iv         初始化向量
           */
          static void doFileFinal(int cipherMode, File infile, File outFile, byte[] keyPlain, byte[] iv) {
      
              try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(infile));
                   BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(outFile))) {
                  final byte[] bytIn = new byte[(int) infile.length()];
                  final int fileLength = bis.read(bytIn);
      
                  assert fileLength > 0;
      
                  final SecretKeySpec secretKeySpec = new SecretKeySpec(keyPlain, AES_FLAG);
                  final Cipher cipher = Cipher.getInstance(AES_ALG);
                  final GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH * Byte.SIZE, iv);
                  cipher.init(cipherMode, secretKeySpec, gcmParameterSpec);
                  final byte[] bytOut = cipher.doFinal(bytIn);
                  bos.write(bytOut);
              } catch (Exception e) {
                  throw new RuntimeException(e.getMessage());
              }
          }
      
      }

解密本地文件

  1. 请准备基础认证信息。
    • ACCESS_KEY: 华为账号Access Key
    • SECRET_ACCESS_KEY: 华为账号Secret Access Key
    • PROJECT_ID: 华为云局点项目ID,请参见华为云局点项目
    • KMS_ENDPOINT: 华为云KMS服务访问终端地址,请参见终端节点
    • 认证用的ak和sk直接写到代码中有很大的安全风险,建议在配置文件或者环境变量中密文存放,使用时解密,确保安全。
    • 本示例以ak和sk保存在环境变量中来实现身份验证为例,运行本示例前请先在本地环境中设置环境变量HUAWEICLOUD_SDK_AK和HUAWEICLOUD_SDK_SK。
  2. 解密本地文件。

    示例代码中:

    • 用户主密钥:华为云控制台创建的密钥ID。
    • 输出的密文数据文件:SecondEncryptFile.jpg。
    • 加密后再解密的数据文件:ThirdDecryptFile.jpg。
      import com.huaweicloud.sdk.core.auth.BasicCredentials;
      import com.huaweicloud.sdk.kms.v1.KmsClient;
      import com.huaweicloud.sdk.kms.v1.model.CreateDatakeyRequest;
      import com.huaweicloud.sdk.kms.v1.model.CreateDatakeyRequestBody;
      import com.huaweicloud.sdk.kms.v1.model.CreateDatakeyResponse;
      import com.huaweicloud.sdk.kms.v1.model.DecryptDatakeyRequest;
      import com.huaweicloud.sdk.kms.v1.model.DecryptDatakeyRequestBody;
      
      import javax.crypto.Cipher;
      import javax.crypto.spec.GCMParameterSpec;
      import javax.crypto.spec.SecretKeySpec;
      import java.io.BufferedInputStream;
      import java.io.BufferedOutputStream;
      import java.io.File;
      import java.io.FileInputStream;
      import java.io.FileOutputStream;
      import java.io.IOException;
      import java.nio.file.Files;
      import java.security.SecureRandom;
      
      /**
       * 使用数据密钥(DEK)进行文件加解密
       * 激活assert语法,请在VM_OPTIONS中添加-ea
       */
      public class FileStreamEncryptionExample {
      
          private static final String ACCESS_KEY = System.getenv("HUAWEICLOUD_SDK_AK");
          private static final String SECRET_ACCESS_KEY = System.getenv("HUAWEICLOUD_SDK_SK");
          private static final String PROJECT_ID = "<ProjectID>";
          private static final String KMS_ENDPOINT = "<KmsEndpoint>";
      
          /**
           * AES算法相关标识:
           * - AES_KEY_BIT_LENGTH: AES256密钥比特长度
           * - AES_KEY_BYTE_LENGTH: AES256密钥字节长度
           * - AES_ALG: AES256算法,本例分组模式使用GCM,填充使用PKCS5Padding
           * - AES_FLAG: AES算法标识
           * - GCM_TAG_LENGTH: GCM TAG长度
           * - GCM_IV_LENGTH: GCM 初始向量长度
           */
          private static final String AES_KEY_BIT_LENGTH = "256";
          private static final String AES_KEY_BYTE_LENGTH = "32";
          private static final String AES_ALG = "AES/GCM/PKCS5Padding";
          private static final String AES_FLAG = "AES";
          private static final int GCM_TAG_LENGTH = 16;
          private static final int GCM_IV_LENGTH = 12;
      
          public static void main(final String[] args) {
              // 您在华为云控制台创建的用户主密钥ID
              final String keyId = args[0];
              // 创建数据密钥时,响应的密文数据密钥
              final String cipherText = args[1];
      
              decryptFile(keyId, cipherText);
          }
      
          /**
           * 使用数据密钥加解密文件实例
           *
           * @param keyId 用户主密钥ID
           * @param cipherText 密文数据密钥
           */
          static void decryptFile(String keyId,String cipherText) {
              // 1.准备访问华为云的认证信息
              final BasicCredentials auth = new BasicCredentials().withAk(ACCESS_KEY).withSk(SECRET_ACCESS_KEY)
                      .withProjectId(PROJECT_ID);
      
              // 2.初始化SDK,传入认证信息及KMS访问终端地址
              final KmsClient kmsClient = KmsClient.newBuilder().withCredential(auth).withEndpoint(KMS_ENDPOINT).build();
      
              // 3.准备待加密的文件
              // inFile 待加密的文件
              // outEncryptFile 加密后的文件
              // outDecryptFile 加密后再解密的文件
              final File inFile = new File("FirstPlainFile.jpg");
              final File outEncryptFile = new File("SecondEncryptFile.jpg");
              final File outDecryptFile = new File("ThirdDecryptFile.jpg");
      
              // 4.使用AES算法进行解密时,初始向量需要与加密时保持一致,此处仅为占位。
              final byte[] iv = new byte[GCM_IV_LENGTH];
              
              // 5.组装解密数据密钥的请求,其中cipherText为创建数据密钥时返回的密文数据密钥。
              final DecryptDatakeyRequest decryptDatakeyRequest = new DecryptDatakeyRequest()
                      .withBody(new DecryptDatakeyRequestBody()
                              .withKeyId(keyId).withCipherText(cipherText).withDatakeyCipherLength(AES_KEY_BYTE_LENGTH));
      
              // 6.解密数据密钥,并对返回的16进制明文密钥换成byte数组
              final byte[] decryptDataKey = hexToBytes(kmsClient.decryptDatakey(decryptDatakeyRequest).getDataKey());
      
              // 7.对文件进行解密,并存储解密后的文件
              // 句末的iv为加密示例中创建的初始向量
              doFileFinal(Cipher.DECRYPT_MODE, outEncryptFile, outDecryptFile, decryptDataKey, iv);
      
              // 8.比对原文件和加密后再解密的文件
              assert getFileSha256Sum(inFile).equals(getFileSha256Sum(outDecryptFile));
      
          }
      
          /**
           * 对文件进行加解密
           *
           * @param cipherMode 加密模式,可选值为Cipher.ENCRYPT_MODE或者Cipher.DECRYPT_MODE
           * @param infile     待加解密的文件
           * @param outFile    加解密后的文件
           * @param keyPlain   明文密钥
           * @param iv         初始化向量
           */
          static void doFileFinal(int cipherMode, File infile, File outFile, byte[] keyPlain, byte[] iv) {
      
              try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(infile));
                   BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(outFile))) {
                  final byte[] bytIn = new byte[(int) infile.length()];
                  final int fileLength = bis.read(bytIn);
      
                  assert fileLength > 0;
      
                  final SecretKeySpec secretKeySpec = new SecretKeySpec(keyPlain, AES_FLAG);
                  final Cipher cipher = Cipher.getInstance(AES_ALG);
                  final GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH * Byte.SIZE, iv);
                  cipher.init(cipherMode, secretKeySpec, gcmParameterSpec);
                  final byte[] bytOut = cipher.doFinal(bytIn);
                  bos.write(bytOut);
              } catch (Exception e) {
                  throw new RuntimeException(e.getMessage());
              }
          }
      
          /**
           * 十六进制字符串转byte数组
           *
           * @param hexString 十六进制字符串
           * @return byte数组
           */
          static byte[] hexToBytes(String hexString) {
              final int stringLength = hexString.length();
              assert stringLength > 0;
              final byte[] result = new byte[stringLength / 2];
              int j = 0;
              for (int i = 0; i < stringLength; i += 2) {
                  result[j++] = (byte) Integer.parseInt(hexString.substring(i, i + 2), 16);
              }
              return result;
          }
      
          /**
           * 计算文件SHA256摘要
           *     
           * @param file 文件     
           * @return SHA256摘要     
           */    
           static String getFileSha256Sum(File file) {
              int length;
              MessageDigest sha256;
              byte[] buffer = new byte[1024];
              try {
                  sha256 = MessageDigest.getInstance("SHA-256");
              } catch (NoSuchAlgorithmException e) {
                  throw new RuntimeException(e.getMessage());
              }
              try (FileInputStream inputStream = new FileInputStream(file)) {
                  while ((length = inputStream.read(buffer)) != -1) {
                      sha256.update(buffer, 0, length);
                  }
                  return new BigInteger(1, sha256.digest()).toString(16);
              } catch (IOException e) {
                  throw new RuntimeException(e.getMessage());
              }
          }
      }