パーケット・モジュラー暗号

データがカラム形式で保存されている場合、Parquetファイルの書き込み時にParquetのモジュール式暗号化を使用して機密性の高いカラムを暗号化し、暗号化されたファイルを読み込む際にこれらのカラムを復号化することができます。 列単位でデータを暗号化することで、どの列を暗号化するかを決定し、列へのアクセスをどのように制御するかを設定できます。

プライバシーを保護するだけでなく、Parquetのモジュール型暗号化は、保存されたデータの完全性も保護します。 ファイルの内容へのいかなる改ざんも検出され、読み取り側で例外が発生します。

主な機能は以下の通りです:

  1. Parquetのモジュール式暗号化および復号化は、Sparkクラスタ上で実行されます。 したがって、機密データや暗号化キーはストレージからは確認できません。

  2. エンコーディング、圧縮、カラム型投影、述語プッシュダウンといったParquetの標準機能は、Parquetモジュラー暗号化形式のファイルでも通常通り動作します。

  3. Parquet仕様で定義されている2つの暗号化アルゴリズムのうち、いずれか1つを選択できます。 ただし、どちらのアルゴリズムも列単位の暗号化に対応しています:

    • デフォルトのアルゴリズムは、Parquetファイル内のデータおよび AES-GCM メタデータ部分に対する改ざんを完全に防止します。
    • この代替アルゴリズムは、Parquet ファイルの部分 AES-GCM-CTR 的な整合性保護をサポートしています。 改ざんから保護されるのはメタデータ部分のみであり、データ部分ではありません。 このアルゴリズムの利点は、従来の AES-GCM アルゴリズムに比べてスループットのオーバーヘッドが小さいことです。
  4. 暗号化する列を選択できます。 その他の列は暗号化されないため、スループットのオーバーヘッドが削減されます。

  5. 異なる列には、それぞれ異なる鍵で暗号化することができます。

  6. デフォルトでは、Parquetのメインメタデータモジュール(ファイルのフッター)は暗号化されており、ファイルのスキーマや機密性の高い列の一覧が隠されています。 ただし、レガシーなリーダー(Parquetのモジュール式暗号化をまだサポートしていない他のSparkディストリビューションなど)が、暗号化されたファイル内の暗号化されていない列を読み取れるようにするため、ファイルのフッターを暗号化しないように設定することも可能です。

  7. 暗号化キーの管理方法は、以下の2つの方法のいずれかで行えます:

    • アプリケーションから直接。 「アプリケーションごとの鍵管理」 を参照してください。
    • Sparkサービスで使用される暗号化キーを生成、保存、破棄するキー管理システム(KMS)によって。 これらのキーはKMSサーバーから外部に持ち出されることはなく、したがってSparkサービスを含む他のコンポーネントからは認識されません。 「KMSによる鍵管理」 を参照してください。
    注: アプリケーションまたはKMSは、マスター暗号化キーのみを管理する必要があります。

    機密性の高い各カラムについて、暗号化に使用する主キーを指定する必要があります。 また、各暗号化ファイル(データフレーム)のフッターには、マスターキーを指定する必要があります。 デフォルトでは、フッターの暗号化には「footer」キーが使用されます。 ただし、プレーンテキストのフッターモードを選択した場合、フッターは暗号化されず、鍵はフッターの完全性検証にのみ使用されます。

    暗号化パラメータは、標準的なSparkの Hadoop 設定を通じて渡すことができます。例えば、アプリケーションの SparkContext: ファイル内の Hadoop 設定で設定値を指定することで渡すことができます

    sc.hadoopConfiguration.set("<parameter name>" , "<parameter value>")
    

    あるいは、書き込みオプションを使用してパラメータ値を指定することもできます:

    <data frame name>.write
    .option("<parameter name>" , "<parameter value>")
    .parquet("<write path>")
    

必須パラメーター

暗号化されたデータを書き込むには、以下のパラメータが必要です:

  • 暗号化対象の列の一覧と、マスター暗号化キー:

    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 パラメーターも設定されていない場合、ファイルは暗号化されません。 これらのパラメータのうち1つでも設定されていない場合、暗号化されたファイルにはこれらのパラメータが必須であるため、例外が発生します。

オプション・パラメーター

暗号化されたデータを書き込む際には、以下のオプションパラメータを使用できます:

  • 暗号化アルゴリズム AES-GCM-CTR

    デフォルトでは、Parquetのモジュール式暗号化では、Parquetファイル内のデータおよびメタデータの改ざんを完全に防止するアルゴリズム AES-GCM が使用されます。 ただし、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のメインメタデータモジュール(ファイルのフッター)は暗号化されており、ファイルのスキーマや機密性の高い列の一覧が隠されています。 ただし、まだParquetモジュラー暗号化に対応していない他のSparkや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()
    

Parquetのモジュール式暗号化を使用するSparkジョブの提出例

次の例は、Parquetのモジュール式暗号化を使用するSparkジョブをSparkインスタンスに送信する方法を示しています。 この例では、デフォルトの Spark テンプレートを使用して、 InMemoryKMS.pyファイルから Python spark ジョブを実行依頼します。 この例では、このファイルがストレージボリュームのルート zen::kms-volume ディレクトリに存在することを前提としています。

{
    "application_details": {
        "application": "/kms-app/kms.py",
        "conf": {
          "ae.spark.executor.count": 2,
          "spark.driver.memory": "4G",
          "spark.executor.memory": "4G"
        }
    },
    "volumes": [
        {
            "name": "zen::kms-volume",
            "mount_path": "/kms-app"
        }
    ]
}

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ファイル内のデータおよびメタデータモジュールを暗号化するために使用されます。

その後、データ暗号化キーは、各マスターキーに対してSpark/Parquet内部で生成されるキー暗号化キー(KEK)を用いて暗号化されます。 暗号化キーは、ローカルでマスター暗号化キー(MEK)を使用して暗号化されます。

暗号化データの暗号化キーおよびキーの暗号化キーは、マスターキーの識別子とともに、Parquetファイルのメタデータに格納されます。 各暗号化キーには一意の識別子(ローカルで生成された安全な16バイトのランダム値)があり、これもファイルのメタデータに保存されます。

Parquetファイルを読み込む際、マスター暗号化キー(MEK)の識別子、暗号化されたキー暗号化キー(KEK)とその識別子、および暗号化されたデータ暗号化キー(DEK)が、ファイルのメタデータから抽出されます。

暗号化キーは、ローカルでマスター暗号化キーを使用して復号されます。 その後、データ暗号化キー(DEK)は、キー暗号化キー(KEK)を使用してローカルで復号されます。

詳細情報