Are You Implementing Field-Level Encryption Correctly? Best Practices Explained

This article examines common pitfalls and misconceptions in field‑level encryption, explains why AES‑GCM and proper AAD are essential, outlines threat modeling, key hierarchy, blind indexing for searchable data, migration strategies, key rotation, performance considerations, and secure integration with MyBatis, Kubernetes, and Vault.

Ray's Galactic Tech
Ray's Galactic Tech
Ray's Galactic Tech
Are You Implementing Field-Level Encryption Correctly? Best Practices Explained

Introduction

Encrypting data at the storage layer (disk, TDE, backup) protects physical media but does not stop a privileged database user or a compromised application from reading plaintext. True field‑level encryption must address the threat model, algorithm misuse, key segregation, ciphertext format, searchable indexes, permission isolation, migration, and fault tolerance.

Threat Modeling and Data Classification

The article classifies data into four levels (L0‑L4) and maps each level to appropriate protection measures. For example, L2 (sensitive data such as phone numbers) requires field encryption with reversible decryption, while L4 (secrets, passwords) should use one‑way hashing or a dedicated secrets manager.

Algorithm Selection

Production systems should prefer AEAD modes, especially AES‑256‑GCM, because they provide confidentiality and integrity. ECB is rejected due to pattern leakage, CBC is allowed only with a correct Encrypt‑then‑MAC construction, and AES‑128 is acceptable but most enterprises standardize on AES‑256 for compliance.

GCM Security Considerations

GCM uses a 12‑byte nonce that must be unique per key. Reusing a nonce leaks the underlying plaintext pattern. The implementation should generate a fresh SecureRandom instance once and reuse it for all encryptions, creating a new nonce for each operation. A 128‑bit authentication tag is recommended; truncating the tag reduces security.

Key Hierarchy (KEK, DEK, BIK)

Three distinct keys are defined:

KEK (Key Encryption Key) : stored in Vault, Cloud KMS, or an HSM; used only to wrap DEKs.

DEK (Data Encryption Key) : the AES‑GCM key that encrypts individual fields; cached in process memory after unwrap.

BIK (Blind Index Key) : used to compute HMAC‑SHA‑256 blind indexes for searchable encrypted columns; never mixed with DEK.

Storing any of these keys in plain configuration files or environment variables is explicitly prohibited.

Envelope Format

A versioned ciphertext envelope is defined as:

enc:v1:aes256gcm:k42:<base64url(nonce|ciphertext|tag)>

The envelope contains a magic prefix, format version, algorithm identifier, key version, and the Base64URL‑encoded nonce plus ciphertext with tag. All metadata fields are also authenticated via AAD.

Blind Index for Searchable Encryption

Randomized ciphertext cannot be used for equality queries. The solution is a deterministic blind index computed as:

blind_index = HMAC‑SHA‑256(BIK, domain || tenant_id || normalized_value)

Normalization (e.g., E.164 for phone numbers) must be versioned. The index enables fast exact‑match lookups while keeping the underlying plaintext secret.

Blind Index Service (Java Example)

package com.example.crypto;

import javax.crypto.Mac;
import javax.crypto.SecretKey;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.util.Objects;

public final class BlindIndexService {
    private static final String HMAC_ALGORITHM = "HmacSHA256";
    private static final byte SEPARATOR = 0x00;
    private final SecretKey blindIndexKey;

    public BlindIndexService(SecretKey blindIndexKey) {
        this.blindIndexKey = Objects.requireNonNull(blindIndexKey, "blindIndexKey");
    }

    public byte[] phoneIndex(long tenantId, String normalizedPhone) {
        Objects.requireNonNull(normalizedPhone, "normalizedPhone");
        byte[] domain = "phone-bidx-v1".getBytes(StandardCharsets.UTF_8);
        byte[] tenant = Long.toUnsignedString(tenantId).getBytes(StandardCharsets.UTF_8);
        byte[] value = normalizedPhone.getBytes(StandardCharsets.UTF_8);
        ByteBuffer input = ByteBuffer.allocate(domain.length + 1 + tenant.length + 1 + value.length);
        input.put(domain).put(SEPARATOR).put(tenant).put(SEPARATOR).put(value);
        try {
            Mac mac = Mac.getInstance(HMAC_ALGORITHM);
            mac.init(blindIndexKey);
            return mac.doFinal(input.array());
        } catch (GeneralSecurityException ex) {
            throw new CryptoException("blind index calculation failed", ex);
        } finally {
            java.util.Arrays.fill(value, (byte)0);
            java.util.Arrays.fill(input.array(), (byte)0);
        }
    }
}

MyBatis Integration Options

Three patterns are described:

Explicit repository encryption : the DAO reads the ciphertext column, decrypts only after authorization, and writes encrypted values explicitly.

Field‑specific TypeHandler : a custom TypeHandler<String> is registered only for the target column, avoiding global string encryption.

Annotation‑driven interceptor : a complex solution that automatically encrypts/decrypts annotated fields but requires careful handling of idempotency and context.

The article recommends the first two approaches and warns against a global String TypeHandler.

Key Rotation Process

A seven‑step state machine is presented:

Create a new DEK (or rotate KEK).

Distribute read capability for both old and new keys.

Switch the writer to the new key.

Observe the system for errors.

Re‑encrypt historical data.

Verify no old versions remain.

Retire the old key.

DEK and KEK rotations are distinguished; DEK rotation requires decrypt‑then‑encrypt, while KEK rotation only re‑wraps the DEK.

Historical Data Migration

A six‑stage online migration avoids a single massive UPDATE:

Add new ciphertext columns.

Write both plaintext and ciphertext (dual‑write) for a short window.

Read prefers ciphertext; fallback to plaintext only when ciphertext is null.

Back‑fill missing ciphertext in batches using primary‑key pagination.

Switch reads to ciphertext only and stop writing plaintext.

Zero‑out the old plaintext column after verification.

All related data pipelines (binlog, CDC, search indexes, backups) must be updated accordingly.

Kubernetes and Vault Integration

Kubernetes Secret objects are Base64‑encoded and may be stored encrypted if the cluster enables static encryption or a KMS provider. The recommended identity chain is:

Workload Identity / ServiceAccount → Vault/KMS token.

Token unwraps the DEK for the pod.

DEK is cached in process memory.

Long‑lived root tokens must never be baked into Deployment manifests.

Performance and Capacity

Typical bottlenecks are not the AES operation itself but remote KMS calls, Base64 encoding, repeated normalization, and ORM overhead. Cipher objects are not thread‑safe; creating them per request is cheap after benchmarking, but a key cache (key domain + tenant + version) is essential. Suggested metrics include encryption/decryption latency (P50/P95/P99), KMS unwrap latency, cache hit ratio, and added row size.

Error Handling and Fault Tolerance

Four custom exception types are defined: CryptoFormatException: unknown or corrupted format. CryptoIntegrityException: authentication tag failure. CryptoKeyUnavailableException: KMS or cache miss. CryptoException: generic provider errors.

Encryption failures must abort the write; decryption failures must not silently return empty strings. Read/write strategies during KMS outage are described, and circuit‑breaker configurations should only retry idempotent errors.

Logging, Auditing, and Access Control

Logging full plaintext or even raw ciphertext is prohibited. Audit records should capture caller identity, service, tenant, data classification, a non‑reversible record identifier, purpose (full view vs masked), and outcome. Decryption permissions are separated from database read permissions.

Testing Strategy

Unit tests verify correct encryption/decryption, nonce uniqueness, AAD binding, version handling, and failure modes. Integration tests cover MyBatis inserts, blind‑index queries, multi‑tenant isolation, and key‑rotation behavior. Security tests attempt nonce reuse, version tampering, and blind‑index manipulation.

Common Mistakes

Treating Base64 as encryption.

Using a fixed IV/nonce.

Sharing DEK and BIK.

Using plain SHA‑256 for blind indexes.

Adding random salt to blind indexes and expecting equality queries.

Storing key version in a single byte.

Globally registering a String TypeHandler.

Deleting old keys before all data is re‑encrypted.

Falling back to plaintext on decryption failure.

Logging plaintext objects after encryption.

Calling KMS for every row.

Reusing the same key across dev, test, and prod.

Assuming “can decrypt” equals “has permission to decrypt”.

Neglecting downstream replicas (binlog, CDC, search indexes) during migration.

Roadmap and Checklist

The evolution is split into phases from immediate “stop‑the‑bleed” actions to full platform‑wide tokenization. A detailed launch checklist covers algorithm choice, key management, query design, application integration, migration steps, container security, and observability.

Conclusion

Field‑level encryption is a system‑level discipline, not a single library call. It requires a clear threat model, AEAD encryption with proper AAD, a strict key hierarchy, versioned ciphertext envelopes, searchable blind indexes, controlled decryption, robust rotation, comprehensive migration, performance‑aware design, and end‑to‑end auditability. When these elements are in place, field‑level encryption becomes a reliable component of an organization’s data‑security architecture.

References

NIST SP 800‑38D – Recommendation for Block Cipher Modes of Operation: GCM and GMAC.

OWASP Cryptographic Storage Cheat Sheet.

OWASP Secrets Management Cheat Sheet.

Google Cloud Envelope Encryption.

Google Tink AEAD documentation.

HashiCorp Vault Transit Secrets Engine.

Kubernetes Secrets Good Practices.

MyBatis Configuration – typeHandlers.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

KubernetesMyBatiskey managementVaultAES-GCMfield-level encryptionblind indexing
Ray's Galactic Tech
Written by

Ray's Galactic Tech

Practice together, never alone. We cover programming languages, development tools, learning methods, and pitfall notes. We simplify complex topics, guiding you from beginner to advanced. Weekly practical content—let's grow together!

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.