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(全ステップを含む実行可能なスクリプト)
#!/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)実行手順
pip install boto3
python3 key_exchange_demo.py検証:CVV 鍵のエクスポートとインポート
以下の 5 ステップで、鍵のエクスポート→インポート→同一性検証を行う。
- KEK(鍵暗号化鍵)を作成
- CVV 鍵を作成し、CVV2 を生成(エクスポート前の基準値)
- CVV 鍵を KEK で TR-31 エクスポート
- キーブロックを同じ KEK で TR-31 インポート
- インポートした鍵で CVV2 を生成し、基準値と一致することを確認
Step 1:KEK の作成
KEK は TR31_K0_KEY_ENCRYPTION_KEY で作成する。
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 を生成する。この値がエクスポート→インポート後も同じであることを確認する基準値になる。
[CVK] TDES_2KEY KCV:0DEBBF
元の鍵で CVV2 生成: 025Step 3:TR-31 エクスポート
export_key で CVV 鍵を KEK で包んでエクスポートする。
export_resp = cp.export_key(
ExportKeyIdentifier=cvk["KeyArn"],
KeyMaterial={"Tr31KeyBlock": {"WrappingKeyIdentifier": kek["KeyArn"]}},
)
key_block = export_resp["WrappedKey"]["KeyMaterial"]WrappedKeyBlock: D0112C0TC00E00006088943C9118219BA0113A95...キーブロックの先頭がヘッダーで、鍵の属性が含まれている。
D— キーブロックバージョン(AES KEK を使用)0112— キーブロック全体の長さC0— KeyUsage(TR31_C0_CARD_VERIFICATION_KEY)T— Exportability(エクスポート可能)C00E0000— アルゴリズムとモード情報
インポート時にこれらの属性を個別に指定する必要がないのは、ヘッダーから自動的に読み取られるためである。
Step 4:TR-31 インポート
同じ KEK でキーブロックをインポートする。
import_resp = cp.import_key(
KeyMaterial={
"Tr31KeyBlock": {
"WrappingKeyIdentifier": kek["KeyArn"],
"WrappedKeyBlock": key_block,
}
}
)
imported = import_resp["Key"][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 生成
インポートした鍵で 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 で復元可能。
