32.7 Multi-Region Keys: Encrypting and Decrypting Across Regions
Right, so you’ve got your data encrypted with a KMS key in us-east-1. Fantastic. Now your user in eu-west-1 needs to decrypt it. Your first thought might be, “I’ll just send them the ciphertext!” Go ahead, try it. I’ll wait.
…
See? AccessDeniedException. Told you. A KMS key is a regional resource, locked tighter than my opinion of that decision. The key material itself never, ever leaves the region it was created in. This is a brilliant security boundary, but it makes cross-region work a bit of a head-scratcher. The solution isn’t to FedEx the key; it’s to use the wonderfully named Multi-Region Keys.
Think of a Multi-Region key (MRK) not as a single key that spans the globe, but as a tightly knit family of identical keys living in different regions. You create a primary key in one region (say, us-east-1), and AWS automatically replicates it to your chosen replica regions (like eu-west-1 and ap-northeast-1). The magic is that they share the same key ID and key material. This is the crucial part. Because they are cryptographically identical, a piece of data encrypted by the primary in us-east-1 can be decrypted by the replica in eu-west-1.
How to Create Your Key Family
You can’t just wave a wand. You define this happy family at creation time. Note the MULTI_REGION key spec and, critically, the list of replica regions. This is a one-time, no-take-backsies kind of setup.
# Create the primary Multi-Region key in us-east-1
aws kms create-key \
--region us-east-1 \
--description "My super-secret global data key" \
--key-spec AES_256 \
--key-usage ENCRYPT_DECRYPT \
--multi-region \
--policy file://key-policy.json
# Save the returned KeyId from this command. It's your primary key ARN.
Then, you create the replicas by pointing back to that primary.
# Create a replica in eu-west-1
aws kms create-key \
--region eu-west-1 \
--replica-key \
--primary-region us-east-1 \
--key-id alias/my-global-key-alias # Use the Primary Key ID from above
The Nuts and Bolts of Encryption and Decryption
Here’s where the magic feels almost… sensible. You encrypt with any key in the family. To decrypt, you can either use the replica in your local region, or you can let the KMS service do a neat trick called automatic geographic routing.
Let’s encrypt something in Virginia.
import boto3
import base64
# Encrypt in us-east-1
kms_east = boto3.client('kms', region_name='us-east-1')
plaintext = "The launch codes are 12345... kidding. Or am I?"
response = kms_east.encrypt(
KeyId='alias/my-global-key-alias',
Plaintext=plaintext.encode()
)
ciphertext_blob = response['CiphertextBlob']
# This is the important part for cross-region decryption
# You MUST preserve the entire CiphertextBlob. It contains metadata.
encrypted_data = base64.b64encode(ciphertext_blob).decode('utf-8')
print(encrypted_data) # You'd send this string to your EU application
Now, to decrypt it in Ireland, you don’t need to specify the original key’s ARN. You just hand the full, unaltered CiphertextBlob to the decrypt function in eu-west-1. The blob contains metadata that tells the KMS service, “Hey, I was encrypted by this MRK family.” KMS then automatically routes the decryption request to the local replica of that family.
# Decrypt in eu-west-1
kms_eu = boto3.client('kms', region_name='eu-west-1')
# Assume we received the encrypted_data string from above
ciphertext_blob = base64.b64decode(encrypted_data.encode('utf-8'))
# Note: We do NOT specify a KeyId. The CiphertextBlob tells KMS everything.
response = kms_eu.decrypt(CiphertextBlob=ciphertext_blob)
decrypted_data = response['Plaintext'].decode('utf-8')
print(decrypted_data) # Should output our original plaintext
The Gotchas and Glorious Details
- Aliases are NOT Global: This is the biggest trip-up. An alias is just a friendly name in a single region. The alias
my-global-key-aliasinus-east-1is a completely different resource frommy-global-key-aliasineu-west-1. You must create the alias in each replica region separately. I don’t love it, but that’s the way it is. - You Must Use the Blob: The decryption only works if you pass the entire
CiphertextBlobyou got from theencryptcall. If you try to manually specify the key ID in thedecryptcall, you’re likely to get an error unless you point it to the exact regional replica that encrypted it, which defeats the whole purpose. - It’s Not All Keys: MRKs are only for symmetric encryption (AES-256). If you’re using asymmetric keys (RSA, ECC) for signing or asymmetric encryption, you’re stuck in one region. Pick your priorities.
- The Primary Isn’t Special: Well, not for crypto operations. You can promote a replica to primary for management tasks (like deleting the key family), but for
encrypt/decrypt, all keys in the family are created equal. Encrypt in Dublin, decrypt in Tokyo, no problem.
The best practice is blindingly obvious: use MRKs for any data that might need to be decrypted in another region. The cost is negligible (you pay for the API calls in each region), the setup is a one-time affair, and it saves you from building horrifyingly complex custom solutions that involve decrypting and re-encrypting data as it moves between regions. Just let the key family handle it.