Encryption โ At Rest and In Transit
Overviewโ
HIPAA Technical Safeguard 45 CFR ยง 164.312(e)(2)(ii) requires encryption of PHI in transit. Encryption at rest is an addressable implementation specification โ in practice, required for all cloud deployments.
1. Encryption Summaryโ
| Layer | Mechanism | Standard | Who Manages |
|---|---|---|---|
| Data at rest โ Cloud SQL | AES-256 (default) + CMEK | AES-256-GCM | Google + You |
| Data at rest โ Redis | AES-256 (default) | AES-256 | |
| Data at rest โ Cloud Storage | AES-256 (default) + CMEK | AES-256-GCM | Google + You |
| Data in transit โ Cloud Run HTTPS | TLS 1.2+ | TLS 1.3 preferred | Google + You |
| Data in transit โ Cloud SQL | TLS (via Auth Proxy) | TLS 1.3 | |
| Data in transit โ Redis | TLS 1.2+ | TLS 1.2/1.3 | |
| Application PHI fields | Fernet / AES-256 | AES-256-CBC | You |
| Secrets | Secret Manager | AES-256 at rest | Google + You |
2. Cloud KMS โ Customer-Managed Encryption Keysโ
2.1 Create Key Ring and Keysโ
gcloud kms keyrings create phi-keyring \
--location=us-central1 --project=YOUR_PHI_PROJECT_ID
# Database encryption key (90-day rotation)
gcloud kms keys create phi-db-key \
--keyring=phi-keyring --location=us-central1 \
--purpose=encryption --rotation-period=7776000s \
--project=YOUR_PHI_PROJECT_ID
# Storage (backups) key
gcloud kms keys create phi-storage-key \
--keyring=phi-keyring --location=us-central1 \
--purpose=encryption --rotation-period=7776000s \
--project=YOUR_PHI_PROJECT_ID
# Field-level encryption key
gcloud kms keys create phi-field-key \
--keyring=phi-keyring --location=us-central1 \
--purpose=encryption --rotation-period=7776000s \
--project=YOUR_PHI_PROJECT_ID
2.2 Separation of Duties for Key Accessโ
# KMS Admins can manage keys
gcloud kms keys add-iam-policy-binding phi-db-key \
--keyring=phi-keyring --location=us-central1 \
--member="group:[email protected]" \
--role="roles/cloudkms.admin" --project=YOUR_PHI_PROJECT_ID
# Cloud SQL SA can use key, cannot manage it
gcloud kms keys add-iam-policy-binding phi-db-key \
--keyring=phi-keyring --location=us-central1 \
--member="serviceAccount:[email protected]" \
--role="roles/cloudkms.cryptoKeyEncrypterDecrypter" \
--project=YOUR_PHI_PROJECT_ID
2.3 Key Rotation Policyโ
| Key | Rotation Period |
|---|---|
| DB encryption key | 90 days |
| Storage key | 90 days |
| Field-level key | 90 days |
3. TLS Configurationโ
Enforce TLS 1.2+ on Load Balancerโ
gcloud compute ssl-policies create phi-ssl-policy \
--profile=RESTRICTED \
--min-tls-version=TLS_1_2 \
--project=YOUR_PHI_PROJECT_ID
gcloud compute target-https-proxies update YOUR_HTTPS_PROXY \
--ssl-policy=phi-ssl-policy --project=YOUR_PHI_PROJECT_ID
The RESTRICTED profile disables TLS 1.0/1.1, RC4, DES, 3DES, EXPORT, and NULL cipher suites.
HSTS Headerโ
@app.after_request
def set_security_headers(response):
response.headers['Strict-Transport-Security'] = \
'max-age=31536000; includeSubDomains; preload'
response.headers['X-Content-Type-Options'] = 'nosniff'
response.headers['X-Frame-Options'] = 'DENY'
response.headers['Cache-Control'] = 'no-store, no-cache, must-revalidate'
return response
4. Application-Level Field Encryptionโ
Using Cloud KMS Directlyโ
from google.cloud import kms
import base64
def encrypt_phi_field(plaintext: str, project_id: str, location: str,
keyring: str, key: str) -> str:
client = kms.KeyManagementServiceClient()
key_name = client.crypto_key_path(project_id, location, keyring, key)
response = client.encrypt(request={"name": key_name, "plaintext": plaintext.encode()})
return base64.b64encode(response.ciphertext).decode()
def decrypt_phi_field(ciphertext_b64: str, project_id: str, location: str,
keyring: str, key: str) -> str:
client = kms.KeyManagementServiceClient()
key_name = client.crypto_key_path(project_id, location, keyring, key)
response = client.decrypt(request={"name": key_name,
"ciphertext": base64.b64decode(ciphertext_b64)})
return response.plaintext.decode()
Envelope Encryption (Recommended for Performance)โ
from cryptography.fernet import Fernet
from google.cloud import kms
import base64
def encrypt_record(record_data: dict, kms_key_name: str) -> dict:
dek = Fernet.generate_key()
encrypted_data = Fernet(dek).encrypt(str(record_data).encode())
kms_client = kms.KeyManagementServiceClient()
response = kms_client.encrypt(request={"name": kms_key_name, "plaintext": dek})
return {
"encrypted_data": base64.b64encode(encrypted_data).decode(),
"encrypted_dek": base64.b64encode(response.ciphertext).decode(),
"kms_key": kms_key_name,
}
5. Secret Managerโ
# Store a secret
echo -n "my-secret-value" | gcloud secrets create my-secret \
--data-file=- --replication-policy=user-managed \
--locations=us-central1 --project=YOUR_PHI_PROJECT_ID
# Rotate: add a new version
echo -n "new-value" | gcloud secrets versions add my-secret \
--data-file=- --project=YOUR_PHI_PROJECT_ID
# Disable old version after rotation
gcloud secrets versions disable VERSION_NUMBER \
--secret=my-secret --project=YOUR_PHI_PROJECT_ID
Secret naming conventions:
db-password โ Cloud SQL app user password
redis-auth-string โ Redis AUTH password
redis-ca-cert โ Redis TLS CA certificate
field-encryption-key โ Application field-level key
api-key-{service} โ External API keys
6. Encryption Compliance Checklistโ
| Requirement | Implementation | Verified |
|---|---|---|
| PHI at rest encrypted | GCP default AES-256 + CMEK | [ ] |
| CMEK for Cloud SQL | phi-db-key in Cloud KMS | [ ] |
| CMEK for Cloud Storage | phi-storage-key in Cloud KMS | [ ] |
| TLS 1.2+ for Cloud Run | SSL policy RESTRICTED on LB | [ ] |
| TLS for Cloud SQL | Cloud SQL Auth Proxy | [ ] |
| TLS for Redis | SERVER_AUTHENTICATION mode | [ ] |
| HSTS header | Application middleware | [ ] |
| No secrets in code | Secret Manager for all secrets | [ ] |
| Key rotation scheduled | 90-day rotation on all KMS keys | [ ] |
| Field-level encryption | Applied to SSN, diagnosis codes | [ ] |
Next: Network Security โ