AWS Payment Cryptography Extra — Implement TR-31 Key Export and Import
Table of Contents
Introduction
The main series used Java SDK to focus on key creation and usage. In production, however, you'll need to migrate keys from existing HSMs or exchange keys with partners.
Key import/export is an operational task — initial migration, periodic rotation — where CLI or scripts are the practical choice. TR-34 KEK establishment involves certificate chain construction, making scripts the realistic option.
This article uses Python (boto3) to implement TR-31 key export and import. We wrap a CVV key with a KEK, export it, import it back, and verify the imported key produces the same CVV2.
Prerequisites
- Familiarity with the introductory article
- Python 3.9+, boto3
- IAM permissions:
payment-cryptography:*(for testing) - Test region: us-east-1
How TR-31 Key Exchange Works
In payment cryptographic processing, keys must be securely moved between partners and HSMs. Transferring key material in plaintext is not acceptable, so keys are wrapped with a "key encryption key" (KEK) for transport.
TR-31 (ANSI X9 TR-31) is the industry standard format for this key exchange. Key material is encrypted with a KEK, and key attributes (usage, algorithm, operation modes) are included in the key block header. The receiver decrypts the key block with the same KEK and automatically restores the attributes from the header.
Source Code and Run
key_exchange_demo.py (runnable script)
#!/usr/bin/env python3
"""TR-31 key export/import verification script"""
import boto3
REGION = "us-east-1"
cp = boto3.client("payment-cryptography", region_name=REGION)
dp = boto3.client("payment-cryptography-data", region_name=REGION)
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']}")
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']}")
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]}...")
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']}")
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)Run instructions
pip install boto3
python3 key_exchange_demo.pyVerification: CVV Key Export and Import
The following 5 steps perform export → import → identity verification:
- Create a KEK (Key Encryption Key)
- Create a CVV key and generate CVV2 (baseline value before export)
- Export the CVV key via TR-31 wrapped with KEK
- Import the key block with the same KEK via TR-31
- Generate CVV2 with the imported key and confirm it matches the baseline
Step 1: Create 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"]Step 2: Create CVV Key and Generate CVV2
[CVK] TDES_2KEY KCV:0DEBBF
Original CVV2: 025Step 3: TR-31 Export
export_resp = cp.export_key(
ExportKeyIdentifier=cvk["KeyArn"],
KeyMaterial={"Tr31KeyBlock": {"WrappingKeyIdentifier": kek["KeyArn"]}},
)
key_block = export_resp["WrappedKey"]["KeyMaterial"]WrappedKeyBlock: D0112C0TC00E00006088943C9118219BA0113A95...The header contains key attributes:
D— Key block version (AES KEK)0112— Total key block lengthC0— KeyUsage (TR31_C0_CARD_VERIFICATION_KEY)T— Exportability (exportable)C00E0000— Algorithm and mode information
Attributes don't need to be specified during import because they're automatically read from the header.
Step 4: TR-31 Import
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:EXTERNALKey observations:
- KCV matches (
0DEBBF) — Proves key material was correctly transferred - KeyOrigin is EXTERNAL — Distinguishes imported keys from service-generated ones
- Attributes auto-populated from TR-31 header — No need to specify KeyUsage or KeyAlgorithm during import
Step 5: Generate CVV2 with Imported Key
Imported CVV2: 025
Match: TrueThe original and imported keys produce the same CVV2 (025), proving key material integrity through TR-31 export/import.
Summary
- TR-31 transfers key attributes along with key material — The key block header contains KeyUsage and algorithm, so import doesn't require specifying attributes separately
- KCV verifies key material identity — Matching KCVs before and after export guarantee correct transfer
- Imported keys have KeyOrigin EXTERNAL — Distinguishable from service-generated keys
- Python/CLI is practical for key import/export — This is an operational task where scripts are more practical than Java SDK. For TR-34 KEK establishment, see the official samples
Cleanup
Cleanup runs automatically when the script executes. All keys are scheduled for deletion after 3 days (DeleteKeyInDays=3). Keys in DELETE_PENDING state can be restored with restore_key.
