Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 716
samith kumarasingha Avatar asked Dec 14 '15 03:12

samith kumarasingha


2 Answers

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.

like image 193
Naros Avatar answered Oct 19 '22 11:10

Naros


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.

like image 26
tharindu_DG Avatar answered Oct 19 '22 09:10

tharindu_DG