Encrypting JPA entity attributes using listeners in Spring

In 2018, it’s mandatory to think about security for every application which stores personal data. When it comes to this topic, you can’t be 100% sure that the application has no vulnerabilities thus it’s wise to make the data harder to read in case of a data leak which practically means storing sensitive information in an encrypted form, usually in the database.

As Hibernate has quite a big share in the industry for reading and writing data, I’ll show how to employ it’s capabilities to make encryption easier and cleaner using only annotations on the selected entity attributes.

The encryption

For demonstration purposes, I’m not going to use real encryption but just Base64 encoding and decoding. The encryption and decryption implementation is a very separated logic from all the other parts of the application, it might involve invoking some HTTP API to do the encryption/decryption but it can be as simple as a simple method call.

I’ll use the following implementation for encryption:

And the following for decryption:

Wiring encryption into entities

Here comes the tricky part. How to handle encryption when saving or loading an entity? The expectation would be when saving a new or updating or reading an existing entity, the value is in a unencrypted format within the application but in the database, it’s stored encrypted.

The options are the following in case of JPA:

  • Using an AttributeConverter
    • I don’t like this option as it’s not about converting types and the main purpose of this interface would be to create a mapping between the database and the application representation.
  • Using an EntityListener
    • This seems to be a way too verbose solution. The @EntityListener(EncryptionListener.class)  must be put on the class however encryption must be a default for all the entities in the application.
  • Using a Hibernate Interceptor
    • This could be an option but it’s not that easy to register an Interceptor within Spring Boot, unless you are manually doing the SessionFactory  registration.
  • Manually handling the encryption and decryption before saving and reading an entity. This has the downside that the business logic will be mixed up with the encryption and it could be simply forgotten therefore having some sensitive data in an unencrypted form.

The first 2 options, using an AttributeConverter, EntityListener has a big downside. You cannot easily access the Spring context as the instances of the classes will not be managed by Spring which means you cannot get any dependency autowired into those instances. However, there is a solution for this problem by saving the ApplicationContext into a static store which can be accessed by the AttributeConverter or EntityListener but obviously this is a pretty bad solution.

The 3rd option would be a good one but as I mentioned, hard to register it in Spring Boot. Manually doing the encryption/decryption could be simply forgotten which is the main problem with it in my opinion.

There is one more option, using EventListeners. Hibernate defines a couple of different events which happens during an entity’s lifecycle like before updating an entity, before persisting it and so on. Implementing encryption will require 3 events to be caught, before persisting an entity, before updating an entity and after loading the entity from the database.

For different lifecycle events, Hibernate defines different interfaces.

All we have to do is to implement these interfaces and register those implementations through the EntityManagerFactory which is automatically created by Spring Boot.

The solution

For the sake of simplicity, I’ll use an entity with 2 fields, one for the id and one which represents personal data and needs to be encrypted. This entity will be representing a Phone number.

The id column will be a UUID, but it could be any other type and the phone number will be stored in the phoneNumber attribute. The latter attribute is special because it has a custom annotation, @Encrypted . This annotation will be used in the event listeners to determine which attributes needs to be encrypted and decrypted. The annotation looks a very simple one:

Let’s create the event listener. One class will implement all the 3 listeners which is needed for the encryption. Notice that it is annotated as @Component  and by the consequence of that, Spring will manage the bean instance and autowiring is now possible.

There are 2 dependencies, FieldEncrypter  and FieldDecrypter , both will be described a bit later. Have a look at the implementation of the methods coming from the interfaces. onPostLoad  is called when the entity instance is filled up with the values from the database but before giving back control to the application. onPreInsert  is called when persist is called but before executing the INSERT statement. onPreUpdate  is the same as onPreInsert , but it’s called before an UPDATE statement is executed.

The latter 2 methods are passing a PreInsert  and PreUpdate  event as a parameter. This has a reference to the actual entity being worked on but it’s a bit tricky as any change you made to those entity instances will be lost in the SQL statements.  Instead of modifying the entity, there is a state parameter passed along which is the store of the data and represented as an Object array. The ordering of this array will match the ordering of the array returned by org.hibernate.persister.entity.EntityPersister#getPropertyNames . The trick is to manipulate this state array instead of the entity so the change will be propagated to the database.

Now check out how the encryption is done with the FieldEncrypter .

Nothing fancy, it takes all the fields which are annotated with @Encrypted  and then gets the field value from the state parameter, then applies the encryption (which is Base64 for the moment) and writes it back to the state array.

EncryptionUtils  is implemented as following:

Now comes the FieldDecrypter :

Similarly, it takes the @Encrypted  fields and sets those to accessible for further using it through reflection. Then the value of the field is taken, decrypted and set back to the field.

This was the encryption implementation but still some configuration is needed which is registering the event listener in Hibernate. Here I’ll utilize a BeanPostProcessor which will use the EntityManagerFactory to register the listener.

UPDATE: The implementation showed above was suffering from a performance issue because as soon as the entity was decrypted after loading it into the persistence context, Hibernate will think that “oops, someone changed the state of the entity, let’s write it back to the database”, meaning that at the end of the transaction, there was an unnecessary UPDATE statement executed.

Instead of using the PostLoadListener , one can utilize the PreLoadListener  which kicks in right before the actual entity loading and let’s you operate on “state” level. Modifying this internal state will result in the proper behavior and implementation looks similar to the encryption logic.

The updated code can be found on GitHub.

Testing time

Let’s see encryption at work. I’ll use a custom class which helps with the transaction handling.

Two bigger tests will be created, one for testing that persisting works and one for updating. Implicitly the reading will be tested in both cases.

The first one tests persisting encryption by creating a JPA entity instance, and calling persist.. Then reading the database using native SQL to verify that the data is really in an encrypted form and last but not least, reading the entity back from the database through JPA and verifying that it’s decrypted.

The second test does almost the same but there is an intermediate step to update the data.

At the end, both of the test cases will be green which means encryption is working fine.

Summary

In this article, we’ve seen how to create a custom annotation to encrypt JPA entity attributes in the database. There was one more tiny trick to define the event listener as a Spring bean allowing to use dependency injection for separating the components of the encryption logic.

The full code can be found on GitHub.

If you liked the article, share it and let me know what you think. For more interesting topics, follow me on Twitter.

2 Replies to “Encrypting JPA entity attributes using listeners in Spring”

  1. Tried Hibernate’s @ColumnTransformer a few weeks ago, but having H2 for testing was an issue when using PostgreSQL pgcrypto and there was no time to try to make it work, especially as we might just need it in the future. As an annoying coincidence, both H2 and Postgre have “encrypt” and “decrypt” methods with exactly 3 parameters of String and byte array, but in different order :). Have implemented a good encryption and decryption method for now (mainly from stackoverflow), but will probably use it like you described if we need to use it more extensively.

  2. I wanted to take your example and run it against PostgreSQL. I’ve updated the application.yml to contain the following, but I get o.h.engine.jdbc.spi.SqlExceptionHelper : ERROR: relation “phone” does not exist when I run the test. I’m guessing that unit tests don’t instruct hibernate to create missing database objects?

    spring:
    jpa:
    properties:
    hibernate:
    ddl-auto: update
    show_sql: true
    format_sql: true
    dialect: org.hibernate.dialect.PostgreSQLDialect
    current_session_context_class: org.springframework.orm.hibernate5.SpringSessionContext
    temp:
    use_jdbc_metadata_defaults: false
    datasource:
    driver-class-name: org.postgresql.Driver
    url: jdbc:postgresql://localhost:5432/testdb
    username: testdbuser
    password: letmein

Leave a Reply

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