0

I am trying to use an attribute converter to convert two fields in my classes. I followed these tutorials pretty faithfully, but am still getting this weird error. https://sultanov.dev/blog/database-column-level-encryption-with-spring-data-jpa/ https://beingcrazydev.com/spring-data-jpa-database-column-encryption/ https://thorben-janssen.com/how-to-use-jpa-type-converter-to/

Here is a shortened version of the error message I keep getting:

Error attempting to apply AttributeConverter; nested exception is javax.persistence.PersistenceException: Error attempting to apply AttributeConverter
org.springframework.orm.jpa.JpaSystemException: Error attempting to apply AttributeConverter; nested exception is javax.persistence.PersistenceException: Error attempting to apply AttributeConverter
    at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:408)
    at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:235)
    at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:551)
    at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61)
    at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:242)
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:152)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:174)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:215)
    at com.sun.proxy.$Proxy88.save(Unknown Source)
    at swe.bookstore.controller.RegisterController.createAccount(RegisterController.java:58)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:197)
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:141)
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:894)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808)
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)

Caused by: javax.persistence.PersistenceException: Error attempting to apply AttributeConverter
    at org.hibernate.type.descriptor.converter.AttributeConverterSqlTypeDescriptorAdapter$2.doConversion(AttributeConverterSqlTypeDescriptorAdapter.java:148)
    at org.hibernate.type.descriptor.converter.AttributeConverterSqlTypeDescriptorAdapter$2.extract(AttributeConverterSqlTypeDescriptorAdapter.java:121)
    at org.hibernate.type.AbstractStandardBasicType.nullSafeGet(AbstractStandardBasicType.java:257)
    at org.hibernate.type.AbstractStandardBasicType.nullSafeGet(AbstractStandardBasicType.java:253)
    at org.hibernate.type.AbstractStandardBasicType.nullSafeGet(AbstractStandardBasicType.java:243)
    at org.hibernate.type.AbstractStandardBasicType.hydrate(AbstractStandardBasicType.java:329)

Caused by: java.lang.IllegalArgumentException: Last unit does not have enough valid bits
    at java.base/java.util.Base64$Decoder.decode0(Base64.java:766)
    at java.base/java.util.Base64$Decoder.decode(Base64.java:538)
    at java.base/java.util.Base64$Decoder.decode(Base64.java:561)
    at swe.bookstore.entity.AttributeEncryptor.convertToEntityAttribute(AttributeEncryptor.java:44)
    at swe.bookstore.entity.AttributeEncryptor.convertToEntityAttribute(AttributeEncryptor.java:14)
    at org.hibernate.metamodel.model.convert.internal.JpaAttributeConverterImpl.toDomainValue(JpaAttributeConverterImpl.java:45)
    at org.hibernate.type.descriptor.converter.AttributeConverterSqlTypeDescriptorAdapter$2.doConversion(AttributeConverterSqlTypeDescriptorAdapter.java:140)
    ... 134 more

Here is my code:

@Component
public class AttributeEncryptor implements AttributeConverter<String, String> {
    private static final String AES = "AES";
    private static final byte[] encryptionKey = "123-a-secret-key".getBytes(); // do not change this!!

    private final Key key;
    private final Cipher encryptCipher;
    private final Cipher decryptCipher;

    public AttributeEncryptor() throws Exception {
        key = new SecretKeySpec(encryptionKey, AES);

        encryptCipher = Cipher.getInstance(AES);
        decryptCipher = Cipher.getInstance(AES);
    }

    @Override
    public String convertToDatabaseColumn(String attribute) {
        try {
            encryptCipher.init(Cipher.ENCRYPT_MODE, key);
            return Base64.getEncoder().encodeToString(encryptCipher.doFinal(attribute.getBytes()));
        } catch (InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) {
            throw new IllegalArgumentException(e);
        }
    }

    @Override
    public String convertToEntityAttribute(String dbData) {
        try {
            decryptCipher.init(Cipher.DECRYPT_MODE, key);
            return new String(decryptCipher.doFinal(Base64.getDecoder().decode(dbData)));
        } catch (InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) {
            throw new IllegalArgumentException(e);
        }
    }

}

And how I applied it in the persistent fields:

@Column(name = "cardNumber", nullable = false)
@Convert(converter = AttributeEncryptor.class)
private String cardNumber;

@Column(name = "`password`", nullable = false, length = 20)
@Convert(converter = AttributeEncryptor.class)
private String password;

I don't understand where I am going wrong, as I followed the steps in the tutorials I read.

Himani
  • 69
  • 9
  • Try writing a test case for your converter class, its a simple class with two methods, so you can easily test passing string value and see if it gives expected value. – code_mechanic Mar 31 '21 at 01:41

1 Answers1

1

Whatever tutorial you followed, it was wrong. Cipher is not thread safe, so you should use a thread local. Also see Is Cipher thread-safe?

Christian Beikov
  • 15,141
  • 2
  • 32
  • 58