How to encrypt and decrypt data with Hibernate

(Last Updated On: January 22, 2019)
Imagine having a tool that can automatically detect if you are using JPA and Hibernate properly. 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.'

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: {}", 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.

Download free ebook sample

Newsletter logo
10 000 readers have found this blog worth following!

If you subscribe to my newsletter, you'll get:
  • A free sample of my Video Course about running Integration tests at warp-speed using Docker and tmpfs
  • 3 chapters from my book, High-Performance Java Persistence,
  • a 10% discount coupon for my book.

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

  1. Hi Vlad,

    Thanks for such an excellent post.

    I went through the steps in the post and tried encrypting/decrypting as explained, yet I keep getting this error – “column is of type bytea but expression is of type character varying”

    Do I have to change the datatype to be byte[] in the JPA instead of having it as String?

    Thank you,
    Jeremy

  2. Hi Vlad

    Thank you again for a great post!

    I want to use the PG functions “pgp_pub_encrypt(?, dearmor(‘” + this.key + “‘))”
    Is there a way to set the key to use via Spring config? (I am wanting to store the key public and private outside of the code)

    Thank You
    Melissa

  3. I’m a bit confused on how that is safer than protecting the data with the same old “user/password” pair. As far as I could understand from this post, postgresql.conf resides on db side and it is applied per datasource user. That means (a) if that particular datasource user credentials leaks, anyone who can connect to the database engine by using that credentials will also be able to call the decrypt function passing the current_setting(‘encrypt.key’).
    Wouldn’t be better to store the encrypt/decrypt key on the application side? We could argue that (b) in case the application deployment is compromised that would expose the key too, but that is also true even application is compromised and key is stored on db, as per (a) that datasource credentials as compromised anyways.

    • The article goal is about encrypting and decrypting data with Hibernate. You can store the key anywhere it’s safer for your application. The DB setting example is just for test sake to emphasise that it’s not a good idea to hardcode the key in the mappings.

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.