信封加密是类似数字信封技术的一种加密手段。这种技术将加密数据的数据密钥封入信封中存储、传递和使用,不再使用用户主密钥(CMK)直接加密和解密数据。本文介绍如何使用KMS实现对数据的信封加密,以及如何解密被信封加密的数据。

为什么使用信封加密

用户在使用数据密钥时,可能存在以下问题:

  • 安全性隐患:通过网络将敏感信息从用户手中传递到阿里云服务的过程中会存在诸多风险,例如:窃听、钓鱼。
  • 无法互相信任和提供可信证明:用户不一定信任阿里云服务,愿意上传敏感数据。阿里云服务也难以证明自己不会误用和泄露这些数据。
  • 性能差、成本高:大量数据需要通过安全信道传递到服务端,加密后再返回给用户,对阿里云服务的性能影响很大。此外,大量的移动数据会带来巨大的成本。

信封加密使用用户主密钥(CMK)生成数据密钥,用离线的数据密钥在本地加密大量数据,而不再使用用户主密钥直接加解密数据。信封加密具有以下特点:

  • 保护数据密钥:加密数据密钥时,数据密钥将受到加密保护,您可以安全地将加密数据与加密数据密钥一起存储。
  • 信任和可信证明:KMS对密钥的所有操作都会进行访问控制及日志跟踪,提供所有密钥的使用记录,满足审计和合规性要求。
  • 性能好、成本低:KMS通过密码运算API在线生成数据密钥,用离线数据密钥在本地加密大量数据。

应用场景

信封加密典型的场景包括(但不限于):
  • 对业务数据文件的加密
  • 对全磁盘数据加密

加密和解密的原理

首先创建一个用户主密钥,使用用户主密钥生成一个数据密钥,再使用数据密钥在本地加解密数据。具体架构如下所示:
  • 信封加密信封加密操作流程如下:
    1. 通过KMS控制台或者调用CreateKey,创建一个用户主密钥。
    2. 调用GenerateDataKey生成数据密钥(包含一个明文数据密钥和一个密文数据密钥)。
    3. 使用明文的数据密钥加密文件,产生密文文件,然后销毁内存中的明文密钥。
    4. 用户将密文的数据密钥和密文文件一同存储到持久化存储设备或服务中。
  • 信封解密信封解密操作流程如下:
    1. 从持久化存储设备或服务中读取密文数据密钥和密文文件。
    2. 调用Decrypt解密数据密钥,取得明文数据密钥。
    3. 使用明文数据密钥解密密文文件,得到明文文件,再销毁内存中的明文密钥。

加密和解密的API

您可以调用以下KMS API,在本地对数据进行加解密。
API名称 说明
CreateKey 创建用户主密钥(CMK)。
CreateAlias 为指定用户主密钥创建一个别名。
GenerateDataKey 在线生成数据密钥,用指定CMK加密数据密钥后,返回数据密钥的密文和明文。
Decrypt 解密KMS直接加密的数据(包括GenerateDataKey产生的数据密钥的密文),不需要指定CMK。

使用阿里云CLI加密和解密本地文件

  1. 调用CreateKey,创建用户主密钥(CMK)。
    aliyun kms CreateKey

    预期输出:

    {
      "KeyMetadata": {
        "CreationDate": "2019-04-08T07:45:54Z",
        "Description": "",
        "KeyId": "1234abcd-12ab-34cd-56ef-12345678****",
        "KeyState": "Enabled",
        "KeyUsage": "ENCRYPT/DECRYPT",
        "DeleteDate": "",
        "Creator": "151266687691****",
        "Arn": "acs:kms:cn-hangzhou:151266687691****:key/1234abcd-12ab-34cd-56ef-12345678****",
        "Origin": "Aliyun_KMS",
        "MaterialExpireTime": ""
      },
      "RequestId": "2a37b168-9fa0-4d71-aba4-2077dd9e80df"
    }
  2. (可选)给用户主密钥添加别名。
    别名是用户主密钥的可选标识。如果用户不创建别名,也可以直接使用密钥的ID。
    aliyun kms CreateAlias --AliasName alias/Apollo/WorkKey --KeyId 1234abcd-12ab-34cd-56ef-12345678****
    说明 其中,Apollo/WorkKey表示Apollo项目中的工作密钥(当前被用于加密的密钥)。您可以在后续示例代码中使用别名(alias/Apollo/WorkKey)调用加密API。
  3. 加密本地文件。
    示例代码中:
    • 用户主密钥:别名为alias/Apollo/WorkKey
    • 明文数据文件:./data/sales.csv。
    • 输出的密文数据文件:./data/sales.csv.cipher
    #!/usr/bin/env python
    # coding=utf-8
    
    import json
    import base64
    
    from Crypto.Cipher import AES
    
    from aliyunsdkcore import client
    from aliyunsdkkms.request.v20160120 import GenerateDataKeyRequest
    
    
    def KmsGenerateDataKey(client, key_alias):
        request = GenerateDataKeyRequest.GenerateDataKeyRequest()
        request.set_accept_format('JSON')
        request.set_KeyId(key_alias)
        request.set_NumberOfBytes(32)
        response = json.loads(client.do_action(request))
    
        datakey_encrypted = response["CiphertextBlob"]
        datakey_plaintext = response["Plaintext"]
        return (datakey_plaintext, datakey_encrypted)
    
    
    def ReadTextFile(in_file):
        file = open(in_file, 'r')
        content = file.read()
        file.close()
        return content
    
    
    def WriteTextFile(out_file, lines):
        file = open(out_file, 'w')
        for ln in lines:
            file.write(ln)
            file.write('\n')
        file.close()
    
    
    # Out file format (text)
    # Line 1: b64 encoded data key
    # Line 2: b64 encoded IV
    # Line 3: b64 encoded ciphertext
    # Line 4: b64 encoded authentication tag
    def LocalEncrypt(datakey_plaintext, datakey_encrypted, in_file, out_file):
        data_key_binary = base64.b64decode(datakey_plaintext)
        cipher = AES.new(data_key_binary, AES.MODE_EAX)
    
        in_content = ReadTextFile(in_file)
        ciphertext, tag = cipher.encrypt_and_digest(in_content.encode('utf-8'))
    
        lines = [datakey_encrypted, base64.b64encode(cipher.nonce).decode('utf-8'), base64.b64encode(ciphertext).decode('utf-8'), base64.b64encode(tag).decode('utf-8')]
        WriteTextFile(out_file, lines)
    
    
    clt = client.AcsClient('Access-Key-Id', 'Access-Key-Secret', 'Region-Id')
    
    key_alias = 'alias/Apollo/WorkKey'
    
    in_file = './data/sales.csv'
    out_file = './data/sales.csv.cipher'
    
    # Generate Data Key
    datakey = KmsGenerateDataKey(clt, key_alias)
    
    # Locally Encrypt the sales record
    LocalEncrypt(datakey[0], datakey[1], in_file, out_file)
  4. 解密本地文件。
    示例代码中:
    • 密文数据文件:./data/sales.csv.cipher
    • 输出的明文数据文件:./data/decrypted_sales.csv
    #!/usr/bin/env python
    #coding=utf-8
    
    import json
    import base64
    
    from Crypto.Cipher import AES
    
    from aliyunsdkcore import client
    from aliyunsdkkms.request.v20160120 import DecryptRequest
    
    def KmsDecrypt(client, ciphertext):
      request = DecryptRequest.DecryptRequest()
      request.set_accept_format('JSON')
      request.set_CiphertextBlob(ciphertext)
      response = json.loads(client.do_action(request))
      return response.get("Plaintext")
    
    def ReadTextFile(in_file):
      file = open(in_file, 'r')
      lines = []
      for ln in file:
        lines.append(ln)
      file.close()
      return lines
    
    def WriteTextFile(out_file, content):
      file = open(out_file, 'w')
      file.write(content)
      file.close()
    
    def LocalDecrypt(datakey, iv, ciphertext, tag, out_file):
      cipher = AES.new(datakey, AES.MODE_EAX, iv)
      data = cipher.decrypt_and_verify(ciphertext, tag).decode('utf-8')
      WriteTextFile(out_file, data)
    
    clt = client.AcsClient('Access-Key-Id','Access-Key-Secret','Region-Id')
    
    in_file = './data/sales.csv.cipher'
    out_file = './data/decrypted_sales.csv'
    
    # Read encrypted file
    in_lines = ReadTextFile(in_file)
    
    # Decrypt data key
    datakey = KmsDecrypt(clt, in_lines[0])
    
    # Locally decrypt the sales record
    LocalDecrypt(
      base64.b64decode(datakey),
      base64.b64decode(in_lines[1]), # IV
      base64.b64decode(in_lines[2]), # Ciphertext
      base64.b64decode(in_lines[3]), # Authentication tag
      out_file
      )

使用SDK加密和解密本地文件

您可以使用以下两种方式进行信封加密:

  • 使用KMS SDK

    您可以使用KMS的SDK,直接调用KMS的GenerateDataKey产生数据密钥,随后利用第三方加密库和数据密钥对数据进行加密,最后将数据密钥的密文作为“信封”和数据的密文组装在一起。

    KMS SDK的代码示例,请参见Java SDK示例

  • 使用加密SDK

    加密SDK帮助您封装了信封加密的最佳实践,您可以通过加密SDK更便捷地在业务中集成加密和解密操作。

    加密SDK的代码示例,请参见加密SDK快速入门(Java)