

本文為英文版的機器翻譯版本，如內容有任何歧義或不一致之處，概以英文版為準。

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

Parquet 模組化加密提供單欄式層級存取控制和加密，以增強以 Parquet 檔案格式儲存的資料的隱私權和資料完整性。從 6.6.0 版開始，Amazon EMR Hive 中提供此功能。

《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. 實作 AWS KMS 服務的 KmsClient，如 文件 [PARQUET-1373：加密金鑰管理工具](https://docs.google.com/document/d/1bEu903840yb95k9q2X-BlsYKuXoygE4VnMDl9xz_zhk/edit)中所述。下列範例顯示實作程式碼片段。

   ```
   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\$1ECS\$1default。

1. 在 Amazon EMR 叢集上的 Hive 應用程式上，使用 `ADD JAR` 陳述式新增上面的用戶端，如 [Apache Hive 資源文件](https://cwiki.apache.org/confluence/display/Hive/LanguageManual+Cli#LanguageManualCli-HiveResources)所述。以下是陳述式範例：

   ```
   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
   ```