Invariant Properties

  • rss
  • Home

Spring-injected Beans in JPA EntityListeners

Bear Giles | September 29, 2013

In Database Encryption Using JPA Listeners I discussed transparent encryption using a JPA EntityListener. This approach was transparent in the sense that the JPA Entity was (nearly) entirely unaware that encryption was occurring and the JPA EntityListener itself was unaware of the details.

There was one big problem. EJB3 can inject resources into an EntityListener. Spring cannot.

(N.B., I am referring to JPA EntityListeners, not AOP methods. An EntityListener has to work under much tighter constraints.)

Skip forward a year and I have a solution. There is one big caveat: this only works if you know that your JPA implementation is Hibernate 4.0.0.Final or better. It’s never fun to introduce implementation details into your design but fortunately we can hide nearly all of them.

Hibernate Callbacks

Hibernate has had callbacks for years. I don’t know how widely they were used – they’re the type of thing that will cause vendor lock-in if you’re not careful. That vendor lock-in is why I, and many other people, try to remain pure JPA.

Sadly sometimes JPA isn’t quite enough.

Enter a blog entry from nearly two years ago: Spring managed event listeners with JPA.

Cutting to the code in post Hibernate 4.0.0.Final world it is easy to add Hibernate listeners. These aren’t JPA EntityListeners but let’s take it one step at a time.

  1. /**
  2.  * Configure Spring-aware entity listeners. This implementation is
  3.  * hibernate-specific.
  4.  *
  5.  * See: http://deepintojee.wordpress.com/2012/02
  6.  * /05/spring-managed-event-listeners-with-jpa/
  7.  *
  8.  * Another approach follows, but it doesn't support Spring injection.
  9.  * http://stackoverflow.com/questions/8616146/eventlisteners-using-hibernate
  10.  * -4-0-with-spring-3-1-0-release
  11.  *
  12.  * @author louis.gueye@gmail.com (see above)
  13.  * @author Bear Giles <bgiles@coyotesong.com>
  14.  */
  15. @Component
  16. public class HibernateListenersConfigurer {
  17.     private static final Logger log = LoggerFactory.getLogger(HibernateListenersConfigurer.class);
  18.  
  19.     @Resource
  20.     private EntityManagerFactory emf;
  21.  
  22.     @Resource
  23.     private HibernateListenersAdapter listener;
  24.  
  25.     @PostConstruct
  26.     public void registerListeners() {
  27.         HibernateEntityManagerFactory hemf = (HibernateEntityManagerFactory) emf;
  28.         SessionFactory sf = hemf.getSessionFactory();
  29.         EventListenerRegistry registry = ((SessionFactoryImpl) sf).getServiceRegistry().getService(
  30.                 EventListenerRegistry.class);
  31.  
  32.         registry.getEventListenerGroup(EventType.PRE_INSERT).appendListener(listener);
  33.         registry.getEventListenerGroup(EventType.POST_COMMIT_INSERT).appendListener(listener);
  34.         registry.getEventListenerGroup(EventType.PRE_UPDATE).appendListener(listener);
  35.         registry.getEventListenerGroup(EventType.POST_COMMIT_UPDATE).appendListener(listener);
  36.         registry.getEventListenerGroup(EventType.PRE_DELETE).appendListener(listener);
  37.         registry.getEventListenerGroup(EventType.POST_COMMIT_DELETE).appendListener(listener);
  38.         registry.getEventListenerGroup(EventType.POST_LOAD).appendListener(listener);
  39.     }
  40. }
/**
 * Configure Spring-aware entity listeners. This implementation is
 * hibernate-specific.
 * 
 * See: http://deepintojee.wordpress.com/2012/02
 * /05/spring-managed-event-listeners-with-jpa/
 * 
 * Another approach follows, but it doesn't support Spring injection.
 * http://stackoverflow.com/questions/8616146/eventlisteners-using-hibernate
 * -4-0-with-spring-3-1-0-release
 * 
 * @author louis.gueye@gmail.com (see above)
 * @author Bear Giles <bgiles@coyotesong.com>
 */
@Component
public class HibernateListenersConfigurer {
    private static final Logger log = LoggerFactory.getLogger(HibernateListenersConfigurer.class);

    @Resource
    private EntityManagerFactory emf;

    @Resource
    private HibernateListenersAdapter listener;

    @PostConstruct
    public void registerListeners() {
        HibernateEntityManagerFactory hemf = (HibernateEntityManagerFactory) emf;
        SessionFactory sf = hemf.getSessionFactory();
        EventListenerRegistry registry = ((SessionFactoryImpl) sf).getServiceRegistry().getService(
                EventListenerRegistry.class);

        registry.getEventListenerGroup(EventType.PRE_INSERT).appendListener(listener);
        registry.getEventListenerGroup(EventType.POST_COMMIT_INSERT).appendListener(listener);
        registry.getEventListenerGroup(EventType.PRE_UPDATE).appendListener(listener);
        registry.getEventListenerGroup(EventType.POST_COMMIT_UPDATE).appendListener(listener);
        registry.getEventListenerGroup(EventType.PRE_DELETE).appendListener(listener);
        registry.getEventListenerGroup(EventType.POST_COMMIT_DELETE).appendListener(listener);
        registry.getEventListenerGroup(EventType.POST_LOAD).appendListener(listener);
    }
}

Hibernate/JPA Adapter

The prior class is a good start but the spring-injected listener needs to use Hibernate entity listener annotations, not standard JPA javax.persistence annotations. Can we do better?

The quick answer is yes – it’s straightforward with reflection. See below.

The not-as-quick answer is yes – but getting the reflection right can be tricky. You need to consider superclasses and interfaces, e.g., an EntityListener may be written for an Auditable interface instead of a specific class. Ordering can become important, especially when a class and its superclass(es) are annotated. I won’t pretend to address these issues in the code below.

The “oh God what were we thinking?” answer is yes – but a robust solution will also have a way to specify that superclasses and/or interfaces should not be checked, to specify or exclude default listeners, etc.

Few of us will encounter these situations in our own code. Library writers have to worry about them but we can start with the most basic assumptions and only add to them as the need arises.

  1. import java.lang.reflect.InvocationTargetException;
  2. import java.lang.reflect.Method;
  3. import java.util.LinkedHashMap;
  4. import java.util.List;
  5. import java.util.Map;
  6.  
  7. import javax.annotation.PostConstruct;
  8. import javax.annotation.Resource;
  9. import javax.persistence.Entity;
  10. import javax.persistence.EntityManagerFactory;
  11. import javax.persistence.PostLoad;
  12. import javax.persistence.PostPersist;
  13. import javax.persistence.PostRemove;
  14. import javax.persistence.PostUpdate;
  15. import javax.persistence.PrePersist;
  16. import javax.persistence.PreRemove;
  17. import javax.persistence.PreUpdate;
  18.  
  19. import org.apache.log4j.Logger;
  20. import org.hibernate.SessionFactory;
  21. import org.hibernate.ejb.HibernateEntityManagerFactory;
  22. import org.hibernate.event.service.spi.EventListenerRegistry;
  23. import org.hibernate.event.spi.PostDeleteEvent;
  24. import org.hibernate.event.spi.PostDeleteEventListener;
  25. import org.hibernate.event.spi.PostInsertEvent;
  26. import org.hibernate.event.spi.PostInsertEventListener;
  27. import org.hibernate.event.spi.PostLoadEvent;
  28. import org.hibernate.event.spi.PostLoadEventListener;
  29. import org.hibernate.event.spi.PostUpdateEvent;
  30. import org.hibernate.event.spi.PostUpdateEventListener;
  31. import org.hibernate.event.spi.PreDeleteEvent;
  32. import org.hibernate.event.spi.PreDeleteEventListener;
  33. import org.hibernate.event.spi.PreInsertEvent;
  34. import org.hibernate.event.spi.PreInsertEventListener;
  35. import org.hibernate.event.spi.PreUpdateEvent;
  36. import org.hibernate.event.spi.PreUpdateEventListener;
  37. import org.hibernate.internal.SessionFactoryImpl;
  38.  
  39. /**
  40.  * Adapter that allows a Hibernate event listener to call a standard JPA
  41.  * EntityListener.
  42.  *
  43.  * For simplicity only a single bean of each class is supported. It is not
  44.  * difficult to support multiple beans, just messy.
  45.  *
  46.  * Each listener can have multiple methods with the same annotation.
  47.  *
  48.  * @author Bear Giles <bgiles@coyotesong.com>
  49.  */
  50. public class HibernateListenersAdapter implements PostInsertEventListener, PreInsertEventListener,
  51.         PreUpdateEventListener, PostUpdateEventListener, PreDeleteEventListener, PostDeleteEventListener,
  52.         PostLoadEventListener {
  53.     private static final long serialVersionUID = 1L;
  54.     private static final Logger log = Logger.getLogger(HibernateListenersAdapter.class);
  55.  
  56.     @Resource
  57.     private List<Object> listeners;
  58.  
  59.     @Resource
  60.     private EntityManagerFactory emf;
  61.  
  62.     private Map<Class, Map<Method, Object>> preInsert = new LinkedHashMap<Class, Map<Method, Object>>();
  63.     private Map<Class, Map<Method, Object>> postInsert = new LinkedHashMap<Class, Map<Method, Object>>();
  64.     private Map<Class, Map<Method, Object>> preUpdate = new LinkedHashMap<Class, Map<Method, Object>>();
  65.     private Map<Class, Map<Method, Object>> postUpdate = new LinkedHashMap<Class, Map<Method, Object>>();
  66.     private Map<Class, Map<Method, Object>> preRemove = new LinkedHashMap<Class, Map<Method, Object>>();
  67.     private Map<Class, Map<Method, Object>> postRemove = new LinkedHashMap<Class, Map<Method, Object>>();
  68.     private Map<Class, Map<Method, Object>> postLoad = new LinkedHashMap<Class, Map<Method, Object>>();
  69.  
  70.     private EventListenerRegistry registry;
  71.  
  72.     @PostConstruct
  73.     public void findMethods() {
  74.         for (Object listener : listeners) {
  75.             findMethodsForListener(listener);
  76.         }
  77.  
  78.         HibernateEntityManagerFactory hemf = (HibernateEntityManagerFactory) emf;
  79.         SessionFactory sf = hemf.getSessionFactory();
  80.         registry = ((SessionFactoryImpl) sf).getServiceRegistry().getService(EventListenerRegistry.class);
  81.     }
  82.  
  83.     public void findMethodsForListener(Object listener) {
  84.         Class<?> c = listener.getClass();
  85.         for (Method m : c.getMethods()) {
  86.             if (Void.TYPE.equals(m.getReturnType())) {
  87.                 Class<?>[] types = m.getParameterTypes();
  88.                 if (types.length == 1) {
  89.                     // check for all annotations now...
  90.                     if (m.getAnnotation(PrePersist.class) != null) {
  91.                         if (!preInsert.containsKey(types[0])) {
  92.                             preInsert.put(types[0], new LinkedHashMap<Method, Object>());
  93.                         }
  94.                         preInsert.get(types[0]).put(m, listener);
  95.                     }
  96.  
  97.                     if (m.getAnnotation(PostPersist.class) != null) {
  98.                         if (!postInsert.containsKey(types[0])) {
  99.                             postInsert.put(types[0], new LinkedHashMap<Method, Object>());
  100.                         }
  101.                         postInsert.get(types[0]).put(m, listener);
  102.                     }
  103.  
  104.                     if (m.getAnnotation(PreUpdate.class) != null) {
  105.                         if (!preUpdate.containsKey(types[0])) {
  106.                             preUpdate.put(types[0], new LinkedHashMap<Method, Object>());
  107.                         }
  108.                         preUpdate.get(types[0]).put(m, listener);
  109.                     }
  110.  
  111.                     if (m.getAnnotation(PostUpdate.class) != null) {
  112.                         if (!postUpdate.containsKey(types[0])) {
  113.                             postUpdate.put(types[0], new LinkedHashMap<Method, Object>());
  114.                         }
  115.                         postUpdate.get(types[0]).put(m, listener);
  116.                     }
  117.  
  118.                     if (m.getAnnotation(PreRemove.class) != null) {
  119.                         if (!preRemove.containsKey(types[0])) {
  120.                             preRemove.put(types[0], new LinkedHashMap<Method, Object>());
  121.                         }
  122.                         preRemove.get(types[0]).put(m, listener);
  123.                     }
  124.  
  125.                     if (m.getAnnotation(PostRemove.class) != null) {
  126.                         if (!postRemove.containsKey(types[0])) {
  127.                             postRemove.put(types[0], new LinkedHashMap<Method, Object>());
  128.                         }
  129.                         postRemove.get(types[0]).put(m, listener);
  130.                     }
  131.  
  132.                     if (m.getAnnotation(PostLoad.class) != null) {
  133.                         if (!postLoad.containsKey(types[0])) {
  134.                             postLoad.put(types[0], new LinkedHashMap<Method, Object>());
  135.                         }
  136.                         postLoad.get(types[0]).put(m, listener);
  137.                     }
  138.                 }
  139.             }
  140.         }
  141.     }
  142.  
  143.     /**
  144.      * Execute the listeners. We need to check the entity's class, parent
  145.      * classes, and interfaces.
  146.      *
  147.      * @param map
  148.      * @param entity
  149.      */
  150.     private void execute(Map<Class, Map<Method, Object>> map, Object entity) {
  151.         if (entity.getClass().isAnnotationPresent(Entity.class)) {
  152.  
  153.             // check for hits on this class or its superclasses.
  154.             for (Class c = entity.getClass(); c != null && c != Object.class; c = c.getSuperclass()) {
  155.                 if (map.containsKey(c)) {
  156.                     for (Map.Entry<Method, Object> entry : map.get(c).entrySet()) {
  157.                         try {
  158.                             entry.getKey().invoke(entry.getValue(), entity);
  159.                         } catch (InvocationTargetException e) {
  160.                             // log it
  161.                         } catch (IllegalAccessException e) {
  162.                             // log it
  163.                         }
  164.                     }
  165.                 }
  166.             }
  167.  
  168.             // check for hits on interfaces.
  169.             for (Class c : entity.getClass().getInterfaces()) {
  170.                 if (map.containsKey(c)) {
  171.                     for (Map.Entry<Method, Object> entry : map.get(c).entrySet()) {
  172.                         try {
  173.                             entry.getKey().invoke(entry.getValue(), entity);
  174.                         } catch (InvocationTargetException e) {
  175.                             // log it
  176.                         } catch (IllegalAccessException e) {
  177.                             // log it
  178.                         }
  179.                     }
  180.                 }
  181.             }
  182.         }
  183.     }
  184.  
  185.     /**
  186.      * @see org.hibernate.event.spi.PostDeleteEventListener#onPostDelete(org.hibernate
  187.      *      .event.spi.PostDeleteEvent)
  188.      */
  189.     @Override
  190.     public void onPostDelete(PostDeleteEvent event) {
  191.         execute(postRemove, event.getEntity());
  192.     }
  193.  
  194.     /**
  195.      * @see org.hibernate.event.spi.PreDeleteEventListener#onPreDelete(org.hibernate
  196.      *      .event.spi.PreDeleteEvent)
  197.      */
  198.     @Override
  199.     public boolean onPreDelete(PreDeleteEvent event) {
  200.         execute(preRemove, event.getEntity());
  201.         return false;
  202.     }
  203.  
  204.     /**
  205.      * @see org.hibernate.event.spi.PreInsertEventListener#onPreInsert(org.hibernate
  206.      *      .event.spi.PreInsertEvent)
  207.      */
  208.     @Override
  209.     public boolean onPreInsert(PreInsertEvent event) {
  210.         execute(preInsert, event.getEntity());
  211.         return false;
  212.     }
  213.  
  214.     /**
  215.      * @see org.hibernate.event.spi.PostInsertEventListener#onPostInsert(org.hibernate
  216.      *      .event.spi.PostInsertEvent)
  217.      */
  218.     @Override
  219.     public void onPostInsert(PostInsertEvent event) {
  220.         execute(postInsert, event.getEntity());
  221.     }
  222.  
  223.     /**
  224.      * @see org.hibernate.event.spi.PreUpdateEventListener#onPreUpdate(org.hibernate
  225.      *      .event.spi.PreUpdateEvent)
  226.      */
  227.     @Override
  228.     public boolean onPreUpdate(PreUpdateEvent event) {
  229.         execute(preUpdate, event.getEntity());
  230.         return false;
  231.     }
  232.  
  233.     /**
  234.      * @see org.hibernate.event.spi.PostUpdateEventListener#onPostUpdate(org.hibernate
  235.      *      .event.spi.PostUpdateEvent)
  236.      */
  237.     @Override
  238.     public void onPostUpdate(PostUpdateEvent event) {
  239.         execute(postUpdate, event.getEntity());
  240.     }
  241.  
  242.     /**
  243.      * @see org.hibernate.event.spi.PostLoadEventListener#onPostLoad(org.hibernate
  244.      *      .event.spi.PostLoadEvent)
  245.      */
  246.     @Override
  247.     public void onPostLoad(PostLoadEvent event) {
  248.         execute(postLoad, event.getEntity());
  249.     }
  250. }
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.persistence.Entity;
import javax.persistence.EntityManagerFactory;
import javax.persistence.PostLoad;
import javax.persistence.PostPersist;
import javax.persistence.PostRemove;
import javax.persistence.PostUpdate;
import javax.persistence.PrePersist;
import javax.persistence.PreRemove;
import javax.persistence.PreUpdate;

import org.apache.log4j.Logger;
import org.hibernate.SessionFactory;
import org.hibernate.ejb.HibernateEntityManagerFactory;
import org.hibernate.event.service.spi.EventListenerRegistry;
import org.hibernate.event.spi.PostDeleteEvent;
import org.hibernate.event.spi.PostDeleteEventListener;
import org.hibernate.event.spi.PostInsertEvent;
import org.hibernate.event.spi.PostInsertEventListener;
import org.hibernate.event.spi.PostLoadEvent;
import org.hibernate.event.spi.PostLoadEventListener;
import org.hibernate.event.spi.PostUpdateEvent;
import org.hibernate.event.spi.PostUpdateEventListener;
import org.hibernate.event.spi.PreDeleteEvent;
import org.hibernate.event.spi.PreDeleteEventListener;
import org.hibernate.event.spi.PreInsertEvent;
import org.hibernate.event.spi.PreInsertEventListener;
import org.hibernate.event.spi.PreUpdateEvent;
import org.hibernate.event.spi.PreUpdateEventListener;
import org.hibernate.internal.SessionFactoryImpl;

/**
 * Adapter that allows a Hibernate event listener to call a standard JPA
 * EntityListener.
 * 
 * For simplicity only a single bean of each class is supported. It is not
 * difficult to support multiple beans, just messy.
 * 
 * Each listener can have multiple methods with the same annotation.
 * 
 * @author Bear Giles <bgiles@coyotesong.com>
 */
public class HibernateListenersAdapter implements PostInsertEventListener, PreInsertEventListener,
        PreUpdateEventListener, PostUpdateEventListener, PreDeleteEventListener, PostDeleteEventListener,
        PostLoadEventListener {
    private static final long serialVersionUID = 1L;
    private static final Logger log = Logger.getLogger(HibernateListenersAdapter.class);

    @Resource
    private List<Object> listeners;

    @Resource
    private EntityManagerFactory emf;

    private Map<Class, Map<Method, Object>> preInsert = new LinkedHashMap<Class, Map<Method, Object>>();
    private Map<Class, Map<Method, Object>> postInsert = new LinkedHashMap<Class, Map<Method, Object>>();
    private Map<Class, Map<Method, Object>> preUpdate = new LinkedHashMap<Class, Map<Method, Object>>();
    private Map<Class, Map<Method, Object>> postUpdate = new LinkedHashMap<Class, Map<Method, Object>>();
    private Map<Class, Map<Method, Object>> preRemove = new LinkedHashMap<Class, Map<Method, Object>>();
    private Map<Class, Map<Method, Object>> postRemove = new LinkedHashMap<Class, Map<Method, Object>>();
    private Map<Class, Map<Method, Object>> postLoad = new LinkedHashMap<Class, Map<Method, Object>>();

    private EventListenerRegistry registry;

    @PostConstruct
    public void findMethods() {
        for (Object listener : listeners) {
            findMethodsForListener(listener);
        }

        HibernateEntityManagerFactory hemf = (HibernateEntityManagerFactory) emf;
        SessionFactory sf = hemf.getSessionFactory();
        registry = ((SessionFactoryImpl) sf).getServiceRegistry().getService(EventListenerRegistry.class);
    }

    public void findMethodsForListener(Object listener) {
        Class<?> c = listener.getClass();
        for (Method m : c.getMethods()) {
            if (Void.TYPE.equals(m.getReturnType())) {
                Class<?>[] types = m.getParameterTypes();
                if (types.length == 1) {
                    // check for all annotations now...
                    if (m.getAnnotation(PrePersist.class) != null) {
                        if (!preInsert.containsKey(types[0])) {
                            preInsert.put(types[0], new LinkedHashMap<Method, Object>());
                        }
                        preInsert.get(types[0]).put(m, listener);
                    }

                    if (m.getAnnotation(PostPersist.class) != null) {
                        if (!postInsert.containsKey(types[0])) {
                            postInsert.put(types[0], new LinkedHashMap<Method, Object>());
                        }
                        postInsert.get(types[0]).put(m, listener);
                    }

                    if (m.getAnnotation(PreUpdate.class) != null) {
                        if (!preUpdate.containsKey(types[0])) {
                            preUpdate.put(types[0], new LinkedHashMap<Method, Object>());
                        }
                        preUpdate.get(types[0]).put(m, listener);
                    }

                    if (m.getAnnotation(PostUpdate.class) != null) {
                        if (!postUpdate.containsKey(types[0])) {
                            postUpdate.put(types[0], new LinkedHashMap<Method, Object>());
                        }
                        postUpdate.get(types[0]).put(m, listener);
                    }

                    if (m.getAnnotation(PreRemove.class) != null) {
                        if (!preRemove.containsKey(types[0])) {
                            preRemove.put(types[0], new LinkedHashMap<Method, Object>());
                        }
                        preRemove.get(types[0]).put(m, listener);
                    }

                    if (m.getAnnotation(PostRemove.class) != null) {
                        if (!postRemove.containsKey(types[0])) {
                            postRemove.put(types[0], new LinkedHashMap<Method, Object>());
                        }
                        postRemove.get(types[0]).put(m, listener);
                    }

                    if (m.getAnnotation(PostLoad.class) != null) {
                        if (!postLoad.containsKey(types[0])) {
                            postLoad.put(types[0], new LinkedHashMap<Method, Object>());
                        }
                        postLoad.get(types[0]).put(m, listener);
                    }
                }
            }
        }
    }

    /**
     * Execute the listeners. We need to check the entity's class, parent
     * classes, and interfaces.
     * 
     * @param map
     * @param entity
     */
    private void execute(Map<Class, Map<Method, Object>> map, Object entity) {
        if (entity.getClass().isAnnotationPresent(Entity.class)) {

            // check for hits on this class or its superclasses.
            for (Class c = entity.getClass(); c != null && c != Object.class; c = c.getSuperclass()) {
                if (map.containsKey(c)) {
                    for (Map.Entry<Method, Object> entry : map.get(c).entrySet()) {
                        try {
                            entry.getKey().invoke(entry.getValue(), entity);
                        } catch (InvocationTargetException e) {
                            // log it
                        } catch (IllegalAccessException e) {
                            // log it
                        }
                    }
                }
            }

            // check for hits on interfaces.
            for (Class c : entity.getClass().getInterfaces()) {
                if (map.containsKey(c)) {
                    for (Map.Entry<Method, Object> entry : map.get(c).entrySet()) {
                        try {
                            entry.getKey().invoke(entry.getValue(), entity);
                        } catch (InvocationTargetException e) {
                            // log it
                        } catch (IllegalAccessException e) {
                            // log it
                        }
                    }
                }
            }
        }
    }

    /**
     * @see org.hibernate.event.spi.PostDeleteEventListener#onPostDelete(org.hibernate
     *      .event.spi.PostDeleteEvent)
     */
    @Override
    public void onPostDelete(PostDeleteEvent event) {
        execute(postRemove, event.getEntity());
    }

    /**
     * @see org.hibernate.event.spi.PreDeleteEventListener#onPreDelete(org.hibernate
     *      .event.spi.PreDeleteEvent)
     */
    @Override
    public boolean onPreDelete(PreDeleteEvent event) {
        execute(preRemove, event.getEntity());
        return false;
    }

    /**
     * @see org.hibernate.event.spi.PreInsertEventListener#onPreInsert(org.hibernate
     *      .event.spi.PreInsertEvent)
     */
    @Override
    public boolean onPreInsert(PreInsertEvent event) {
        execute(preInsert, event.getEntity());
        return false;
    }

    /**
     * @see org.hibernate.event.spi.PostInsertEventListener#onPostInsert(org.hibernate
     *      .event.spi.PostInsertEvent)
     */
    @Override
    public void onPostInsert(PostInsertEvent event) {
        execute(postInsert, event.getEntity());
    }

    /**
     * @see org.hibernate.event.spi.PreUpdateEventListener#onPreUpdate(org.hibernate
     *      .event.spi.PreUpdateEvent)
     */
    @Override
    public boolean onPreUpdate(PreUpdateEvent event) {
        execute(preUpdate, event.getEntity());
        return false;
    }

    /**
     * @see org.hibernate.event.spi.PostUpdateEventListener#onPostUpdate(org.hibernate
     *      .event.spi.PostUpdateEvent)
     */
    @Override
    public void onPostUpdate(PostUpdateEvent event) {
        execute(postUpdate, event.getEntity());
    }

    /**
     * @see org.hibernate.event.spi.PostLoadEventListener#onPostLoad(org.hibernate
     *      .event.spi.PostLoadEvent)
     */
    @Override
    public void onPostLoad(PostLoadEvent event) {
        execute(postLoad, event.getEntity());
    }
}

@SpringEntityListeners

This approach requires the HibernateListenersConfigurer bean be passed an explicit list of beans. Could we use an annotation on our entity beans instead? Call it @SpringEntityListeners, in contrast to the standard JPA @EntityListeners, and pass it bean classes.

If only there were a way to get a list of managed beans….

There is! The JPA EntityManager provides a way to get a metamodel that includes all managed beans. We can scan this list to find annotated entity classes.

The code:

  1. @Component
  2. public class SpringEntityListenersConfigurer implements ApplicationContextAware {
  3.     private static final Logger log = LoggerFactory.getLogger(SpringEntityListenersConfigurer.class);
  4.  
  5.     private ApplicationContext context;
  6.  
  7.     @Resource
  8.     private EntityManagerFactory entityManagerFactory;
  9.  
  10.     @Override
  11.     public void setApplicationContext(ApplicationContext context) {
  12.         this.context = context;
  13.     }
  14.  
  15.     @PostConstruct
  16.     public void registerListeners() {
  17.         // get registry so we can add listeners.
  18.         HibernateEntityManagerFactory hemf = (HibernateEntityManagerFactory) entityManagerFactory;
  19.         SessionFactory sf = hemf.getSessionFactory();
  20.         EventListenerRegistry registry = ((SessionFactoryImpl) sf).getServiceRegistry().getService(
  21.                 EventListenerRegistry.class);
  22.  
  23.         final Set<Object> listeners = new HashSet<Object>();
  24.  
  25.         EntityManager entityManager = null;
  26.         try {
  27.             entityManager = hemf.createEntityManager();
  28.             // for every entity known to the system...
  29.             for (EntityType<?> entity : entityManager.getMetamodel().getEntities()) {
  30.  
  31.                 // ... register event listeners for it.
  32.                 if (entity.getJavaType().isAnnotationPresent(SpringEntityListeners.class)) {
  33.                     SpringEntityListeners annotation = (SpringEntityListeners) entity.getJavaType().getAnnotation(
  34.                             SpringEntityListeners.class);
  35.                     for (Class<?> beanClass : annotation.value()) {
  36.                         Map<String, ?> map = context.getBeansOfType(beanClass);
  37.                         listeners.addAll(map.values());
  38.                     }
  39.                 }
  40.             }
  41.         } finally {
  42.             if (entityManager != null) {
  43.                 entityManager.close();
  44.             }
  45.         }
  46.  
  47.         // register adapter and listeners.
  48.         HibernateEntityListenersAdapter adapter = new HibernateEntityListenersAdapter(new ArrayList<Object>(listeners),
  49.                 entityManagerFactory);
  50.         registry.getEventListenerGroup(EventType.PRE_INSERT).appendListener(adapter);
  51.         registry.getEventListenerGroup(EventType.POST_COMMIT_INSERT).appendListener(adapter);
  52.         registry.getEventListenerGroup(EventType.PRE_UPDATE).appendListener(adapter);
  53.         registry.getEventListenerGroup(EventType.POST_COMMIT_UPDATE).appendListener(adapter);
  54.         registry.getEventListenerGroup(EventType.PRE_DELETE).appendListener(adapter);
  55.         registry.getEventListenerGroup(EventType.POST_COMMIT_DELETE).appendListener(adapter);
  56.         registry.getEventListenerGroup(EventType.POST_LOAD).appendListener(adapter);
  57.     }
  58. }
@Component
public class SpringEntityListenersConfigurer implements ApplicationContextAware {
    private static final Logger log = LoggerFactory.getLogger(SpringEntityListenersConfigurer.class);

    private ApplicationContext context;

    @Resource
    private EntityManagerFactory entityManagerFactory;

    @Override
    public void setApplicationContext(ApplicationContext context) {
        this.context = context;
    }

    @PostConstruct
    public void registerListeners() {
        // get registry so we can add listeners.
        HibernateEntityManagerFactory hemf = (HibernateEntityManagerFactory) entityManagerFactory;
        SessionFactory sf = hemf.getSessionFactory();
        EventListenerRegistry registry = ((SessionFactoryImpl) sf).getServiceRegistry().getService(
                EventListenerRegistry.class);

        final Set<Object> listeners = new HashSet<Object>();

        EntityManager entityManager = null;
        try {
            entityManager = hemf.createEntityManager();
            // for every entity known to the system...
            for (EntityType<?> entity : entityManager.getMetamodel().getEntities()) {

                // ... register event listeners for it.
                if (entity.getJavaType().isAnnotationPresent(SpringEntityListeners.class)) {
                    SpringEntityListeners annotation = (SpringEntityListeners) entity.getJavaType().getAnnotation(
                            SpringEntityListeners.class);
                    for (Class<?> beanClass : annotation.value()) {
                        Map<String, ?> map = context.getBeansOfType(beanClass);
                        listeners.addAll(map.values());
                    }
                }
            }
        } finally {
            if (entityManager != null) {
                entityManager.close();
            }
        }

        // register adapter and listeners.
        HibernateEntityListenersAdapter adapter = new HibernateEntityListenersAdapter(new ArrayList<Object>(listeners),
                entityManagerFactory);
        registry.getEventListenerGroup(EventType.PRE_INSERT).appendListener(adapter);
        registry.getEventListenerGroup(EventType.POST_COMMIT_INSERT).appendListener(adapter);
        registry.getEventListenerGroup(EventType.PRE_UPDATE).appendListener(adapter);
        registry.getEventListenerGroup(EventType.POST_COMMIT_UPDATE).appendListener(adapter);
        registry.getEventListenerGroup(EventType.PRE_DELETE).appendListener(adapter);
        registry.getEventListenerGroup(EventType.POST_COMMIT_DELETE).appendListener(adapter);
        registry.getEventListenerGroup(EventType.POST_LOAD).appendListener(adapter);
    }
}

Code is available at http://code.google.com/p/invariant-properties-blog/source/browse/spring-entity-listener. In addition to simple logger entity listeners and a transparent password encryption entity listener it has a basic spring-data implementation allowing demonstration of the code.

Comments
1 Comment »
Categories
java
Comments rss Comments rss
Trackback Trackback

Creating Password-Based Encryption Keys

Bear Giles | September 29, 2013

This article discusses creating password-based encryption PBE keys.

First a reminder of earlier points – as a rule you should, when practical, use the PBE key as a master key that is used solely to unlock a working key. This has three major benefits:

  • You can have multiple passwords, e.g., an escrowed recovery key,
  • You can change your password without being forced to reencrypt everything,
  • You can change your working key without being forced to change your password.

I’ll discuss using working keys in database encryption in a future article.

Password-Based Encryption Key Generation with PBKDF2WithHmacSHA1

Java has not had a standard way to create a PBE Key in the past. Individual cryptographic providers provided their own generators but it was a painful process to identify and use a generator that matched your cipher.

This changed with Java 7. There is now a standard key generation algorithm that is available in all JCE implementations. It can definitely be used to produce AES keys. I’ve seen an example of it being used to produce a key of arbitrary length but I haven’t been able to duplicate that behavior – it may be a nonstandard extension.

This algorithm takes four parameters. The first is the key length – use 128 for AES keys. Other possible values are 192 and 256 bits. The second is the number of iterations. Your wifi router uses 4096 iterations but many people now recommend at least 10,000 iterations.

The third parameter is the ‘salt’. A wifi router uses the SSID, many sites use a small file, and I’ll discuss another approach below. The salt should be large enough that the entropy is greater than the key length. E.g., if you want a 128-bit key you should either have (at least) 128 bits of random binary data or about 22 random alpha-numeric characters.

The final parameter is the password. Again the entropy should be greater than the key length. In a webapp the password is often provided by the app server via JNDI.

Finally we want both a cipher key and an IV, not just a cipher key. The lack of an IV, or the use of a weak one, is one of the most common mistakes made by people unfamiliar with cryptography. (See: Not using a random initialization vector with cipher block chaining mode [owasp.org].) One common approach is to generate a random salt and prepend it to the ciphertext for use during decryption but I’ll discuss a different approach that uses the password and salt.

Now the code. First we see how to create a cipher key and IV from a password and salt. (We’ll discuss the salt in a moment.)

  1. public class PbkTest {
  2.     private static final Provider bc = new BouncyCastleProvider();
  3.     private static final ResourceBundle BUNDLE = ResourceBundle
  4.             .getBundle(PbkTest.class.getName());
  5.     private SecretKey cipherKey;
  6.     private AlgorithmParameterSpec ivSpec;
  7.  
  8.     /**
  9.      * Create secret key and IV from password.
  10.      *
  11.      * Implementation note: I've believe I've seen other code that can extract
  12.      * the random bits for the IV directly from the PBEKeySpec but I haven't
  13.      * been able to duplicate it. It might have been a BouncyCastle extension.
  14.      *
  15.      * @throws Exception
  16.      */
  17.     public void createKeyAndIv(char[] password) throws SecurityException,
  18.             NoSuchAlgorithmException, InvalidKeySpecException {
  19.         final String algorithm = "PBKDF2WithHmacSHA1";
  20.         final SecretKeyFactory factory = SecretKeyFactory
  21.                 .getInstance(algorithm);
  22.         final int derivedKeyLength = 128;
  23.         final int iterations = 10000;
  24.  
  25.         // create salt
  26.         final byte[][] salt = feistelSha1Hash(createSalt(), 1000);
  27.  
  28.         // create cipher key
  29.         final PBEKeySpec cipherSpec = new PBEKeySpec(password, salt[0],
  30.                 iterations, derivedKeyLength);
  31.         cipherKey = factory.generateSecret(cipherSpec);
  32.         cipherSpec.clearPassword();
  33.  
  34.         // create IV. This is just one of many approaches. You do
  35.         // not want to use the same salt used in creating the PBEKey.
  36.         try {
  37.             final Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding", bc);
  38.             cipher.init(Cipher.ENCRYPT_MODE, cipherKey, new IvParameterSpec(
  39.                     salt[1], 0, 16));
  40.             ivSpec = new IvParameterSpec(cipher.doFinal(salt[1], 4, 16));
  41.         } catch (NoSuchPaddingException e) {
  42.             throw new SecurityException("unable to create IV", e);
  43.         } catch (InvalidAlgorithmParameterException e) {
  44.             throw new SecurityException("unable to create IV", e);
  45.         } catch (InvalidKeyException e) {
  46.             throw new SecurityException("unable to create IV", e);
  47.         } catch (BadPaddingException e) {
  48.             throw new SecurityException("unable to create IV", e);
  49.         } catch (IllegalBlockSizeException e) {
  50.             throw new SecurityException("unable to create IV", e);
  51.         }
  52.     }
  53. }
public class PbkTest {
    private static final Provider bc = new BouncyCastleProvider();
    private static final ResourceBundle BUNDLE = ResourceBundle
            .getBundle(PbkTest.class.getName());
    private SecretKey cipherKey;
    private AlgorithmParameterSpec ivSpec;

    /**
     * Create secret key and IV from password.
     * 
     * Implementation note: I've believe I've seen other code that can extract
     * the random bits for the IV directly from the PBEKeySpec but I haven't
     * been able to duplicate it. It might have been a BouncyCastle extension.
     * 
     * @throws Exception
     */
    public void createKeyAndIv(char[] password) throws SecurityException,
            NoSuchAlgorithmException, InvalidKeySpecException {
        final String algorithm = "PBKDF2WithHmacSHA1";
        final SecretKeyFactory factory = SecretKeyFactory
                .getInstance(algorithm);
        final int derivedKeyLength = 128;
        final int iterations = 10000;

        // create salt
        final byte[][] salt = feistelSha1Hash(createSalt(), 1000);

        // create cipher key
        final PBEKeySpec cipherSpec = new PBEKeySpec(password, salt[0],
                iterations, derivedKeyLength);
        cipherKey = factory.generateSecret(cipherSpec);
        cipherSpec.clearPassword();

        // create IV. This is just one of many approaches. You do
        // not want to use the same salt used in creating the PBEKey.
        try {
            final Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding", bc);
            cipher.init(Cipher.ENCRYPT_MODE, cipherKey, new IvParameterSpec(
                    salt[1], 0, 16));
            ivSpec = new IvParameterSpec(cipher.doFinal(salt[1], 4, 16));
        } catch (NoSuchPaddingException e) {
            throw new SecurityException("unable to create IV", e);
        } catch (InvalidAlgorithmParameterException e) {
            throw new SecurityException("unable to create IV", e);
        } catch (InvalidKeyException e) {
            throw new SecurityException("unable to create IV", e);
        } catch (BadPaddingException e) {
            throw new SecurityException("unable to create IV", e);
        } catch (IllegalBlockSizeException e) {
            throw new SecurityException("unable to create IV", e);
        }
    }
}

We could simply load a file containing random binary data but using a Feistel cipher allows us to mix entropy from two sources.

  1.     /**
  2.      * Create salt. Two values are provided to support creation of both a cipher
  3.      * key and IV from a single password.
  4.      *
  5.      * The 'left' salt is pulled from a file outside of the app context. this
  6.      * makes it much harder for a compromised app to obtain or modify this
  7.      * value. You could read it as classloader resource but that's not really
  8.      * different from the properties file used below. Another possibility is to
  9.      * load it from a read-only value in a database, ideally one with a
  10.      * different schema than the rest of the application. (It could even be an
  11.      * in-memory database such as H2 that contains nothing but keying material,
  12.      * again initialized from a file outside of the app context.)
  13.      *
  14.      * The 'right' salt is pulled from a properties file. It is possible to use
  15.      * a base64-encoded value but administration is a lot easier if we just take
  16.      * an arbitrary string and hash it ourselves. At a minimum it should be a
  17.      * random mix-cased string of at least (120/5 = 24) characters.
  18.      *
  19.      * The generated salts are equally strong.
  20.      *
  21.      * Implementation note: since this is for demonstration purposes a static
  22.      * string in used in place of reading an external file.
  23.      */
  24.     public byte[][] createSalt() throws NoSuchAlgorithmException {
  25.         final MessageDigest digest = MessageDigest.getInstance("SHA1");
  26.         final byte[] left = new byte[20]; // fall back to all zeroes
  27.         final byte[] right = new byte[20]; // fall back to all zeroes
  28.  
  29.         // load value from file or database.
  30.         // note: we use fixed value for demonstration purposes.
  31.         final String leftValue = "this string should be read from file or database";
  32.         if (leftValue != null) {
  33.             System.arraycopy(digest.digest(leftValue.getBytes()), 0, left, 0,
  34.                     left.length);
  35.             digest.reset();
  36.         }
  37.  
  38.         // load value from resource bundle.
  39.         final String rightValue = BUNDLE.getString("salt");
  40.         if (rightValue != null) {
  41.             System.arraycopy(digest.digest(rightValue.getBytes()), 0, right, 0,
  42.                     right.length);
  43.             digest.reset();
  44.         }
  45.  
  46.         final byte[][] salt = feistelSha1Hash(new byte[][] { left, right },
  47.                 1000);
  48.  
  49.         return salt;
  50.     }
    /**
     * Create salt. Two values are provided to support creation of both a cipher
     * key and IV from a single password.
     * 
     * The 'left' salt is pulled from a file outside of the app context. this
     * makes it much harder for a compromised app to obtain or modify this
     * value. You could read it as classloader resource but that's not really
     * different from the properties file used below. Another possibility is to
     * load it from a read-only value in a database, ideally one with a
     * different schema than the rest of the application. (It could even be an
     * in-memory database such as H2 that contains nothing but keying material,
     * again initialized from a file outside of the app context.)
     * 
     * The 'right' salt is pulled from a properties file. It is possible to use
     * a base64-encoded value but administration is a lot easier if we just take
     * an arbitrary string and hash it ourselves. At a minimum it should be a
     * random mix-cased string of at least (120/5 = 24) characters.
     * 
     * The generated salts are equally strong.
     * 
     * Implementation note: since this is for demonstration purposes a static
     * string in used in place of reading an external file.
     */
    public byte[][] createSalt() throws NoSuchAlgorithmException {
        final MessageDigest digest = MessageDigest.getInstance("SHA1");
        final byte[] left = new byte[20]; // fall back to all zeroes
        final byte[] right = new byte[20]; // fall back to all zeroes

        // load value from file or database.
        // note: we use fixed value for demonstration purposes.
        final String leftValue = "this string should be read from file or database";
        if (leftValue != null) {
            System.arraycopy(digest.digest(leftValue.getBytes()), 0, left, 0,
                    left.length);
            digest.reset();
        }

        // load value from resource bundle.
        final String rightValue = BUNDLE.getString("salt");
        if (rightValue != null) {
            System.arraycopy(digest.digest(rightValue.getBytes()), 0, right, 0,
                    right.length);
            digest.reset();
        }

        final byte[][] salt = feistelSha1Hash(new byte[][] { left, right },
                1000);

        return salt;
    }

A practical implementation using both a resource bundle (which is visible in the classpath) and a string loaded from the filesystem or database is:

  1.     /**
  2.      * Create salt. Two values are provided to support creation of both a cipher
  3.      * key and IV from a single password.
  4.      *
  5.      * The 'left' salt is pulled from a file outside of the app context. this
  6.      * makes it much harder for a compromised app to obtain or modify this
  7.      * value. You could read it as classloader resource but that's not really
  8.      * different from the properties file used below. Another possibility is to
  9.      * load it from a read-only value in a database, ideally one with a
  10.      * different schema than the rest of the application. (It could even be an
  11.      * in-memory database such as H2 that contains nothing but keying material,
  12.      * again initialized from a file outside of the app context.)
  13.      *
  14.      * The 'right' salt is pulled from a properties file. It is possible to use
  15.      * a base64-encoded value but administration is a lot easier if we just take
  16.      * an arbitrary string and hash it ourselves. At a minimum it should be a
  17.      * random mix-cased string of at least (120/5 = 24) characters.
  18.      *
  19.      * The generated salts are equally strong.
  20.      *
  21.      * Implementation note: since this is for demonstration purposes a static
  22.      * string in used in place of reading an external file.
  23.      */
  24.     public byte[][] createSalt() throws NoSuchAlgorithmException {
  25.         final MessageDigest digest = MessageDigest.getInstance("SHA1");
  26.         final byte[] left = new byte[20]; // fall back to all zeroes
  27.         final byte[] right = new byte[20]; // fall back to all zeroes
  28.  
  29.         // load value from file or database.
  30.         // note: we use fixed value for demonstration purposes.
  31.         final String leftValue = "this string should be read from file or database";
  32.         if (leftValue != null) {
  33.             System.arraycopy(digest.digest(leftValue.getBytes()), 0, left, 0,
  34.                     left.length);
  35.             digest.reset();
  36.         }
  37.  
  38.         // load value from resource bundle.
  39.         final String rightValue = BUNDLE.getString("salt");
  40.         if (rightValue != null) {
  41.             System.arraycopy(digest.digest(rightValue.getBytes()), 0, right, 0,
  42.                     right.length);
  43.             digest.reset();
  44.         }
  45.  
  46.         final byte[][] salt = feistelSha1Hash(new byte[][] { left, right },
  47.                 1000);
  48.  
  49.         return salt;
  50.     }
    /**
     * Create salt. Two values are provided to support creation of both a cipher
     * key and IV from a single password.
     * 
     * The 'left' salt is pulled from a file outside of the app context. this
     * makes it much harder for a compromised app to obtain or modify this
     * value. You could read it as classloader resource but that's not really
     * different from the properties file used below. Another possibility is to
     * load it from a read-only value in a database, ideally one with a
     * different schema than the rest of the application. (It could even be an
     * in-memory database such as H2 that contains nothing but keying material,
     * again initialized from a file outside of the app context.)
     * 
     * The 'right' salt is pulled from a properties file. It is possible to use
     * a base64-encoded value but administration is a lot easier if we just take
     * an arbitrary string and hash it ourselves. At a minimum it should be a
     * random mix-cased string of at least (120/5 = 24) characters.
     * 
     * The generated salts are equally strong.
     * 
     * Implementation note: since this is for demonstration purposes a static
     * string in used in place of reading an external file.
     */
    public byte[][] createSalt() throws NoSuchAlgorithmException {
        final MessageDigest digest = MessageDigest.getInstance("SHA1");
        final byte[] left = new byte[20]; // fall back to all zeroes
        final byte[] right = new byte[20]; // fall back to all zeroes

        // load value from file or database.
        // note: we use fixed value for demonstration purposes.
        final String leftValue = "this string should be read from file or database";
        if (leftValue != null) {
            System.arraycopy(digest.digest(leftValue.getBytes()), 0, left, 0,
                    left.length);
            digest.reset();
        }

        // load value from resource bundle.
        final String rightValue = BUNDLE.getString("salt");
        if (rightValue != null) {
            System.arraycopy(digest.digest(rightValue.getBytes()), 0, right, 0,
                    right.length);
            digest.reset();
        }

        final byte[][] salt = feistelSha1Hash(new byte[][] { left, right },
                1000);

        return salt;
    }

Finally we can see it in practice in two test methods:

  1.     /**
  2.      * Obtain password. Architectually we'll want good "separation of concerns"
  3.      * and we should get the cipher key and IV from a separate place than where
  4.      * we use it.
  5.      *
  6.      * This is a unit test so the password is stored in a properties file. In
  7.      * practice we'll want to get it from JNDI from an appserver, or at least a
  8.      * file outside of the appserver's directory.
  9.      *
  10.      * @throws Exception
  11.      */
  12.     @Before
  13.     public void setup() throws Exception {
  14.         createKeyAndIv(BUNDLE.getString("password").toCharArray());
  15.     }
  16.  
  17.     /**
  18.      * Test encryption.
  19.      *
  20.      * @throws Exception
  21.      */
  22.     @Test
  23.     public void testEncryption() throws Exception {
  24.         String plaintext = BUNDLE.getString("plaintext");
  25.  
  26.         Cipher cipher = Cipher.getInstance(BUNDLE.getString("algorithm"), bc);
  27.         cipher.init(Cipher.ENCRYPT_MODE, cipherKey, ivSpec);
  28.         byte[] actual = cipher.doFinal(plaintext.getBytes());
  29.         assertEquals(BUNDLE.getString("ciphertext"),
  30.                 new String(Base64.encode(actual), Charset.forName("UTF-8")));
  31.     }
  32.  
  33.     /**
  34.      * Test decryption.
  35.      *
  36.      * @throws Exception
  37.      */
  38.     @Test
  39.     public void testEncryptionAndDecryption() throws Exception {
  40.         String ciphertext = BUNDLE.getString("ciphertext");
  41.  
  42.         Cipher cipher = Cipher.getInstance(BUNDLE.getString("algorithm"), bc);
  43.         cipher.init(Cipher.DECRYPT_MODE, cipherKey, ivSpec);
  44.         byte[] actual = cipher.doFinal(Base64.decode(ciphertext));
  45.  
  46.         assertEquals(BUNDLE.getString("plaintext"),
  47.                 new String(actual, Charset.forName("UTF-8")));
  48.     }
    /**
     * Obtain password. Architectually we'll want good "separation of concerns"
     * and we should get the cipher key and IV from a separate place than where
     * we use it.
     * 
     * This is a unit test so the password is stored in a properties file. In
     * practice we'll want to get it from JNDI from an appserver, or at least a
     * file outside of the appserver's directory.
     * 
     * @throws Exception
     */
    @Before
    public void setup() throws Exception {
        createKeyAndIv(BUNDLE.getString("password").toCharArray());
    }

    /**
     * Test encryption.
     * 
     * @throws Exception
     */
    @Test
    public void testEncryption() throws Exception {
        String plaintext = BUNDLE.getString("plaintext");

        Cipher cipher = Cipher.getInstance(BUNDLE.getString("algorithm"), bc);
        cipher.init(Cipher.ENCRYPT_MODE, cipherKey, ivSpec);
        byte[] actual = cipher.doFinal(plaintext.getBytes());
        assertEquals(BUNDLE.getString("ciphertext"),
                new String(Base64.encode(actual), Charset.forName("UTF-8")));
    }

    /**
     * Test decryption.
     * 
     * @throws Exception
     */
    @Test
    public void testEncryptionAndDecryption() throws Exception {
        String ciphertext = BUNDLE.getString("ciphertext");

        Cipher cipher = Cipher.getInstance(BUNDLE.getString("algorithm"), bc);
        cipher.init(Cipher.DECRYPT_MODE, cipherKey, ivSpec);
        byte[] actual = cipher.doFinal(Base64.decode(ciphertext));

        assertEquals(BUNDLE.getString("plaintext"),
                new String(actual, Charset.forName("UTF-8")));
    }

The full source code is available at http://code.google.com/p/invariant-properties-blog/source/browse/pbekey.

See also: NIST SP 800-132, Recommendation for Password-Based Key Derivation, section 5.3.

See also: http://stackoverflow.com/questions/2465690/pbkdf2-hmac-sha1/2465884#2465884 for discussion of creation of master key for WPA2 network.

Comments
No Comments »
Categories
java, security
Comments rss Comments rss
Trackback Trackback

Archives

  • May 2020 (1)
  • March 2019 (1)
  • August 2018 (1)
  • May 2018 (1)
  • February 2018 (1)
  • November 2017 (4)
  • January 2017 (3)
  • June 2016 (1)
  • May 2016 (1)
  • April 2016 (2)
  • March 2016 (1)
  • February 2016 (3)
  • January 2016 (6)
  • December 2015 (2)
  • November 2015 (3)
  • October 2015 (2)
  • August 2015 (4)
  • July 2015 (2)
  • June 2015 (2)
  • January 2015 (1)
  • December 2014 (6)
  • October 2014 (1)
  • September 2014 (2)
  • August 2014 (1)
  • July 2014 (1)
  • June 2014 (2)
  • May 2014 (2)
  • April 2014 (1)
  • March 2014 (1)
  • February 2014 (3)
  • January 2014 (6)
  • December 2013 (13)
  • November 2013 (6)
  • October 2013 (3)
  • September 2013 (2)
  • August 2013 (5)
  • June 2013 (1)
  • May 2013 (2)
  • March 2013 (1)
  • November 2012 (1)
  • October 2012 (3)
  • September 2012 (2)
  • May 2012 (6)
  • January 2012 (2)
  • December 2011 (12)
  • July 2011 (1)
  • June 2011 (2)
  • May 2011 (5)
  • April 2011 (6)
  • March 2011 (4)
  • February 2011 (3)
  • October 2010 (6)
  • September 2010 (8)

Recent Posts

  • 8-bit Breadboard Computer: Good Encapsulation!
  • Where are all the posts?
  • Better Ad Blocking Through Pi-Hole and Local Caching
  • The difference between APIs and SPIs
  • Hadoop: User Impersonation with Kerberos Authentication

Meta

  • Log in
  • Entries RSS
  • Comments RSS
  • WordPress.org

Pages

  • About Me
  • Notebook: Common XML Tasks
  • Notebook: Database/Webapp Security
  • Notebook: Development Tips

Syndication

Java Code Geeks

Know Your Rights

Support Bloggers' Rights
Demand Your dotRIGHTS

Security

  • Dark Reading
  • Krebs On Security Krebs On Security
  • Naked Security Naked Security
  • Schneier on Security Schneier on Security
  • TaoSecurity TaoSecurity

Politics

  • ACLU ACLU
  • EFF EFF

News

  • Ars technica Ars technica
  • Kevin Drum at Mother Jones Kevin Drum at Mother Jones
  • Raw Story Raw Story
  • Tech Dirt Tech Dirt
  • Vice Vice

Spam Blocked

53,314 spam blocked by Akismet
rss Comments rss valid xhtml 1.1 design by jide powered by Wordpress get firefox