How to encrypt and decrypt data with Hibernate

Imagine having a tool that can automatically detect JPA and Hibernate performance issues. Hypersistence Optimizer is that tool!

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:

Vault 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.

Online Workshops

If you enjoyed this article, I bet you are going to love my upcoming Online Workshops.

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.

Transactions and Concurrency Control eBook

6 Comments on “How to encrypt and decrypt data with Hibernate

  1. Hi Vlad! Nice blog and post.
    I have a question: If I would have a salt column, could I use it inside the annotation? Or @ColumnTransformer can only refers to its column?
    Thanks!!

    • For a more dynamic approach you could use a database session variable, like the key in this example.

      • Hi, Vlad!

        I have a question: the a database session variable is user-defined or database system variable? For example, it is invalid. Please give me some suggestions or ideas, thank you very much

        jdbc:mysql://xx/xx?characterEncoding=utf-8&useUnicode=true&serverTimezone=UTC&sessionVariables=encrypt_slat=xx

      • You can try with a session variable that’s set and unset only for the duration of the application transaction.

    • Hi Vlad.

      Modern approach to encrypt data mix a key on session/application parameter , plus another part of key (user part of key) on row inside database. Having both you can decrypt/encrypt data.

      When a user ask you to forget me on database, just clear the user part of the key on database and all data was lost.

      Do you have any idea how to implement this approach on postgres/jpa ?

      • Sounds like an interesting idea, thanks for sharing it. You should definitely write an article about it. I’m looking forward to reading it.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.