@shinyaz

AWS Payment Cryptography 番外編 — TR-31 による鍵のエクスポート・インポートを実装する

目次

はじめに

シリーズ本編では Java SDK を使って鍵の作成と使用に焦点を当てたが、実運用では既存の HSM からの鍵移行や、パートナーとの鍵交換が必要になる。

鍵のインポート/エクスポートは、初期移行や定期ローテーションなど運用寄りの作業であり、実務では CLI やスクリプトで行うのが一般的である。特に TR-34 による KEK の確立は証明書チェーンの構築が絡むため、スクリプトが現実的な選択肢になる。

本記事では、Python(boto3)を使って TR-31 による鍵のエクスポート・インポートを実装する。KEK で CVV 鍵を包んでエクスポートし、同じ KEK でインポートした後、元の鍵と同じ CVV2 が生成されることを確認する。

前提条件

  • 入門編の内容を理解していること
  • Python 3.9 以上、boto3
  • IAM 権限:payment-cryptography:*(検証用)
  • 検証リージョン:us-east-1

TR-31 鍵交換の仕組み

決済暗号処理では、パートナー間や HSM 間で鍵を安全に移動する必要がある。鍵素材を平文で転送するのはセキュリティ上許容されないため、「鍵を暗号化するための鍵」(KEK: Key Encryption Key)で包んで転送する。

TR-31(ANSI X9 TR-31)は、この鍵交換のための業界標準フォーマットである。鍵素材を KEK で暗号化し、鍵の属性(用途、アルゴリズム、操作モード)をヘッダーに含めたキーブロックとして転送する。受信側は同じ KEK でキーブロックを復号し、ヘッダーから属性を自動的に復元する。

ソースコードと実行

key_exchange_demo.py(全ステップを含む実行可能なスクリプト)
key_exchange_demo.py
#!/usr/bin/env python3
"""TR-31 鍵のエクスポート・インポート検証スクリプト"""
 
import boto3
 
REGION = "us-east-1"
cp = boto3.client("payment-cryptography", region_name=REGION)
dp = boto3.client("payment-cryptography-data", region_name=REGION)
 
# Step 1: KEK の作成
kek = cp.create_key(
    Exportable=True,
    KeyAttributes={
        "KeyUsage": "TR31_K0_KEY_ENCRYPTION_KEY",
        "KeyClass": "SYMMETRIC_KEY",
        "KeyAlgorithm": "AES_256",
        "KeyModesOfUse": {
            "Encrypt": True, "Decrypt": True,
            "Wrap": True, "Unwrap": True,
        },
    },
)["Key"]
print(f"[KEK] {kek['KeyAttributes']['KeyAlgorithm']} KCV:{kek['KeyCheckValue']}")
 
# Step 2: CVV 鍵の作成と CVV2 生成
cvk = cp.create_key(
    Exportable=True,
    KeyAttributes={
        "KeyUsage": "TR31_C0_CARD_VERIFICATION_KEY",
        "KeyClass": "SYMMETRIC_KEY",
        "KeyAlgorithm": "TDES_2KEY",
        "KeyModesOfUse": {"Generate": True, "Verify": True},
    },
)["Key"]
print(f"[CVK] {cvk['KeyAttributes']['KeyAlgorithm']} KCV:{cvk['KeyCheckValue']}")
 
original = dp.generate_card_validation_data(
    KeyIdentifier=cvk["KeyArn"],
    PrimaryAccountNumber="4111111111111111",
    ValidationDataLength=3,
    GenerationAttributes={"CardVerificationValue2": {"CardExpiryDate": "0328"}},
)
print(f"Original CVV2: {original['ValidationData']}")
 
# Step 3: TR-31 エクスポート
export_resp = cp.export_key(
    ExportKeyIdentifier=cvk["KeyArn"],
    KeyMaterial={"Tr31KeyBlock": {"WrappingKeyIdentifier": kek["KeyArn"]}},
)
key_block = export_resp["WrappedKey"]["KeyMaterial"]
print(f"WrappedKeyBlock: {key_block[:40]}...")
 
# Step 4: TR-31 インポート
import_resp = cp.import_key(
    KeyMaterial={
        "Tr31KeyBlock": {
            "WrappingKeyIdentifier": kek["KeyArn"],
            "WrappedKeyBlock": key_block,
        }
    }
)
imported = import_resp["Key"]
print(f"[Imported] KCV:{imported['KeyCheckValue']} Origin:{imported['KeyOrigin']}")
 
# Step 5: インポートした鍵で CVV2 生成
imported_cvv2 = dp.generate_card_validation_data(
    KeyIdentifier=imported["KeyArn"],
    PrimaryAccountNumber="4111111111111111",
    ValidationDataLength=3,
    GenerationAttributes={"CardVerificationValue2": {"CardExpiryDate": "0328"}},
)
print(f"Imported CVV2: {imported_cvv2['ValidationData']}")
print(f"Match: {original['ValidationData'] == imported_cvv2['ValidationData']}")
 
# クリーンアップ
for arn in [cvk["KeyArn"], imported["KeyArn"], kek["KeyArn"]]:
    cp.delete_key(KeyIdentifier=arn, DeleteKeyInDays=3)
実行手順
Terminal
pip install boto3
 
python3 key_exchange_demo.py

検証:CVV 鍵のエクスポートとインポート

以下の 5 ステップで、鍵のエクスポート→インポート→同一性検証を行う。

  1. KEK(鍵暗号化鍵)を作成
  2. CVV 鍵を作成し、CVV2 を生成(エクスポート前の基準値)
  3. CVV 鍵を KEK で TR-31 エクスポート
  4. キーブロックを同じ KEK で TR-31 インポート
  5. インポートした鍵で CVV2 を生成し、基準値と一致することを確認

Step 1:KEK の作成

KEK は TR31_K0_KEY_ENCRYPTION_KEY で作成する。

Python
kek = cp.create_key(
    Exportable=True,
    KeyAttributes={
        "KeyUsage": "TR31_K0_KEY_ENCRYPTION_KEY",
        "KeyClass": "SYMMETRIC_KEY",
        "KeyAlgorithm": "AES_256",
        "KeyModesOfUse": {
            "Encrypt": True, "Decrypt": True,
            "Wrap": True, "Unwrap": True,
        },
    },
)["Key"]

Step 2:CVV 鍵の作成と CVV2 生成

CVV 鍵を作成し、CVV2 を生成する。この値がエクスポート→インポート後も同じであることを確認する基準値になる。

Output
[CVK] TDES_2KEY KCV:0DEBBF
元の鍵で CVV2 生成: 025

Step 3:TR-31 エクスポート

export_key で CVV 鍵を KEK で包んでエクスポートする。

Python
export_resp = cp.export_key(
    ExportKeyIdentifier=cvk["KeyArn"],
    KeyMaterial={"Tr31KeyBlock": {"WrappingKeyIdentifier": kek["KeyArn"]}},
)
key_block = export_resp["WrappedKey"]["KeyMaterial"]
Output
WrappedKeyBlock: D0112C0TC00E00006088943C9118219BA0113A95...

キーブロックの先頭がヘッダーで、鍵の属性が含まれている。

  • D — キーブロックバージョン(AES KEK を使用)
  • 0112 — キーブロック全体の長さ
  • C0 — KeyUsage(TR31_C0_CARD_VERIFICATION_KEY
  • T — Exportability(エクスポート可能)
  • C00E0000 — アルゴリズムとモード情報

インポート時にこれらの属性を個別に指定する必要がないのは、ヘッダーから自動的に読み取られるためである。

Step 4:TR-31 インポート

同じ KEK でキーブロックをインポートする。

Python
import_resp = cp.import_key(
    KeyMaterial={
        "Tr31KeyBlock": {
            "WrappingKeyIdentifier": kek["KeyArn"],
            "WrappedKeyBlock": key_block,
        }
    }
)
imported = import_resp["Key"]
Output
[Imported] TR31_C0_CARD_VERIFICATION_KEY TDES_2KEY KCV:0DEBBF Origin:EXTERNAL

注目ポイント:

  • KCV が一致0DEBBF)— 鍵素材が正しく転送されたことを証明
  • KeyOrigin が EXTERNAL — サービス内で生成された鍵(AWS_PAYMENT_CRYPTOGRAPHY)ではなく、インポートされた鍵であることを示す
  • KeyUsage と KeyAlgorithm が自動設定 — TR-31 ヘッダーから属性が読み取られ、インポート時に指定する必要がない

Step 5:インポートした鍵で CVV2 生成

Output
インポートした鍵で CVV2 生成: 025
元の鍵と一致: True

元の鍵とインポートした鍵で同じ CVV2(025)が生成された。鍵素材が TR-31 エクスポート→インポートを経ても保持されていることが実証された。

まとめ

  • TR-31 は鍵の属性ごと転送する — キーブロックのヘッダーに KeyUsage やアルゴリズムが含まれるため、インポート時に属性を個別に指定する必要がない
  • KCV で鍵素材の同一性を検証できる — エクスポート前後で KCV が一致すれば、鍵素材が正しく転送されたことが保証される
  • インポートした鍵の KeyOrigin は EXTERNAL — サービス内で生成した鍵と区別できる
  • 鍵のインポート/エクスポートは Python や CLI が現実的 — 運用寄りの作業であり、Java SDK よりもスクリプトのほうが取り回しがよい。TR-34 による KEK の確立が必要な場合は公式サンプルを参照

クリーンアップ

スクリプトの実行時にクリーンアップが自動で行われる。全ての鍵は 3 日後に完全削除される(DeleteKeyInDays=3)。削除予約中の鍵は restore_key で復元可能。

共有する

田原 慎也

田原 慎也

ソリューションアーキテクト @ AWS

AWS ソリューションアーキテクトとして金融業界のお客様を中心に技術支援をしており、クラウドアーキテクチャや AI/ML に関する学びをこのサイトで発信しています。このサイトの内容は個人の見解であり、所属企業の公式な意見や見解を代表するものではありません。

関連記事