Skip to main content

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โ€‹

LayerMechanismStandardWho Manages
Data at rest โ€” Cloud SQLAES-256 (default) + CMEKAES-256-GCMGoogle + You
Data at rest โ€” RedisAES-256 (default)AES-256Google
Data at rest โ€” Cloud StorageAES-256 (default) + CMEKAES-256-GCMGoogle + You
Data in transit โ€” Cloud Run HTTPSTLS 1.2+TLS 1.3 preferredGoogle + You
Data in transit โ€” Cloud SQLTLS (via Auth Proxy)TLS 1.3Google
Data in transit โ€” RedisTLS 1.2+TLS 1.2/1.3Google
Application PHI fieldsFernet / AES-256AES-256-CBCYou
SecretsSecret ManagerAES-256 at restGoogle + 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โ€‹

KeyRotation Period
DB encryption key90 days
Storage key90 days
Field-level key90 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()
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โ€‹

RequirementImplementationVerified
PHI at rest encryptedGCP default AES-256 + CMEK[ ]
CMEK for Cloud SQLphi-db-key in Cloud KMS[ ]
CMEK for Cloud Storagephi-storage-key in Cloud KMS[ ]
TLS 1.2+ for Cloud RunSSL policy RESTRICTED on LB[ ]
TLS for Cloud SQLCloud SQL Auth Proxy[ ]
TLS for RedisSERVER_AUTHENTICATION mode[ ]
HSTS headerApplication middleware[ ]
No secrets in codeSecret Manager for all secrets[ ]
Key rotation scheduled90-day rotation on all KMS keys[ ]
Field-level encryptionApplied to SSN, diagnosis codes[ ]

Next: Network Security โ†’