

# 使用字段级加密帮助保护敏感数据
<a name="field-level-encryption"></a>

借助 Amazon CloudFront，您可以使用 HTTPS 对与源服务器的端到端连接实施保护。字段级加密增加了一个额外的安全保护层，可让您在整个系统处理过程中保护特定的数据，以便只有某些应用程序才能查看它。

借助字段级加密，您可以让您的用户安全地向您的 Web 服务器上传敏感信息。用户提供的敏感信息在靠近用户的边缘进行加密，并在整个应用程序堆栈中保持加密状态。此加密确保只有需要数据的应用程序（并且具有用于解密的凭证）能够做到这一点。

要使用字段级加密，请在配置您的 CloudFront 分配时，指定要加密的 POST 请求中的字段集以及用于对其进行加密的公有密钥。您最多可以在一个请求中加密 10 个数据字段。（您无法使用字段级加密在一个请求中加密所有数据；您必须指定要加密的各个字段。）

如果带有字段级加密的 HTTPS 请求转发到源，并且请求路由经过您的整个源应用程序或子系统，则敏感数据仍然进行加密，从而降低敏感数据泄露或意外丢失的风险。出于业务原因需要访问敏感数据的组件 (例如需要访问信用号码的支付处理系统) 可以使用适当的私有密钥来解密和访问数据。

**注意**  
要使用字段级加密，您的源必须支持分块编码。

![\[CloudFront 中的字段级加密\]](http://docs.aws.amazon.com/zh_cn/AmazonCloudFront/latest/DeveloperGuide/images/fleoverview.png)


CloudFront 字段级加密使用非对称加密，也称为公有密钥加密。您需要向 CloudFront 提供一个公有密钥，之后系统会自动加密您指定的所有敏感数据。不能使用您向 CloudFront 提供的密钥解密经过加密的值；只有您的私有密钥才能执行解密。

![\[仅加密敏感数据\]](http://docs.aws.amazon.com/zh_cn/AmazonCloudFront/latest/DeveloperGuide/images/encryptedfields.png)


**Topics**
+ [

## 字段级加密概览
](#field-level-encryption-overview)
+ [

## 设置字段级加密
](#field-level-encryption-setting-up)
+ [

## 在源端解密数据字段
](#field-level-encryption-decrypt)

## 字段级加密概览
<a name="field-level-encryption-overview"></a>

以下步骤概述了如何设置字段级加密。有关具体步骤，请参阅[设置字段级加密](#field-level-encryption-setting-up)。

1. **获取公有密钥/私有密钥对。**您必须获取和添加公有密钥，然后才能开始在 CloudFront 中设置字段级加密。

1. **创建字段级加密配置文件。**您在 CloudFront 中创建的字段级加密配置文件可定义要加密的字段。

1. **创建字段级加密配置。**配置指定要用于加密特定数据字段的配置文件（根据请求的内容类型或查询参数）。您还可以为不同方案选择所需的请求转发行为选项。例如，您可以设置请求 URL 中的查询参数指定的配置文件名称在 CloudFront 中不存在时的行为。

1. **链接到缓存行为。**将配置链接到分配的缓存行为，用于指定 CloudFront 应加密数据的时间。

## 设置字段级加密
<a name="field-level-encryption-setting-up"></a>

按照以下步骤操作，开始使用字段级加密。要了解有关字段级加密的配额（以前称为限制），请参阅[配额](cloudfront-limits.md)。
+ [第 1 步：创建 RSA 密钥对](#field-level-encryption-setting-up-step1)
+ [第 2 步：将您的公有密钥添加到 CloudFront](#field-level-encryption-setting-up-step2)
+ [第 3 步：为字段级加密创建配置文件](#field-level-encryption-setting-up-step3)
+ [第 4 步：创建配置](#field-level-encryption-setting-up-step4)
+ [第 5 步：将配置添加到缓存行为](#field-level-encryption-setting-up-step5)

### 第 1 步：创建 RSA 密钥对
<a name="field-level-encryption-setting-up-step1"></a>

要开始使用，您必须创建包含公有密钥和私有密钥的 RSA 密钥对。公有密钥使 CloudFront 能够对数据进行加密，私有密钥使源处的组件能够对已加密的字段进行解密。可以使用 OpenSSL 或其他工具创建密钥对。密钥大小必须为 2048 位。

例如，如果使用 OpenSSL，则可以使用以下命令生成一个长度为 2048 位的密钥对，并将其保存到文件 `private_key.pem` 中：

```
openssl genrsa -out private_key.pem 2048
```

生成的文件同时包含公有密钥和私有密钥。要从该文件中提取公有密钥，请运行以下命令：

```
openssl rsa -pubout -in private_key.pem -out public_key.pem
```

公有密钥文件 (`public_key.pem`) 包含您在以下步骤中粘贴的已编码密钥值。

### 第 2 步：将您的公有密钥添加到 CloudFront
<a name="field-level-encryption-setting-up-step2"></a>

获取 RSA 密钥对后，将您的公有密钥添加到 CloudFront。<a name="field-level-encryption-setting-up-step2-procedure"></a>

**将您的公有密钥添加到 CloudFront（控制台）**

1. 登录 AWS 管理控制台，并通过以下网址打开 CloudFront 控制台：[https://console.aws.amazon.com/cloudfront/v4/home](https://console.aws.amazon.com/cloudfront/v4/home)。

1. 在导航窗格中，选择**公有密钥**。

1. 选择**添加公有密钥**。

1. 对于**密钥名称**，请为密钥键入唯一的名称。该名称不能包含空格，且只能包含字母数字字符、下划线 (\$1) 和连字符 (-)。最大字符数为 128。

1. 对于 **Key value (密钥值)**，粘贴公有密钥的已编码密钥值，包括 `-----BEGIN PUBLIC KEY-----` 和 `-----END PUBLIC KEY-----` 行。

1. 对于**注释**，请添加一个可选的注释。例如，您可以包含公有密钥的到期日期。

1. 选择 **Add**。

您可以重复此过程中的步骤，添加更多密钥供 CloudFront 使用。

### 第 3 步：为字段级加密创建配置文件
<a name="field-level-encryption-setting-up-step3"></a>

在您至少将一个公有密钥添加到 CloudFront 后，创建一个配置文件，在其中告诉 CloudFront 加密哪些字段。<a name="field-level-encryption-setting-up-step3-procedure"></a>

**为字段级加密创建配置文件 (控制台)**

1. 在导航窗格中，选择**字段级加密**。

1. 选择**创建配置文件**。

1. 填写以下字段：  
**配置文件名称**  
为配置文件键入唯一名称。该名称不能包含空格，且只能包含字母数字字符、下划线 (\$1) 和连字符 (-)。最大字符数为 128。  
**公有密钥名称**  
在下拉列表中，选择您在第 2 步中添加到 CloudFront 的公有密钥的名称。CloudFront 可使用该密钥加密您在此配置文件中指定的字段。  
**提供商名称**  
键入短语以帮助识别密钥，例如从其获取密钥对的提供商。在应用程序解密数据字段时，需要该信息以及私有密钥。提供商名称不能包含空格，且只能包含字母数字字符、冒号 (:)、下划线 (\$1) 和连字符 (-)。最大字符数为 128。  
**要匹配的字段名称模式**  
键入数据字段的名称，或者键入用于识别请求中希望 CloudFront 加密的数据字段名称的模式。选择 \$1 选项以添加您想要使用此密钥加密的所有字段。  
对于字段名称模式，您可以键入数据字段的整个名称，如 DateOfBirth，或者仅键入名称的第一部分及通配符 (\$1)，如 CreditCard\$1。除了可选的通配符 (\$1) 外，字段名称模式必须仅包含字母数字字符、方括号 ([ 和 ])、句点 (.)、下划线 (\$1) 和连字符 (-)。  
请确保您未针对不同的字段名称模式使用重叠字符。例如，如果您有一个字段名称模式 ABC\$1，您就不能再添加另一个字段名称模式 AB\$1。此外，字段名称区分大小写，并且您可以使用的字符的最大数目为 128。  
**评论**  
（可选）键入有关该配置文件的注释。您可以使用的最大字符数为 128。

1. 填写字段后，选择**创建配置文件**。

1. 如果您要添加更多配置文件，请选择**添加配置文件**。

### 第 4 步：创建配置
<a name="field-level-encryption-setting-up-step4"></a>

在您创建一个或多个字段级加密配置文件后，请创建一个配置，以便指定请求的内容类型，包括要加密的数据、要用于加密的配置文件以及用于指定希望 CloudFront 如何处理加密的其他选项。

例如，当 CloudFront 无法加密数据时，您可以指定在以下情况中，CloudFront 是阻止请求还是将请求转发到您的源：
+ **当请求的内容类型未在配置中时** – 如果尚未在配置中添加某种内容类型，则可以指定 CloudFront 是应将具有该内容类型的请求转发到源而不加密数据字段，还是阻止该请求并返回错误。
**注意**  
如果您将内容类型添加到配置中，但未指定要与该类型一起使用的配置文件，则 CloudFront 始终将带有该内容类型的请求转发到源。
+ **在查询参数中提供的配置文件名称未知时** – 在指定的 `fle-profile` 查询参数具有的配置文件名称对于分配不存在时，您可以指定 CloudFront 是应将请求发送到源而不加密数据字段，还是阻止该请求并返回错误。

在配置中，您还可以指定在 URL 中提供配置文件作为查询参数的操作是否覆盖您已为该查询映射到内容类型的配置文件。默认情况下，CloudFront 使用您已映射到内容类型的配置文件（如果您已指定）。这样一来，您便拥有一个默认使用的配置文件，但您可以决定要强制执行其他配置文件的某些请求。

因此，举例来说，您可以（在您的配置中）指定 **SampleProfile** 作为要使用的查询参数配置文件。然后，您可以使用 URL `https://d1234.cloudfront.net?fle-profile=SampleProfile` 而不是 `https://d1234.cloudfront.net` 以便 CloudFront 在该请求中使用 **SampleProfile** 而不是您为请求的内容类型设置的配置文件。

您可以为单个账户最多创建 10 个配置，然后将其中一个配置与该账户的任何分配的缓存行为相关联。<a name="field-level-encryption-setting-up-step4-procedure"></a>

**为字段级加密创建配置（控制台）**

1. 在**字段级加密**页面上，选择**创建配置**。

   注意：如果您尚未创建至少一个配置文件，您将不会看到配置创建选项。

1. 填写以下字段以指定要使用的配置文件。（某些字段无法更改。）  
**内容类型（无法更改）**  
内容类型将设置为 `application/x-www-form-urlencoded` 并且无法更改。  
**默认配置文件 ID（可选）**  
在下拉列表中，选择您要映射到**内容类型**字段中的内容类型的配置文件。  
**内容格式（无法更改）**  
内容格式将设置为 `URLencoded` 并且无法更改。

1. 如果您要更改以下选项的默认 CloudFront 行为，请选择适当的复选框。  
**当请求的内容类型未配置时，将请求转发到原始地址**  
如果您要允许请求转到源，*并且您尚未指定要用于请求的内容类型的配置文件，*则选中该复选框。  
**使用提供的查询参数覆盖内容类型的配置文件**  
如果您要允许某个查询参数中提供的配置文件*覆盖您为内容类型指定的配置文件*，则选中该复选框。

1. 如果您选中该复选框以允许查询参数覆盖默认配置文件，则您必须完成以下额外配置字段。您最多可以创建五个查询参数映射供查询使用。  
**查询参数**  
键入要包含在 `fle-profile` 查询参数的 URL 中的值。对于此查询的字段级加密，此值告诉 CloudFront 使用与此查询参数关联的配置文件 ID（在下一个字段中指定）。  
您可以使用的最大字符数为 128。该值不能包含空格，并且必须仅使用字母数字或以下字符：短划线 (-)、句点 (.)、下划线 (\$1)、星号 (\$1)、加号 (\$1)、百分号 (%)。  
**配置文件 ID**  
在下拉列表中，选择您要与为**查询参数**键入的值相关联的配置文件。  
**当查询参数中指定的配置文件不存在时，将请求转发到源**  
如果您要允许请求转到您的源，*并且在查询参数中指定的配置文件没有在 CloudFront 中定义，*则选择该复选框。

### 第 5 步：将配置添加到缓存行为
<a name="field-level-encryption-setting-up-step5"></a>

要使用字段级加密，需要将配置链接到分配的缓存行为，方法为添加配置 ID 作为分配的值。

**重要**  
要将字段级加密配置链接到缓存行为，必须将分配配置为始终使用 HTTPS，并接受来自查看器的 HTTP `POST` 和 `PUT` 请求。也就是说，必须满足以下条件：  
缓存行为的**查看器协议策略**必须设置为**将 HTTP 重定向到 HTTPS** 或**仅 HTTPS**。（在 CloudFormation 或 CloudFront API 中，`ViewerProtocolPolicy` 必须设置为 `redirect-to-https` 或 `https-only`。）
缓存行为的 **Allowed HTTP Methods（允许的 HTTP 方法）**必须设置为 **GET、HEAD、OPTIONS、PUT、POST、PATCH、DELETE**。（在 CloudFormation 或 CloudFront API 中，`AllowedMethods` 必须设置为 `GET`、`HEAD`、`OPTIONS`、`PUT`、`POST`、`PATCH`、`DELETE`。可以按照任意顺序指定这些项。）
源设置的**源协议策略**必须设置为**匹配查看器**或**仅 HTTPS**。（在 CloudFormation 或 CloudFront API 中，`OriginProtocolPolicy` 必须设置为 `match-viewer` 或 `https-only`。）

有关更多信息，请参阅 [所有分配设置参考](distribution-web-values-specify.md)。

## 在源端解密数据字段
<a name="field-level-encryption-decrypt"></a>

CloudFront 使用 [AWS Encryption SDK](https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/introduction.html) 来加密数据字段。在您的整个应用程序堆栈中，数据保持加密，并且只能由具有用于解密数据的凭证的应用程序进行访问。

加密后，密码文本采用 base64 编码。当您的应用程序解密源上的文本时，应用程序必须首先解码密码文本，然后使用 AWS 加密开发工具包解密数据。

以下代码示例说明了应用程序如何解密源上的数据。请注意以下几点：
+ 为简化示例，此示例从工作目录中的文件内加载公有密钥和私有密钥（采用 DER 格式）。实际上，您会将私有密钥存储在安全的离线位置（如离线硬件安全模块），并将公有密钥分配给您的开发团队。
+ CloudFront 在加密数据时使用特定信息，应在源上使用同一组参数进行解密。初始化主密钥时 CloudFront 使用的参数包括以下这些：
  + PROVIDER\$1NAME：您在创建字段级加密配置文件时指定该值。在此处使用相同的值。
  + KEY\$1NAME：当您将公有密钥上传到 CloudFront 时创建了其名称，然后在配置文件中指定了密钥名称。在此处使用相同的值。
  + ALGORITHM：CloudFront 使用 `RSA/ECB/OAEPWithSHA-256AndMGF1Padding` 作为加密算法，因此，您必须使用相同的算法解密数据。
+ 如果您在运行以下示例程序时使用密码文本作为输入，则解密的数据会输出到您的控制台。有关更多信息，请参阅 [加密开发工具包中的 ](https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/java-example-code.html)Java 代码示例AWS。

### 代码示例
<a name="field-level-encryption-decrypt-sample"></a>

```
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

import org.apache.commons.codec.binary.Base64;

import com.amazonaws.encryptionsdk.AwsCrypto;
import com.amazonaws.encryptionsdk.CryptoResult;
import com.amazonaws.encryptionsdk.jce.JceMasterKey;

/**
 * Sample example of decrypting data that has been encrypted by CloudFront field-level encryption.
 */
public class DecryptExample {

    private static final String PRIVATE_KEY_FILENAME = "private_key.der";
    private static final String PUBLIC_KEY_FILENAME = "public_key.der";
    private static PublicKey publicKey;
    private static PrivateKey privateKey;

    // CloudFront uses the following values to encrypt data, and your origin must use same values to decrypt it.
    // In your own code, for PROVIDER_NAME, use the provider name that you specified when you created your field-level
    // encryption profile. This sample uses 'DEMO' for the value.
    private static final String PROVIDER_NAME = "DEMO";
    // In your own code, use the key name that you specified when you added your public key to CloudFront. This sample
    // uses 'DEMOKEY' for the key name.
    private static final String KEY_NAME = "DEMOKEY";
    // CloudFront uses this algorithm when encrypting data.
    private static final String ALGORITHM = "RSA/ECB/OAEPWithSHA-256AndMGF1Padding";

    public static void main(final String[] args) throws Exception {

        final String dataToDecrypt = args[0];

        // This sample uses files to get public and private keys.
        // In practice, you should distribute the public key and save the private key in secure storage.
        populateKeyPair();

        System.out.println(decrypt(debase64(dataToDecrypt)));
    }

    private static String decrypt(final byte[] bytesToDecrypt) throws Exception {
        // You can decrypt the stream only by using the private key.

        // 1. Instantiate the SDK
        final AwsCrypto crypto = new AwsCrypto();

        // 2. Instantiate a JCE master key
        final JceMasterKey masterKey = JceMasterKey.getInstance(
                publicKey,
                privateKey,
                PROVIDER_NAME,
                KEY_NAME,
                ALGORITHM);

        // 3. Decrypt the data
        final CryptoResult <byte[], ? > result = crypto.decryptData(masterKey, bytesToDecrypt);
        return new String(result.getResult());
    }

    // Function to decode base64 cipher text.
    private static byte[] debase64(final String value) {
        return Base64.decodeBase64(value.getBytes());
    }

    private static void populateKeyPair() throws Exception {
        final byte[] PublicKeyBytes = Files.readAllBytes(Paths.get(PUBLIC_KEY_FILENAME));
        final byte[] privateKeyBytes = Files.readAllBytes(Paths.get(PRIVATE_KEY_FILENAME));
        publicKey = KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(PublicKeyBytes));
        privateKey = KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(privateKeyBytes));
    }
}
```