@shinyaz

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)
key_exchange_demo.py
#!/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
Terminal
pip install boto3
 
python3 key_exchange_demo.py

Verification: CVV Key Export and Import

The following 5 steps perform export → import → identity verification:

  1. Create a KEK (Key Encryption Key)
  2. Create a CVV key and generate CVV2 (baseline value before export)
  3. Export the CVV key via TR-31 wrapped with KEK
  4. Import the key block with the same KEK via TR-31
  5. Generate CVV2 with the imported key and confirm it matches the baseline

Step 1: Create KEK

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: Create CVV Key and Generate CVV2

Output
[CVK] TDES_2KEY KCV:0DEBBF
Original CVV2: 025

Step 3: TR-31 Export

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

The header contains key attributes:

  • D — Key block version (AES KEK)
  • 0112 — Total key block length
  • C0 — 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

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

Key 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

Output
Imported CVV2: 025
Match: True

The 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.

Share this post

Shinya Tahara

Shinya Tahara

Solutions Architect @ AWS

I'm a Solutions Architect at AWS, providing technical guidance primarily to financial industry customers. I share learnings about cloud architecture and AI/ML on this site.The views and opinions expressed on this site are my own and do not represent the official positions of my employer.

Related Posts