-
Use AES-256-GCM (authenticated encryption) for all sensitive data:
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import os
def encrypt(plaintext: bytes, key: bytes) -> bytes:
aesgcm = AESGCM(key)
nonce = os.urandom(12)
ciphertext = aesgcm.encrypt(nonce, plaintext, associated_data=None)
return nonce + ciphertext
def decrypt(stored: bytes, key: bytes) -> bytes:
nonce, ciphertext = stored[:12], stored[12:]
aesgcm = AESGCM(key)
return aesgcm.decrypt(nonce, ciphertext, associated_data=None)
const { createCipheriv, createDecipheriv, randomBytes } = require('crypto');
function encrypt(plaintext, key) {
const iv = randomBytes(12);
const cipher = createCipheriv('aes-256-gcm', key, iv);
const encrypted = Buffer.concat([cipher.update(plaintext), cipher.final()]);
const tag = cipher.getAuthTag();
return Buffer.concat([iv, tag, encrypted]);
}
-
Store encryption keys separately from the data they protect — use a Key Management Service:
import boto3
kms = boto3.client('kms')
def get_data_key(key_id):
response = kms.generate_data_key(KeyId=key_id, KeySpec='AES_256')
plaintext_key = response['Plaintext']
encrypted_key = response['CiphertextBlob']
return plaintext_key, encrypted_key
def decrypt_data_key(encrypted_key):
response = kms.decrypt(CiphertextBlob=encrypted_key)
return response['Plaintext']
Alternatives: HashiCorp Vault, Google Cloud KMS, Azure Key Vault.
-
For field-level encryption in databases — encrypt before storing, decrypt after reading:
class EncryptedField:
def __set_name__(self, owner, name):
self.name = name
def __set__(self, obj, value):
if value is not None:
obj.__dict__[self.name] = encrypt(value.encode(), get_key())
else:
obj.__dict__[self.name] = None
def __get__(self, obj, objtype=None):
raw = obj.__dict__.get(self.name)
if raw is not None:
return decrypt(raw, get_key()).decode()
return None
class Patient(Model):
ssn = EncryptedField()
dob = EncryptedField()
-
Use envelope encryption for scale — one master key in KMS, unique data keys per record:
Master Key (KMS — never leaves KMS)
└─ Data Key (unique per user/record, generated by KMS)
└─ Encrypted Data (stored in DB alongside encrypted data key)
This limits blast radius: compromising one data key affects only one record.
-
Derive keys from passwords using a KDF — when the encryption key comes from a user secret:
from cryptography.hazmat.primitives.kdf.scrypt import Scrypt
import os
def derive_key(password: bytes, salt: bytes = None):
if salt is None:
salt = os.urandom(16)
kdf = Scrypt(salt=salt, length=32, n=2**17, r=8, p=1)
key = kdf.derive(password)
return key, salt
Use Argon2id (preferred), scrypt, or PBKDF2-HMAC-SHA256 (FIPS only). Store the salt alongside the encrypted data.
-
Rotate keys without re-encrypting all data — use key versioning: