Is Your Database Storing Plaintext? How Spring Boot Field‑Level Encryption Can Rescue You
The article explains why storing sensitive columns in plaintext leaves systems vulnerable even when the database is encrypted, and demonstrates a low‑intrusion, maintainable field‑level encryption solution for Spring Boot using JPA AttributeConverters, including code, pros, cons, and production best practices.
Why Field‑Level Encryption Matters
Even when a database is encrypted at the storage or network level, a breach that exposes the database files still reveals any columns stored in plaintext. Attackers can directly read personal data from those columns.
Encryption Layers in Database Design
Full‑Database Encryption – e.g., Transparent Data Encryption (TDE) encrypts the entire database files.
Table‑Level Encryption – encrypts all columns of a table uniformly.
Field‑Level Encryption (FLE) – encrypts only selected sensitive columns.
Core Idea of Field‑Level Encryption
Only truly sensitive data is encrypted before being written to the database and automatically decrypted when read.
Typical Sensitive Fields
Personal Identifiable Information (PII): name, email, ID number, phone number.
Financial data: credit‑card number, bank account, payment identifier.
Medical & compliance data: patient ID, medical record index, private diagnosis information.
Implementation Options in Spring Boot
Option 1 – Manual encryption in the service layer (not recommended)
Intrusive to business logic.
Easy to forget.
High maintenance cost.
Option 2 – JPA @Converter (recommended)
Automatic encryption/decryption.
Transparent to business code.
Cleanest implementation.
Option 3 – Hibernate interceptor / listener
Suitable for complex global policies.
Steeper learning curve.
Usually overkill for most applications.
Chosen approach: JPA AttributeConverter
Step‑by‑Step Implementation
Project Structure
src/main/java/com/icoderoad/security
├── crypto
│ └── AESUtil.java
├── converter
│ └── EncryptionConverter.java
├── entity
│ └── Customer.java
├── repository
│ └── CustomerRepository.java
└── runner
└── DemoRunner.javaAES Utility (simplified example)
package com.icoderoad.security.crypto;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
public class AESUtil {
private static final String ALGORITHM = "AES";
private static final String KEY = "MySecretKey12345"; // 16 chars = 128‑bit
public static String encrypt(String data) {
try {
SecretKeySpec keySpec = new SecretKeySpec(KEY.getBytes(), ALGORITHM);
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
return Base64.getEncoder().encodeToString(cipher.doFinal(data.getBytes()));
} catch (Exception e) {
throw new RuntimeException("Encrypt failed", e);
}
}
public static String decrypt(String encrypted) {
try {
SecretKeySpec keySpec = new SecretKeySpec(KEY.getBytes(), ALGORITHM);
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, keySpec);
return new String(cipher.doFinal(Base64.getDecoder().decode(encrypted)));
} catch (Exception e) {
throw new RuntimeException("Decrypt failed", e);
}
}
}Note: Hard‑coding the key is for demonstration only; production code should retrieve keys from a secure vault.
JPA AttributeConverter
package com.icoderoad.security.converter;
import com.icoderoad.security.crypto.AESUtil;
import jakarta.persistence.AttributeConverter;
import jakarta.persistence.Converter;
@Converter
public class EncryptionConverter implements AttributeConverter<String, String> {
@Override
public String convertToDatabaseColumn(String attribute) {
return attribute == null ? null : AESUtil.encrypt(attribute);
}
@Override
public String convertToEntityAttribute(String dbData) {
return dbData == null ? null : AESUtil.decrypt(dbData);
}
}Entity with Selective Encryption
package com.icoderoad.security.entity;
import com.icoderoad.security.converter.EncryptionConverter;
import jakarta.persistence.*;
@Entity
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name; // not encrypted
@Convert(converter = EncryptionConverter.class)
private String ssn; // sensitive
@Convert(converter = EncryptionConverter.class)
private String creditCard; // sensitive
// getters & setters omitted for brevity
}Repository – No Special Handling Required
package com.icoderoad.security.repository;
import com.icoderoad.security.entity.Customer;
import org.springframework.data.jpa.repository.JpaRepository;
public interface CustomerRepository extends JpaRepository<Customer, Long> { }Demo Runner Showing Write‑as‑Ciphertext, Read‑as‑Plaintext
package com.icoderoad.security.runner;
import com.icoderoad.security.entity.Customer;
import com.icoderoad.security.repository.CustomerRepository;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
@Component
public class DemoRunner implements CommandLineRunner {
private final CustomerRepository repo;
public DemoRunner(CustomerRepository repo) {
this.repo = repo;
}
@Override
public void run(String... args) {
Customer c = new Customer();
c.setName("John Doe");
c.setSsn("123-45-6789");
c.setCreditCard("4111111111111111");
repo.save(c);
Customer dbCustomer = repo.findById(c.getId()).orElseThrow();
System.out.println(dbCustomer.getSsn()); // prints decrypted value
}
}Actual Database Rows
id | name | ssn | credit_card
-----------------------------------------------
1 | John Doe | 7FhGhsd7a8= | KJH78sdhJshd9=When the database is exfiltrated, the attacker sees only Base64‑encoded ciphertext.
Pros and Cons
Pros
Protects only sensitive columns, keeping performance impact low.
Native JPA support yields clean, database‑agnostic code.
Cons
Encrypted columns cannot be used directly in WHERE clauses.
Key management introduces a new security surface.
Base64 encoding adds modest storage overhead.
Production Best Practices
Never hard‑code keys; use managed services such as AWS KMS, HashiCorp Vault, or Alibaba Cloud KMS.
Rotate encryption keys regularly.
Avoid encrypting columns that require frequent filtered queries.
Prefer AES‑256 over AES‑128 for stronger security.
Conclusion
Storing sensitive fields in plaintext is a design flaw. Field‑level encryption does not make a system invulnerable, but it raises the attack cost dramatically, especially for compliance‑heavy domains such as finance and healthcare. Adding an AttributeConverter‑based field‑level encryption layer to an existing Spring Boot + JPA project is a low‑effort, high‑impact improvement.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
LuTiao Programming
LuTiao Programming is a friendly community offering free programming lessons. We inspire learners to explore new ideas and technologies and quickly acquire job-ready skills.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
