Skip to content

Key Rotation & Vòng Đời CryptoKeyVersion

Tại sao rotation quan trọng và tại sao nó phức tạp hơn nhiều người nghĩ

Rotation key là một trong những khái niệm bị thực thi sai nhiều nhất trong cloud security. Nhiều team setup automatic rotation mà không hiểu:

  1. Rotation tạo ra version mới nhưng không tự động re-encrypt data cũ
  2. Rotation không destroy version cũ — version cũ vẫn tồn tại và vẫn tốn tiền
  3. Nếu destroy version cũ trước khi re-encrypt data, data bị mất vĩnh viễn

Hiểu đúng rotation mechanics là điều kiện để không gây ra production incidents.


Internal model — Rotation là gì

Về mặt kỹ thuật, "rotate a key" trong Cloud KMS có nghĩa:

  1. Tạo một CryptoKeyVersion mới với key material mới (được tạo ngẫu nhiên bởi KMS)
  2. Set version mới làm primary của CryptoKey
  3. Không làm gì với version cũ — version cũ vẫn ENABLED, vẫn có thể dùng để decrypt
Trước rotation:
CryptoKey: database-key
├── version/1 [ENABLED, PRIMARY] ← dùng để Encrypt() mặc định
└── (chỉ 1 version)

Sau rotation:
CryptoKey: database-key
├── version/1 [ENABLED]           ← vẫn tồn tại, có thể decrypt ciphertext cũ
└── version/2 [ENABLED, PRIMARY]  ← dùng cho Encrypt() mới

Data cũ được mã hoá bằng version/1 vẫn decrypt được — KMS tự biết dùng version nào dựa vào metadata nhúng trong ciphertext.

Data mới được mã hoá bằng version/2 (primary mới).

Không có gì "break" sau rotation. Đây là tính chất backward compatibility của envelope encryption.


Automatic rotation — chỉ cho ENCRYPT_DECRYPT

Automatic rotation chỉ khả dụng cho keys có purpose ENCRYPT_DECRYPT (symmetric). Keys asymmetric (ASYMMETRIC_SIGN, ASYMMETRIC_DECRYPT, MAC) không hỗ trợ automatic rotation.

Tại sao asymmetric keys không support automatic rotation?

Với symmetric keys, chỉ có một key material được giữ bí mật (cả encrypt và decrypt cùng dùng key đó). Rotation tạo key material mới — không ai outside KMS biết key material cũ hay mới.

Với asymmetric keys, public key được distribute ra bên ngoài. Nếu KMS tự động rotate, public key mới được tạo — nhưng tất cả systems đang dùng public key cũ để verify signatures sẽ fail. Rotation asymmetric key phải là coordinated operation: update public key ở tất cả verifiers trước khi signing key thay đổi.

Cấu hình automatic rotation

bash
# Khi tạo key mới
gcloud kms keys create my-key \
  --location=us-central1 \
  --keyring=prod-keys \
  --purpose=encryption \
  --rotation-period=7776000s \    # 90 ngày (7,776,000 giây)
  --next-rotation-time=2026-07-01T00:00:00Z

# Update rotation schedule cho key hiện có
gcloud kms keys update my-key \
  --location=us-central1 \
  --keyring=prod-keys \
  --rotation-period=7776000s \
  --next-rotation-time=2026-07-01T00:00:00Z

Ràng buộc rotation period:

  • Tối thiểu: 86,400 giây (1 ngày)
  • Tối đa: 31,536,000 giây (~1 năm) — thực tế là 876,000 giờ (~100 năm)

Điều gì xảy ra khi rotation trigger

Khi đến next_rotation_time:

  1. Cloud KMS tự động gọi internal CreateCryptoKeyVersion() → tạo version mới
  2. Gọi UpdateCryptoKeyPrimaryVersion() → set version mới làm primary
  3. Tính toán next_rotation_time tiếp theo = current_time + rotation_period
  4. Log entry được tạo trong Cloud Audit Logs (Admin Activity log)

Rotation không require downtime. Cả primary version mới và version cũ đều available ngay sau rotation.


Manual rotation

Rotation thủ công thường cần thiết khi:

  • Suspect key bị compromise (emergency rotation)
  • Cần rotate trước schedule
  • Rotate asymmetric key sau khi đã update tất cả public key consumers
bash
# Tạo version mới thủ công
gcloud kms keys versions create \
  --location=us-central1 \
  --keyring=prod-keys \
  --key=my-key

# Lấy version number vừa tạo
gcloud kms keys versions list \
  --location=us-central1 \
  --keyring=prod-keys \
  --key=my-key

# Set version mới làm primary (chỉ cho ENCRYPT_DECRYPT)
gcloud kms keys update my-key \
  --location=us-central1 \
  --keyring=prod-keys \
  --primary-version=3   # version number vừa tạo

Lưu ý: Manual rotation không ảnh hưởng đến next_rotation_time — automatic rotation schedule vẫn chạy theo cấu hình. Nếu bạn manual rotate ngay trước next_rotation_time, sẽ có thêm một rotation nữa sớm sau đó.


Version lifecycle states

ENABLED

Version đang hoạt động bình thường:

  • Có thể dùng để Encrypt (nếu là primary)
  • Có thể dùng để Decrypt
  • Tốn tiền ($0.06/active version/month cho SOFTWARE, ~$1/month cho HSM)

DISABLED

Version bị vô hiệu hóa:

  • Không thể dùng để Encrypt hay Decrypt
  • Key material vẫn còn (có thể re-enable)
  • Vẫn tốn tiền (disabled versions vẫn bị tính phí)
  • Ciphertext được tạo bởi version này không thể decrypt khi version đang DISABLED

DISABLED thường dùng để "tạm dừng" một version trong khi điều tra incident, mà không phải commit vào việc destroy vĩnh viễn.

SCHEDULED_FOR_DESTRUCTION (Destroy Scheduled)

Version đang trong grace period trước khi bị destroy vĩnh viễn.

DestroyCryptoKeyVersion() → SCHEDULED_FOR_DESTRUCTION

                   [destroyScheduledDuration elapsed]


                                     DESTROYED

Thay đổi quan trọng từ February 1, 2024:

Trước tháng 2/2024: destroyScheduledDuration mặc định là 24 giờ
Từ tháng 2/2024: destroyScheduledDuration mặc định là 30 ngày

Đây là thay đổi có ý nghĩa lớn cho production:

  • Old behavior: Gọi destroy → 24h sau key biến mất (nếu quên re-encrypt data trong 24h → data lost)
  • New behavior: Gọi destroy → 30 ngày grace period → nhiều thời gian hơn để phát hiện và cancel

Trong SCHEDULED_FOR_DESTRUCTION state:

  • Version không thể dùng để Encrypt hay Decrypt
  • Có thể cancel bằng cách gọi RestoreCryptoKeyVersion() → version quay về DISABLED

DESTROYED

Version đã bị xóa vĩnh viễn:

  • Key material không thể phục hồi
  • Ciphertext được mã hoá bằng version này mất vĩnh viễn (không thể decrypt)
  • Resource entry vẫn tồn tại (để duy trì audit trail) nhưng không chứa key material
  • Không tốn phí (DESTROYED versions không bị tính)

Version numbers không được tái sử dụng: Sau khi version/3 bị destroy, version tiếp theo được tạo sẽ là version/4, không phải version/3 mới. Đây đảm bảo audit trail không bị confuse.


Re-encryption — bước bị bỏ qua quan trọng nhất

Rotation tạo version mới, nhưng data cũ vẫn được mã hoá bằng version cũ. Để thực sự tăng cường bảo mật sau rotation, cần re-encrypt data cũ bằng key version mới — đây gọi là re-encryption.

Cloud KMS documentation chỉ rõ:

"Data encrypted with previous key versions isn't automatically re-encrypted with the new key version. [...] Re-encrypting data removes your reliance on old key versions, allowing you to destroy them."

Re-encryption là gì về mặt kỹ thuật

TRƯỚC RE-ENCRYPTION:
[ciphertext_A] ← được mã hoá bởi DEK_A ← được wrap bởi KEK_version_1
[ciphertext_B] ← được mã hoá bởi DEK_B ← được wrap bởi KEK_version_1

SAU ROTATION (chưa re-encrypt):
[ciphertext_A] ← vẫn dùng KEK_version_1 (vẫn decrypt được)
[ciphertext_B] ← vẫn dùng KEK_version_1 (vẫn decrypt được)
KEK_version_2  ← primary, dùng cho data MỚI

SAU RE-ENCRYPTION:
[ciphertext_A] ← unwrap DEK_A với v1, re-wrap DEK_A với v2
[ciphertext_B] ← unwrap DEK_B với v1, re-wrap DEK_B với v2
KEK_version_1  ← có thể destroy an toàn (không còn DEK nào dùng nó)
KEK_version_2  ← primary

Re-encryption chỉ re-wrap DEK, không re-encrypt actual data. Với large datasets, đây vẫn là non-trivial operation.

Re-encryption workflow thực tế

GCP services hỗ trợ CMEK thường có built-in re-encryption mechanism:

Cloud Storage: Gọi storage.objects.rewrite() với rewriteToken cho large objects. Cloud Storage tự gọi KMS để unwrap DEK cũ và re-wrap với primary version mới.

BigQuery: Không có explicit re-encrypt API — re-encrypt xảy ra khi table được CREATE OR REPLACE hoặc thông qua EXPORT + reimport workflow.

Tự implement cho application-level envelope encryption:

python
from google.cloud import kms_v1

kms_client = kms_v1.KeyManagementServiceClient()

def re_encrypt_dek(encrypted_dek: bytes, key_name: str) -> bytes:
    # Decrypt DEK với version cũ (KMS tự detect version từ ciphertext)
    plaintext_dek = kms_client.decrypt(
        request={"name": key_name, "ciphertext": encrypted_dek}
    ).plaintext

    # Re-encrypt DEK với primary version (mới nhất)
    new_encrypted_dek = kms_client.encrypt(
        request={"name": key_name, "plaintext": plaintext_dek}
    ).ciphertext

    # Xóa plaintext_dek khỏi memory (zeroize)
    del plaintext_dek
    return new_encrypted_dek

Destroy schedule và recovery window

Thay đổi destroyScheduledDuration

Default là 30 ngày (từ Feb 2024), nhưng có thể cấu hình khi tạo key:

bash
# Tạo key với custom destroy schedule
gcloud kms keys create my-key \
  --location=us-central1 \
  --keyring=prod-keys \
  --purpose=encryption \
  --destroy-scheduled-duration=86400s   # 1 ngày (nếu muốn nhanh hơn default)

Ràng buộc:

  • Tối thiểu: 24 giờ (86,400 giây)
  • Tối đa: 120 ngày (10,368,000 giây)

destroyScheduledDuration là property của CryptoKey, áp dụng cho tất cả versions của key đó.

Restore trong SCHEDULED_FOR_DESTRUCTION

Trong grace period, có thể cancel destruction:

bash
# Cancel destruction → version quay về DISABLED state
gcloud kms keys versions restore 3 \
  --location=us-central1 \
  --keyring=prod-keys \
  --key=my-key

Sau restore, version ở trạng thái DISABLED (không phải ENABLED tự động). Phải explicitly enable lại nếu cần:

bash
gcloud kms keys versions enable 3 \
  --location=us-central1 \
  --keyring=prod-keys \
  --key=my-key

Failure modes

Destroy version trước khi re-encrypt

Kịch bản thảm họa phổ biến nhất:

  1. Rotate key → version/2 là primary mới
  2. Destroy version/1 (hoặc đợi 30 ngày)
  3. Sau đó phát hiện có data cũ vẫn còn DEK được wrap bằng version/1
  4. Data không thể decrypt → mất vĩnh viễn

Ngăn chặn: Audit xem còn DEK nào dùng version cũ trước khi destroy. Một số services cung cấp re-encryption status API.

Key version bị disable accidentaly

Nếu version hiện tại là primary và bị disable:

  • Mọi Encrypt() call mới fail (FAILED_PRECONDITION)
  • Decrypt() với ciphertext từ version đó cũng fail
  • Service bị impact ngay lập tức

Recovery: re-enable version (hoặc set primary sang version ENABLED khác).

Cross-project key access bị revoke

Nếu service ở project A dùng CMEK key từ project B, và IAM binding bị xóa:

  • Service không thể encrypt/decrypt data
  • Tùy service, có thể trigger alerting hoặc service degradation

Monitoring: Set alert trên Cloud KMS permission errors và Audit Log entries với PERMISSION_DENIED.


Best practices cho rotation trong production

1. Rotate nhưng đừng destroy ngay: Sau rotation, để version cũ ở ENABLED trong ít nhất một chu kỳ backup của bạn (ví dụ: 90 ngày). Đảm bảo tất cả backup data đã được re-encrypt hoặc có thể được decrypted trước khi destroy.

2. Monitor key version count: Nếu có 50+ ENABLED versions trên một key, đó là dấu hiệu re-encryption chưa được thực hiện và versions cũ chưa được clean up.

3. Test decryption trước khi destroy: Luôn verify data có thể được decrypt với new version trước khi destroy old version.

4. Tách DEK storage và re-encrypt metadata: Lưu {object_id, encrypted_dek, kek_version_used} riêng để có thể audit và bulk re-encrypt.


References