

本文属于机器翻译版本。若本译文内容与英语原文存在差异，则一律以英文原文为准。

# Hive 中的 Parquet 模块化加密
<a name="hive-parquet-modular-encryption"></a>

Parquet 模块化加密提供列级访问控制和加密功能，以增强以 Parquet 文件格式存储的数据的隐私和数据完整性。Amazon EMR Hive 从版本 6.6.0 起提供此功能。

有关以前支持的安全性和完整性解决方案，包括文件加密或存储层加密的信息，详见《Amazon EMR 管理指南》中的 [加密选项](https://docs.aws.amazon.com/emr/latest/ManagementGuide/emr-data-encryption-options.html)。这些解决方案可以用于 Parquet 文件，但是利用集成 Parquet 加密机制的新功能可以在列级别实现精细访问控制，并提高性能和安全性。有关此功能的更多信息，请访问 Apache github 页面 [Parquet 模块化加密](https://github.com/apache/parquet-format/blob/master/Encryption.md)。

用户使用 Hadoop 配置将配置传递给 Parquet 读取器和写入器。有关用户配置读取器和写入器以启用加密以及切换高级功能的详细配置，详见 [PARQUET-1854：Parquet 加密管理的属性驱动接口](https://docs.google.com/document/d/1boH6HPkG0ZhgxcaRkGk3QpZ8X_J91uXZwVGwYN45St4/edit) 

## 用法示例
<a name="usage-examples"></a>

以下示例涉及使用 AWS KMS 创建加密密钥并写入 Hive 表以进行加密密钥管理。

1. 如文档 [PARQUET-1373：加密密钥管理工具](https://docs.google.com/document/d/1bEu903840yb95k9q2X-BlsYKuXoygE4VnMDl9xz_zhk/edit)中所述，为 AWS KMS 服务实现。 KmsClient 以下示例演示了一个实施片段。

   ```
   package org.apache.parquet.crypto.keytools;
   
   import com.amazonaws.AmazonClientException;
   import com.amazonaws.AmazonServiceException;
   import com.amazonaws.regions.Regions;
   import com.amazonaws.services.kms.AWSKMS;
   import com.amazonaws.services.kms.AWSKMSClientBuilder;
   import com.amazonaws.services.kms.model.DecryptRequest;
   import com.amazonaws.services.kms.model.EncryptRequest;
   import com.amazonaws.util.Base64;
   import org.apache.hadoop.conf.Configuration;
   import org.apache.parquet.crypto.KeyAccessDeniedException;
   import org.apache.parquet.crypto.ParquetCryptoRuntimeException;
   import org.apache.parquet.crypto.keytools.KmsClient;
   import org.slf4j.Logger;
   import org.slf4j.LoggerFactory;
   
   import java.nio.ByteBuffer;
   import java.nio.charset.Charset;
   import java.nio.charset.StandardCharsets;
   
   public class AwsKmsClient implements KmsClient {
   
       private static final AWSKMS AWSKMS_CLIENT = AWSKMSClientBuilder
               .standard()
               .withRegion(Regions.US_WEST_2)
               .build();
       public static final Logger LOG = LoggerFactory.getLogger(AwsKmsClient.class);
   
       private String kmsToken;
       private Configuration hadoopConfiguration;
   
       @Override
       public void initialize(Configuration configuration, String kmsInstanceID, String kmsInstanceURL, String accessToken) throws KeyAccessDeniedException {
           hadoopConfiguration = configuration;
           kmsToken = accessToken;
   
       }
   
       @Override
       public String wrapKey(byte[] keyBytes, String masterKeyIdentifier) throws KeyAccessDeniedException {
           String value = null;
           try {
               ByteBuffer plaintext = ByteBuffer.wrap(keyBytes);
   
               EncryptRequest req = new EncryptRequest().withKeyId(masterKeyIdentifier).withPlaintext(plaintext);
               ByteBuffer ciphertext = AWSKMS_CLIENT.encrypt(req).getCiphertextBlob();
   
               byte[] base64EncodedValue = Base64.encode(ciphertext.array());
               value = new String(base64EncodedValue, Charset.forName("UTF-8"));
           } catch (AmazonClientException ae) {
               throw new KeyAccessDeniedException(ae.getMessage());
           }
           return value;
       }
   
       @Override
       public byte[] unwrapKey(String wrappedKey, String masterKeyIdentifier) throws KeyAccessDeniedException {
           byte[] arr = null;
           try {
               ByteBuffer ciphertext  = ByteBuffer.wrap(Base64.decode(wrappedKey.getBytes(StandardCharsets.UTF_8)));
               DecryptRequest request = new DecryptRequest().withKeyId(masterKeyIdentifier).withCiphertextBlob(ciphertext);
               ByteBuffer decipheredtext = AWSKMS_CLIENT.decrypt(request).getPlaintext();
               arr = new byte[decipheredtext.remaining()];
               decipheredtext.get(arr);
           } catch (AmazonClientException ae) {
               throw new KeyAccessDeniedException(ae.getMessage());
           }
           return arr;
       }
   }
   ```

1. 按照*AWS Key Management Service 开发人员指南*中的创建密钥中所述，为页脚以及您的 IAM 角色具有访问权限的列[创建 AWS KMS 加密密钥](https://docs.aws.amazon.com/kms/latest/developerguide/create-keys.html)。默认 IAM 角色为 EMR\_ECS\_default。

1. 按照 [Apache Hive 资源文档](https://cwiki.apache.org/confluence/display/Hive/LanguageManual+Cli#LanguageManualCli-HiveResources) 所述，在 Amazon EMR 集群上的 Hive 应用程序中，使用上述 `ADD JAR` 语句添加客户端。下面是一个示例语句：

   ```
   ADD JAR 's3://location-to-custom-jar';
   ```

   另一种方法是使用引导操作将 JAR 添加到 Hive 的 `auxlib` 中。以下是要添加到引导操作的示例行：

   ```
   aws s3 cp 's3://location-to-custom-jar' /usr/lib/hive/auxlib 
   ```

1. 设置下列配置值：

   ```
   set parquet.crypto.factory.class=org.apache.parquet.crypto.keytools.PropertiesDrivenCryptoFactory;
   set parquet.encryption.kms.client.class=org.apache.parquet.crypto.keytools.AwsKmsClient;
   ```

1. 创建一个 Parquet 格式的 Hive 表，在 SERDEPROPERTIES 中指定 AWS KMS 密钥并向其中插入一些数据：

   ```
   CREATE TABLE my_table(name STRING, credit_card STRING)
   ROW FORMAT SERDE 'org.apache.hadoop.hive.ql.io.parquet.serde.ParquetHiveSerDe’
   WITH SERDEPROPERTIES (
     'parquet.encryption.column.key’=<aws-kms-key-id-for-column-1>: credit_card’,
     'parquet.encryption.footer.key’='<aws-kms-key-id-for-footer>’)
   STORED AS parquet
   LOCATION “s3://<bucket/<warehouse-location>/my_table”;
   
   INSERT INTO my_table SELECT 
   java_method ('org.apache.commons.lang.RandomStringUtils','randomAlphabetic',5) as name,
   java_method ('org.apache.commons.lang.RandomStringUtils','randomAlphabetic',10) as credit_card
   from (select 1) x lateral view posexplode(split(space(100),' ')) pe as i,x;
   
   select * from my_table;
   ```

1. 验证当您在同一位置创建无法访问 AWS KMS 密钥的外部表时（例如，IAM 角色访问被拒绝），您是否无法读取数据。

   ```
   CREATE EXTERNAL TABLE ext_table (name STRING, credit_card STRING)
   ROW FORMAT SERDE 'org.apache.hadoop.hive.ql.io.parquet.serde.ParquetHiveSerDe’
   STORED AS parquet
   LOCATION “s3://<bucket>/<warehouse-location>/my_table”;
   
   SELECT * FROM ext_table;
   ```

1. 最后一条语句应触发以下异常：

   ```
   Failed with exception java.io.IOException:org.apache.parquet.crypto.KeyAccessDeniedException: Footer key: access denied
   ```