How to encrypt and decrypt data with Hibernate
Imagine having a tool that can automatically detect JPA and Hibernate performance issues. Wouldn’t that be just awesome?
Well, Hypersistence Optimizer is that tool! And it works with Spring Boot, Spring Framework, Jakarta EE, Java EE, Quarkus, or Play Framework.
So, enjoy spending your time on the things you love rather than fixing performance issues in your production system on a Saturday night!
Introduction
Today, one of my Twitter followers sent me the following StackOverflow question, and, while answering it, I realized that it definitely deserves a post of its own.
In this post, I will explain how you can encrypt and decrypt data with Hibernate.
PostgreSQL crypto module
Because the StackOverflow question mentions PostgreSQL, we first need to enable the pgcrypto
extension. For this purpose, we need to execute the following statement:
CREATE EXTENSION pgcrypto;
The pgcrypto
will allow us to use the pgp_sym_encrypt
and pgp_sym_decrypt
functions.
Domain Model
Assuming we have the following entity:
The storage
column needs to be encrypted upon being written and decrypted during a read operation.
@ColumnTransformer annotation to the rescue!
Luckily, Hibernate offers the @ColumnTransformer
annotation which was added exactly for this type of scenarios.
Therefore, the Vault
mapping looks like this:
@Entity(name = "Vault") public class Vault { @Id private Long id; @ColumnTransformer( read = """ pgp_sym_decrypt( storage, current_setting('encrypt.key') ) """, write = """ pgp_sym_encrypt( ?, current_setting('encrypt.key') ) """ ) @Column(columnDefinition = "bytea") private String storage; //Getter and setters omitted for brevity }
Because hard-coding the encryption key in the mapping does not sound like a very good idea, we will use the PostgreSQL support for user-defined settings instead.
So, the encrypt.key
is stored in postgresql.conf
configuration file:
encrypt.key = 'Wow! So much security.'
Note that storing the encryption key in the
postgresql.conf
configuration file is just to avoid hard-coding it. This is not meant to be used in a production environment, where a Security expert should advise you about the best way to store this very sensitive info.
Testing time
When persisting a Vault
entity:
Vault user = new Vault(); user.setId(1L); user.setStorage("my_secret_key"); entityManager.persist(user);
Hibernate is going to encrypt the column, so if you select it with a native SQL query:
String encryptedStorage = (String) entityManager.createNativeQuery(""" select encode(storage, 'base64') from Vault where id = :id """) .setParameter("id", 1L) .getSingleResult(); LOGGER.info("Encoded storage: \n{}", encryptedStorage);
You are going to see a value like this:
Encoded storage: ww0EBwMC3If4VmIUn2x+0j4BKrKR9j0GFpg87Qoz/v21etflhGPE6l9p7O5Sz9yOhynbvr+gwncW
However, when loading the entity with Hibernate:
Vault vault = entityManager.find( Vault.class, 1L ); assertEquals("my_secret_key", vault.getStorage());
The storage
attribute is properly decrypted back to the original value.
If you enjoyed this article, I bet you are going to love my Book and Video Courses as well.
Conclusion
As I explained in my book, High-Performance Java Persistence, if you don’t take advantage of the underlying JPA provider or relational database capabilities, you are going to lose lots of features, like easy-peasy encryption.
