How to encrypt/decypt data with custom anotation(hibernate) in spring project

14,835

Solution 1

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.

Solution 2

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.

Share:
14,835
samith kumarasingha
Author by

samith kumarasingha

Updated on June 04, 2022

Comments

  • samith kumarasingha
    samith kumarasingha almost 2 years

    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.

  • Naros
    Naros over 8 years
    I would not implement any business logic inside a getter/setter because you taint the state management of your entity by doing so.
  • tharindu_DG
    tharindu_DG over 8 years
    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.
  • Naros
    Naros over 8 years
    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.
  • tharindu_DG
    tharindu_DG over 8 years
    @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.
  • Naros
    Naros over 8 years
    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.
  • Alan Ho
    Alan Ho over 8 years
    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)