Field-Level Encryption with Spring JPA and JPA EntityListener Data

I am trying to encrypt several fields of domain objects before inserting / updating and decrypt them when choosing to display in the user interface.

I use Spring JPA data stores with Hibernate and EntityListener, which decrypt during the @PostLoad life cycle and encrypt during @PrePersist and @PreUpdate. The problem is that after the record is loaded from the database into the PersistenceContext, the listener decrypts the data, which makes EntityManager think that the object has been changed, which in turn causes updating again and, therefore, @PreUpdate encryption. Any tips on how to handle this?

  • Spring 4.0.4.RELEASE
  • Spring Data JPA 1.5.2.RELEASE
  • Hibernate 4.2.14.Final

Is there an easy way to return individual objects from a JPA repository?

Entity class

@Entity
@Table(name="cases")
@EntityListeners(EncryptionListener.class)
public class MyCase implements Serializable, EncryptionEntity {

private static final Logger logger = LoggerFactory.getLogger(MyCase.class);

private static final long serialVersionUID = 1L;

private String caseNumber;
private byte[] secretProperty;
private byte[] iv;

@Id
@Column(name="case_number")
public String getCaseNumber() {
    return caseNumber;
}
public void setCaseNumber(String caseNumber) {
    this.caseNumber = caseNumber;
}

@Column(name="secret_property")
public byte[] getSecretProperty() {
    return secretProperty;
}
public void setSecretProperty(byte[] secretProperty) {
    this.secretProperty = secretProperty;
}

@Column
public byte[] getIv() {
    return iv;
}
public void setIv(byte[] iv) {
    this.iv = iv;
}

@Override
@Transient
public byte[] getInitializationVector() {
    return this.iv;
}

@Override
public void setInitializationVector(byte[] iv) {
    this.setIv(iv);
}

}

EncryptionEntity Interface

public interface EncryptionEntity {

    public byte[] getInitializationVector();
    public void setInitializationVector(byte[] iv);
}

Spring JPA Data Warehouse

public interface MyCaseRepository extends JpaRepository<MyCase, String> {

}

Interface MyCaseService

public interface MyCaseService {

    public MyCase findOne(String caseNumber);

    public MyCase save(MyCase case);

}

MyCaseService implementation

public class MyCaseServiceImpl implements MyCaseService {

    private static final Logger logger = LoggerFactory.getLogger(MyCaseServiceImpl.class);

    @Autowired
    private MyCaseRepository repos;


    @Override
    public MyCase findOne(String caseNumber) {
        return repos.findOne(caseNumber);
    }

    @Transactional(readOnly=false)
    public MyCase save(MyCase case) {

        return repos.save(case);
    }

}

JPA Encryption Class

@Component
public class EncryptionListener {

    private static final Logger logger = LoggerFactory.getLogger(EncryptionListener.class);

    private static EncryptionUtils encryptionUtils;
    private static SecureRandom secureRandom;

    private static Map<Class<? extends EncryptionEntity>, 
        List<EncryptionEntityProperty>> propertiesToEncrypt;

    @Autowired
    public void setCrypto(EncryptionUtils encryptionUtils){
        EncryptionListener.encryptionUtils = encryptionUtils;
    }

    @Autowired
    public void setSecureRandom(SecureRandom secureRandom){
        EncryptionListener.secureRandom = secureRandom;
    }

    public EncryptionListener(){

        if (propertiesToEncrypt == null){

            propertiesToEncrypt = new HashMap<Class<? extends EncryptionEntity>, List<EncryptionEntityProperty>>();

            //MY CASE
            List<EncryptionEntityProperty> propertyList = new ArrayList<EncryptionEntityProperty>();
            propertyList.add(new EncryptionEntityProperty(MyCase.class, "secretProperty", byte[].class));
            propertiesToEncrypt.put(MyCase.class, propertyList);

        }

    }

    @PrePersist
    public void prePersistEncryption(EncryptionEntity entity){
        logger.debug("PRE-PERSIST");
        encryptFields(entity);
    }

    @PreUpdate
    public void preUpdateEncryption(EncryptionEntity entity){
        logger.debug("PRE-UPDATE");
        encryptFields(entity); 
    }

    public void encryptFields(EncryptionEntity entity){
        byte[] iv = new byte[16];
        secureRandom.nextBytes(iv);
        encryptionUtils.setIv(iv);
        entity.setInitializationVector(iv);

        logger.debug("Encrypting " + entity);

        Class<? extends EncryptionEntity> entityClass = entity.getClass();

        List<EncryptionEntityProperty> properties = propertiesToEncrypt.get(entityClass);

        for (EncryptionEntityProperty property : properties){

            logger.debug("Encrypting '{}' field of {}", property.getName(), entityClass.getSimpleName());
            if (property.isEncryptedWithIv() == false){
                logger.debug("Encrypting '{}' without IV.", property.getName());
            }

            try {
                byte[] bytesToEncrypt = (byte[]) property.getGetter().invoke(entity, (Object[]) null);

                if (bytesToEncrypt == null || bytesToEncrypt.length == 0){
                    continue;
                }

                byte[] encrypted = encryptionUtils.encrypt(bytesToEncrypt, property.isEncryptedWithIv());

                property.getSetter().invoke(entity, new Object[]{encrypted});


            } catch (Exception e){
                logger.error("Error while encrypting '{}' property of {}: " + e.getMessage(), property.getName(), entityClass.toString());
                e.printStackTrace();
            }

        }

    }

    @PostLoad
    public void decryptFields(EncryptionEntity entity){

        logger.debug("POST-LOAD");

        logger.debug("Decrypting " + entity);

        Class<? extends EncryptionEntity> entityClass = entity.getClass();
        byte[] iv = entity.getInitializationVector();

        List<EncryptionEntityProperty> properties = propertiesToEncrypt.get(entityClass);

        for (EncryptionEntityProperty property : properties){

            try {
                byte[] value = (byte[]) property.getGetter().invoke(entity, (Object[]) null);

                if (value == null || value.length == 0){
                    logger.debug("Ignoring blank field {} of {}", property.getName(), entityClass.getSimpleName());
                    continue;
                } 

                logger.debug("Decrypting '{}' field of {}", property.getName(), entityClass.getSimpleName());
                if (property.isEncryptedWithIv() == false){
                    logger.debug("Decrypting '{}' without IV.", property.getName());
                }

                byte[] decrypted = encryptionUtils.decrypt(value, iv, property.isEncryptedWithIv());

                property.getSetter().invoke(entity, new Object[]{decrypted});

            } catch (Exception e){
                logger.error("Error while decrypting '{}' property of {}", property.getName(), entityClass.toString());
                e.printStackTrace();
            }
        }

    }



}
+4
source share

All Articles