How to use a JPA Attribute Converter to encrypt your data
Take your skills to the next level!
The Persistence Hub is the place to be for every Java developer. It gives you access to all my premium video courses, monthly Java Persistence News, monthly coding problems, and regular expert sessions.
A few days ago, I read an interesting article by Bear Giles about Database encryption using JPA listeners from 2012. He discusses his requirements for an encryption solution and provides a code example with JPA listeners. His main requirements are:
- provide transparent encryption that does not affect the application,
- be able to add the encryption at deployment time,
- develop application and security/encryption by two different teams/persons.
And I completely agree with him. But after 1.5 years and a spec update to JPA 2.1, JPA listeners are not the only solution anymore. JPA 2.1 introduced Attribute Converter, which can be used to create a maybe better solution.
General information and setup
This example expects, that you have some basic knowledge about JPA Attribute Converter. If you want to read in more detail about Attribute Converters, check my previous article on JPA 2.1 – How to implement an Attribute Converter and this free cheat sheet with all new features introduced in JPA 2.1.
Creating the CryptoConverter
Payment information like a credit card number is confidential information that should be encrypted. The following code snippet shows the CreditCard entity which we will use for this example.
@Entity public class CreditCard { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private String ccNumber; private String name; ... }
As we pointed out in the beginning, the encryption should work in a transparent way. That means, that the application is not affected by the encryption and that it can be added without any changes to the existing codebase. For me, this also includes the data model in the database because it is often created by some application-specific scripts which shall not be changed. So we need an Attribute Converter that does not change the data type while encrypting and decrypting the information.
The following code snippet shows an example of such a converter. As you can see, the converter is quite simple. The convertToDatabaseColumn method is called by Hibernate before the entity is persisted to the database. It gets the unencrypted String from the entity and uses the AES algorithm with a PKCS5Padding for encryption. Then a base64 encoding is used to convert the encrypted byte[] into a String which will be persisted to the database.
When the persistence provider reads the entity from the database, the method convertToEntityAttribute gets called. It takes the encrypted String from the database, uses a base64 decoding to transform it to a byte[] and performs the decryption. The decrypted String is assigned to the attribute of the entity.
For a real application, you might want to put some more effort into the encryption or move it to a separate class. But this should be good enough to explain the general idea.
@Converter public class CryptoConverter implements AttributeConverter<String, String> { private static final String ALGORITHM = "AES/ECB/PKCS5Padding"; private static final byte[] KEY = "MySuperSecretKey".getBytes(); @Override public String convertToDatabaseColumn(String ccNumber) { // do some encryption Key key = new SecretKeySpec(KEY, "AES"); try { Cipher c = Cipher.getInstance(ALGORITHM); c.init(Cipher.ENCRYPT_MODE, key); return Base64.encodeBytes(c.doFinal(ccNumber.getBytes())); } catch (Exception e) { throw new RuntimeException(e); } } @Override public String convertToEntityAttribute(String dbData) { // do some decryption Key key = new SecretKeySpec(KEY, "AES"); try { Cipher c = Cipher.getInstance(ALGORITHM); c.init(Cipher.DECRYPT_MODE, key); return new String(c.doFinal(Base64.decode(dbData))); } catch (Exception e) { throw new RuntimeException(e); } } }
OK, we have an Attribute Converter that encrypts and decrypts a String. Now we need to tell hibernate to use this converter to persist the ccNumber attribute of the CreditCard entity. As described in one of my previous articles, we could use the @Convert annotation for this. But that would change the code of our application.
Another and for our requirements the better option is to assign the converter in the XML configuration. This can be done in the orm.xml file. The following snippet assigns the CryptoConverter to the ccNumber attribute of the CreditCard entity.
<entity-mappings version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence/orm" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence/orm_2_1.xsd"> <entity class="blog.thoughts.on.java.jpa21.enc.entity.CreditCard"> <convert converter="blog.thoughts.on.java.jpa21.enc.converter.CryptoConverter" attribute-name="ccNumber"/> </entity> </entity-mappings>
That is everything we need to do to implement and configure an Attribute Converter based encryption for a single database field.
Entity Listeners or Attribute Converter?
The answer to this question is not as easy as it seems. Both solutions have their advantages and disadvantages.
The entity listener described by Bear Giles can use multiple attributes of the entity during encryption. So you can join multiple attributes, encrypt them, and store the encrypted data in one database field. Or you can use different attributes for the encrypted and decrypted data to avoid the serialization of the decrypted data (as described by Bear Giles). But using an entity listener has also drawbacks. Its implementation is specific for an entity and more complex than the implementation of an Attribute Converter. And if you need to encrypt an additional attribute, you need to change the implementation.
As you saw in the example above, the implementation of an Attribute Converter is easy and reusable. The CryptoConverter can be used to encrypt any String attribute of any entity. And by using the XML based configuration to register the converter to the entity attribute, it requires no change in the source code of the application. You could even add it to the application at a later point in time if you migrate the existing data. A drawback of this solution is, that the encrypted entity attribute cannot be marked as transient. This might result in vulnerabilities if the entity gets written to the disk.
You see, both approaches have their pros and cons. You have to decide which advantages and disadvantages are more important to you.
Conclusion
At the beginning of this post, we defined 3 requirements:
- provide transparent encryption that does not affect the application,
- be able to add the encryption at deployment time,
- develop application and security/encryption by two different teams/persons.
The described implementation of the CryptoConverter fulfills all of them. The encryption can be added at deployment time and does not affect the application if the XML configuration is used to assign the Attribute Converter. The development of the application and the encryption is completely independent and can be done by different teams. On top of this, the CryptoConverter can be used to convert any String attribute of any entity. So it has high reusability. But this solution has also some drawbacks as we saw in the last paragraph.
You have to make the decision which approach you want to use. Please write me a comment about your choice.
Hi thanks for this nice post.
I have a question on how the searching works. For example if I search a person having a credit card number, by inputting it , will it give result. The credit card number stored in data base is in encrypted format and the one used to input is in plain text format , how both will match and get the result
Hi,
If you use a JPQL or Criteria Query, Hibernate will apply the AttributeConverter to your bind parameter value. So, it encrypts the given bind parameter value before it compares it with encrypted value in the database.
Regards,
Thorben
Nice post, Helpful.
But unfortunately , @Converter does not support when using Hibernate Envers for auditing at the same time.
If you have any thoughts on it that would be great.
Thanks Anyway.
Raj
Hi Raj,
the @Converter support for Hibernate Envers was added in version 5.0.0 (http://in.relation.to/2015/08/20/hibernate-orm-500-final-release/). The Hibernate 4.3.x versions unfortunately missed the @Converter support in a lot of places 🙁
If you can’t switch to Hibernate 5, you could also you Hibernate user types. They are similar to @Converter but a proprietary feature.
Regards,
Thorben
Thank you for the excellent article! I have successfully implemented my AttributeConverter and it is being triggered as it should be. But now I have a problem using an @Inject in it. I already use @Inject at many other places and can not figure out why it won’t work at this specific place. Hence my questions:
1. Is it not possible to use CDI in an AttributeConverter?
2. Which container manages those AttributeConverters?
Any help will be appreciated!
Hi Svetoslav,
sorry for the late response. As far as I could find out so far, it’s not possible to use CDI in AttributeConverters. That’s really annoying but not completely unexpected, as it had to be specifically added to EntityListeners (and is still not working correctly).
Regards,
Thorben
How did you implemented the seach case you mentioned above ?
Like in db credit card number is encrypted, but user input is plain text ?
Will this work on Jboss 7.1.1?
Not out of the box because the used Hibernate version does not implement JPA 2.1. You need to update Hibernate to a version >4.3.0 or you switch to Wildfly 8.
Nice!
Using the @Convert annotation is the better approach, if you want to add the converter during the development phase. The XML allows you to also add it at a later point in time…
If you like, you can find more information here:
JPA 2.1 – How to implement a Type Converter .
Regards,
Thorben
This was very helpful. Based on this article I have created a version which has some code to lookup the encryption key from a properties file on the classpath and an example of applying it to an entity using a java annotation rather than xml. Enjoy https://gist.github.com/simbo1905/0e696dec7eee5f4bacb2