Database Encryption Strategies
Database Encryption Strategies
Database encryption requires balancing security needs with performance and functionality requirements. Field-level encryption protects specific sensitive columns while allowing normal operations on other data. This granular approach enables searches and joins on non-sensitive fields while protecting personally identifiable information. However, encrypted fields cannot be indexed traditionally, requiring alternative approaches for searchable encryption.
Order-preserving encryption (OPE) maintains sort order in ciphertext, enabling range queries on encrypted data. While OPE leaks some information about data distribution, it may provide acceptable security for certain use cases when combined with other protections. Homomorphic encryption promises computation on encrypted data without decryption, though current implementations remain too slow for most production use cases. Searchable encryption schemes offer middle ground, enabling specific queries while maintaining security.
// Example: Implementing field-level encryption with searchable indexes
import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.MessageDigest;
import java.util.Base64;
public class FieldLevelEncryption {
private static final String ALGORITHM = "AES/GCM/NoPadding";
private static final int GCM_TAG_LENGTH = 16;
private static final int GCM_IV_LENGTH = 12;
private SecretKeySpec dataKey;
private SecretKeySpec indexKey;
public FieldLevelEncryption(byte[] masterKey) {
// Derive separate keys for data and blind indexes
this.dataKey = deriveKey(masterKey, "data-encryption");
this.indexKey = deriveKey(masterKey, "index-generation");
}
public EncryptedField encryptField(String plaintext, String fieldName) throws Exception {
// Generate random IV
byte[] iv = new byte[GCM_IV_LENGTH];
SecureRandom.getInstanceStrong().nextBytes(iv);
// Encrypt data
Cipher cipher = Cipher.getInstance(ALGORITHM);
GCMParameterSpec parameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, iv);
cipher.init(Cipher.ENCRYPT_MODE, dataKey, parameterSpec);
// Add field name as additional authenticated data
cipher.updateAAD(fieldName.getBytes());
byte[] ciphertext = cipher.doFinal(plaintext.getBytes());
// Generate blind index for searching
String blindIndex = generateBlindIndex(plaintext, fieldName);
return new EncryptedField(
Base64.getEncoder().encodeToString(ciphertext),
Base64.getEncoder().encodeToString(iv),
blindIndex
);
}
private String generateBlindIndex(String value, String fieldName) throws Exception {
// Create searchable blind index using HMAC
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(indexKey);
// Include field name to prevent cross-field matching
mac.update(fieldName.getBytes());
mac.update(value.toLowerCase().trim().getBytes());
byte[] blindIndex = mac.doFinal();
// Truncate to reduce storage while maintaining security
byte[] truncated = Arrays.copyOf(blindIndex, 16);
return Base64.getUrlEncoder().withoutPadding().encodeToString(truncated);
}
public String searchableHash(String searchTerm, String fieldName) throws Exception {
// Generate same blind index for search term
return generateBlindIndex(searchTerm, fieldName);
}
// Database query would look like:
// SELECT * FROM users WHERE email_blind_index = ?
// Parameter: searchableHash(searchEmail, "email")
}
class EncryptedField {
public final String ciphertext;
public final String iv;
public final String blindIndex;
public EncryptedField(String ciphertext, String iv, String blindIndex) {
this.ciphertext = ciphertext;
this.iv = iv;
this.blindIndex = blindIndex;
}
}