9

I'm developing some RESTFull web services for a project. I use the Spring framework and use gradle for build. The problem is , I want to encrypt and decrypt data table when write and read data. I already have a algorithm(class) to encrypt and decrypt data with AES etc. What I need is, how annotate this method to hibernate entity class, am I need to create bean for this class ?

Ex:-

@Column(columnDefinition= "LONGBLOB", name = "card_no")
        @ColumnTransformer(
                read="decrypt(card_no)",
                write="encrypt(?)")
        private String cardNo;

Like this I want to add my own encryption/decryption java method to here.

2 Answers 2

16

If you have access to JPA 2.1, I would advocate for the use of an @Convert annotation with an AttributeConverter implementation.

An AttributeConverter defines a contract between the state of an entity property when it is serialized to the datastore and when it is deserialized from the datastore.

public class CreditCard {
  @Convert(converter = CreditCardNumberConverter.class)
  private String creditCardNumber;
}

Your converter implementation might look like this

public class CreditCardNumberConverter implements AttributeConverter<String, String> {
  @Override
  public String convertToDatabaseColumn(String attribute) {
    /* perform encryption here */
  }
  @Override
  public String convertToEntityAttribute(String dbData) {
    /* perform decryption here */
  }
}

If you are not able to leverage JPA 2.1, an EntityListener or the use of @PrePersist, @PreUpdate, and @PostLoad may be used in order to perform similar logic for encrypting and decrypting the database value.

Just be sure that if you decide to use an EntityListener or any of the Pre/Post callback method annotations, store the decrypted result in a transient field and use that field as your business layer's usage, such as follows:

public class CreditCard {    

  // this field could have package private get/set methods  
  @Column(name = "card_number", length = 25, nullable = false)
  private String encrpytedCardNumber;

  // this is the public operated upon field
  @Transient
  private String cardNumber;

  @PostLoad
  public void decryptCardNumber() {
    // decrypts card number during DATABASE READ
    this.cardNumber = EncryptionUtils.decrypt(encryptedCardNumber);
  }

  @PrePersist
  @PreUpdate
  public void encryptCardNumber() {
    // encrypts card number during INSERT/UPDATE
    this.encryptedCardNumber = EncryptionUtils.encrypt(cardNumber);
  }
}

Doing the above keeps entity state consistent in the object as to what exists in your database, without having Hibernate believing that the entity has changed immediately upon loading the database data.

Sign up to request clarification or add additional context in comments.

2 Comments

I tried this approach but Hibnerate will not invoke @PreUpdate to encrypt the cardNumber, when em.merge() is invoked. Because cardNumber isn't under hibernate's control, Hibernate will ignore changes made to cardNumber. To make hibernate aware of changes made to cardNumber, you have to annotate cardNumber with @Column(name = "card_number", insertable = false, updateable = false)
I believe this is wrong. When the encryptCardNumber method is executed, the transient field (cardNumber) is dropped and thus null.
6

You could do this in several ways.

  • Using JPA Listeners

Following is a simple example. Please change accordingly.

public class CustomListener{
   @Inject
   private EncryptorBean encryptor;


   @PostLoad
   @PostUpdate
   public void decrypt(Object pc) {
      if (!(pc instanceof)) {
         return;
      }

      MyObj obj = (MyObj) pc;

      if (obj.getCardNo() != null) {
         obj.setCardNo(
            encryptor.decryptString(user.getEncryptedCardNo);
      }
   }


   @PrePersist
   @PreUpdate
   public void encrypt(Object pc) {
      if (!(pc instanceof MyObj)) {
         return;
      }

      MyObj obj = (MyObj ) pc;

      if (obj.getCardNo() != null) {
         user.setEncryptedCardNo(
            encryptor.encryptString(user.getCardNo());
      }
   }
}

With this approach, you might have to take some precaution to avoid encrypting a already encrypted cardNo value. An additional Transient property could be used to hold the state whether the cardNo is already encrypted or not.

  • Or Simply Implementing this feature in the getters and setters of the entity property.

     public String getCardNo(){
         return EncrypUtil.decrypt(this.cardNo);
     }
    
     public void setCardNo(String cardNo){
         this.cardNo = EncrypUtil.encrypt(cardNo);
     }
    
  • You could also use JPA vendor specific interceptors. i.e HibernateInterceptors

    public class CustomInterceptor extends EmptyInterceptor{
    
        public boolean onSave(Object entity,Serializable id,
             Object[] state,String[] propertyNames,Type[] types)
             throws CallbackException {
    
             if (entity instanceof MyObj){
                 // check if already encrypted or not.
                 //(A transient property could be useful)
                 entity.setCardNo(EncrypUtils.encrypt(entity.getCardNo()));
             }
    
  • You could also use @Convert annotation and specify a converter

        @Convert(converter = CCConverter.class)
        private String creditCardNumber;
    

    CCConverter class should be an implementation of AttributeConverter

Hope this helps.

5 Comments

I would not implement any business logic inside a getter/setter because you taint the state management of your entity by doing so.
I think, encryption/decryption details may not be part of the business logic. It's just details of how you keep the cardNo safe & confidential. Adding that into the getters & setters would improve the cohesion of that class.
The problem is any manipulation of data inside a setter can trigger setting the entity as dirty, which can have undesired side affects. This is precisely why JPA Listeners and AttributeConverter implementations exist in the first place.
@Naros. Yes. But when you want set cardNo you have to use the setter for that method anyway. Whether it contains the encryption details or not using that setter would allow the entity to be marked as dirty. If you use an interceptor or listener it would be called for every entity although you're using it only for few entities. I think it would be an unnecessary overhead.
This is all precisely why I advocated for AttributeConverter. It allows the user to continue use of the traditional getter/setter methods without a transient field, doesn't impact Hibernate's dirty checking mechanics at all, and it also neatly abstracts away the implementation detail into a very clean and reusable component that can be placed on any field of any object. Interceptors are by far the worst suggestion here and while a JPA listener class implementation will work, both get tied to a specific class or interface designation that can be avoided with converters.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.