如果数据以列格式存储,您可使用 Parquet 模块化加密在写入 Parquet 文件时将敏感列加密,并在读取已加密的文件时将这些列解密。 通过在列级别将数据加密,您可以决定要将哪些列加密,以及如何控制列访问权。
除确保隐私之外,Parquet 模块化加密还可以保护已存储的数据的完整性。 对文件内容的任何篡改都会被检测到,并触发读程序端异常。
主要功能包括:
Parquet 模块化加密和解密是在 Spark 集群上执行。 因此,敏感数据和加密密钥对存储器不可见。
标准 Parquet 功能(例如,编码、压缩、按列投影和谓词下推)继续正常作用于使用 Parquet 模块化加密格式的文件。
您可以选择 Parquet 规范中定义的两种加密算法中的一种。 但是,这两种算法都支持列加密:
- 缺省算法
AES-GCM
提供全面保护,防止篡改 Parquet 文件中的数据和元数据部分。 - 备用算法
AES-GCM-CTR
支持对 Parquet 文件进行局部完整性保护。 只会保护元数据部分不被篡改,不保护数据部分。 这种算法的优点是,与AES-GCM
算法相比,它的吞吐量开销较低。
- 缺省算法
您可以选择要加密的列。 其他列将不会加密,从而降低吞吐量开销。
不同的列可以使用不同密钥进行加密。
缺省情况下,主 Parquet 元数据模块(文件尾)会进行加密,以隐藏文件模式和敏感列列表。 但是,您可以选择不加密文件页脚,以便使旧阅读器 (例如,其他尚不支持 Parquet 模块化加密的 Spark 分发版) 能够读取加密文件中未加密的列。
管理加密密钥的方式有两种:
- 直接由应用程序管理。 请参阅按应用程序的密钥管理。
- 由密钥管理系统 (KMS) 管理,该系统负责生成、存储和销毁 Spark 服务所使用的加密密钥。 这些密钥永远不会离开 KMS 服务器,因此对其他组件(包括 Spark 服务)不可见。 请参阅通过 KMS 进行密钥管理。
注:只有主加密密钥 (MEK) 需要由应用程序或 KMS 管理。
对于每个敏感列,必须指定要用于加密的主密钥。 此外,必须为每个加密文件(数据框)的文件尾指定主密钥。 缺省情况下,文件尾密钥用于文件尾加密。 但是,如果您选择明文文件尾方式,那么文件尾不会加密,该密钥仅用于文件尾的完整性验证。
The encryption parameters can be passed via the standard Spark Hadoop configuration, for example by setting configuration values in the Hadoop configuration of the application's SparkContext:
sc.hadoopConfiguration.set("<parameter name>" , "<parameter value>")
或者,可以通过写选项传递参数值:
<data frame name>.write .option("<parameter name>" , "<parameter value>") .parquet("<write path>")
使用 Parquet 模块化加密运行
Parquet 模块化加密仅用于在 IBM Analytics Engine 服务实例中运行的 Spark Notebook。 在 Spark 环境中运行的 Notebook 中不支持 Parquet 模块化加密。
要启用 Parquet 模块化加密,请设置以下 Spark 类路径属性以指向实施 Parquet 模块化加密的 Parquet jar 文件,以及密钥管理 jar 文件:
浏览至 Ambari > Spark> Config-> Custom spark2-default。
添加以下两个参数以明确指向 JAR 文件的位置。 确保编辑路径以使用集群上实际版本的 jar 文件。
spark.driver.extraClassPath=/home/common/lib/parquetEncryption/ibm-parquet-kms-<latestversion>-jar-with-dependencies.jar:/home/common/lib/parquetEncryption/parquet-format-<latestversion>.jar:/home/common/lib/parquetEncryption/parquet-hadoop-<latestversion>.jar spark.executor.extraClassPath=/home/common/lib/parquetEncryption/ibm-parquet-<latestversion>-jar-with-dependencies.jar:/home/common/lib/parquetEncryption/parquet-format-<latestversion>.jar:/home/common/lib/parquetEncryption/parquet-hadoop-<latestversion>.jar
必要参数
需要下列参数才能写加密数据:
要加密的一组列,以及主加密密钥:
parameter name: "encryption.column.keys" parameter value: "<master key ID>:<column>,<column>;<master key ID>:<column>,.."
文件尾密钥:
parameter name: "encryption.footer.key" parameter value: "<master key ID>"
例如:
dataFrame.write .option("encryption.footer.key" , "k1") .option("encryption.column.keys" , "k2:SSN,Address;k3:CreditCard") .parquet("<path to encrypted files>")
重要说明:如果既未设置
encryption.column.keys
参数,也未设置encryption.footer.key
参数,那么将不会对文件进行加密。 如果仅设置其中一个参数,那么会抛出异常,因为这两个参数都是加密文件所必需。
可选参数
写入已加密的数据时,可以使用下列可选参数:
加密算法
AES-GCM-CTR
缺省情况下,Parquet 模块化加密会使用
AES-GCM
算法,该算法提供全面的保护,防止 Parquet 文件中的数据和元数据遭篡改。 但是,因为 Spark 2.3.0 在 Java 8 上运行,而 Java 8 不支持 CPU 硬件中的 AES 加速(这是直至 Java 9 才新增的功能),在某些情况下,数据完整性验证的开销可能会影响工作负载吞吐量。为加以弥补,您可以关闭数据完整性验证支持,并使用备用算法
AES-GCM-CTR
来写入已加密的文件,该算法仅验证元数据部分的完整性,不验证数据部分,因而吞吐量开销相较于AES-GCM
算法更低。parameter name: "encryption.algorithm" parameter value: "AES_GCM_CTR_V1"
适用于旧式读程序的明文文件尾方式
缺省情况下,主 Parquet 元数据模块(文件尾)会进行加密,以隐藏文件模式和敏感列列表。 但是,您可以决定不加密文件页脚,以便使其他 Spark 和 Parquet 阅读器 (尚不支持 Parquet 模块化加密) 能够读取加密文件中未加密的列。 要关闭文件尾加密,请设置以下参数:
parameter name: "encryption.plaintext.footer" parameter value: "true"
重要说明:还必须在纯文本页脚方式中指定
encryption.footer.key
参数。 虽然文件尾未加密,但密钥用于签署文件尾内容,这意味着新的读程序可以验证其完整性。 添加文件尾签名对旧式读程序没有影响。
用法示例
Python 的以下样本代码片段显示了如何创建数据帧,如何写入加密的 parquet 文件以及如何从加密的 parquet 文件中读取数据帧。
Python:写入已加密的数据:
from pyspark.sql import Row squaresDF = spark.createDataFrame( sc.parallelize(range(1, 6)) .map(lambda i: Row(int_column=i, square_int_column=i ** 2))) sc._jsc.hadoopConfiguration().set("encryption.key.list", "key1: AAECAwQFBgcICQoLDA0ODw==, key2: AAECAAECAAECAAECAAECAA==") sc._jsc.hadoopConfiguration().set("encryption.column.keys", "key1:square_int_column") sc._jsc.hadoopConfiguration().set("encryption.footer.key", "key2") encryptedParquetPath = "squares.parquet.encrypted" squaresDF.write.parquet(encryptedParquetPath)
Python:读取已加密的数据:
sc._jsc.hadoopConfiguration().set("encryption.key.list", "key1: AAECAwQFBgcICQoLDA0ODw==, key2: AAECAAECAAECAAECAAECAA==") encryptedParquetPath = "squares.parquet.encrypted" parquetFile = spark.read.parquet(encryptedParquetPath) parquetFile.show()
Python 作业文件 InMemoryKMS.py
的内容如下所示:
from pyspark.sql import SparkSession
from pyspark import SparkContext
from pyspark.sql import Row
if __name__ == "__main__":
spark = SparkSession \
.builder \
.appName("InMemoryKMS") \
.getOrCreate()
sc = spark.sparkContext
##KMS operation
print("Setup InMemoryKMS")
hconf = sc._jsc.hadoopConfiguration()
encryptedParquetFullName = "testparquet.encrypted"
print("Write Encrypted Parquet file")
hconf.set("encryption.key.list", "key1: AAECAwQFBgcICQoLDA0ODw==, key2: AAECAAECAAECAAECAAECAA==")
btDF = spark.createDataFrame(sc.parallelize(range(1, 6)).map(lambda i: Row(ssn=i, value=i ** 2)))
btDF.write.mode("overwrite").option("encryption.column.keys", "key1:ssn").option("encryption.footer.key", "key2").parquet(encryptedParquetFullName)
print("Read Encrypted Parquet file")
encrDataDF = spark.read.parquet(encryptedParquetFullName)
encrDataDF.createOrReplaceTempView("bloodtests")
queryResult = spark.sql("SELECT ssn, value FROM bloodtests")
queryResult.show(10)
sc.stop()
spark.stop()
加密密钥处理内幕
写入 Parquet 文件时,就会为每一个已加密的列以及文件尾生成随机数据加密密钥 (DEK)。 这些密钥用于将 Parquet 文件中的数据和元数据模块加密。
然后,使用密钥加密密钥 (KEK) 将数据加密密钥加密,其中,KEK 也是在 Spark/Parquet 中针对每个主密钥生成。 该密钥加密密钥会使用主加密密钥 (MEK) 在本地进行加密。
已加密的数据加密密钥和密钥加密密钥随主密钥身份存储在 Parquet 文件元数据中。 每个密钥加密密钥都有唯一的身份(以安全的随机 16 字节值形式在本地生成),而且也存储在文件元数据中。
读取 Parquet 文件时,就会从文件元数据中抽取主加密密钥 (MEK) 的标识和已加密的密钥加密密钥 (KEK) 及其标识,以及已加密的数据加密密钥 (DEK)。
该密钥加密密钥会使用主加密密钥在本地进行解密。 然后,使用密钥加密密钥 (KEK) 在本地将数据加密密钥 (DEK) 解密。
了解更多信息
父主题: Notebook 和脚本