diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..6e1397d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,35 @@
+# IntelliJ
+*.iml
+*.ipr
+*.iws
+.idea
+classes
+
+# Eclipse
+.classpath
+.project
+.buildpath
+.springBeans
+.settings/
+.metadata/
+
+# Netbeans
+nb-configuration.xml
+
+# Maven
+target
+pom.xml.versionsBackup
+
+# Gradle
+build
+.gradle
+
+# OS
+Thumbs.db
+.DS_Store
+
+# misc
+.checkstyle
+.pmd
+.fbprefs
+MANIFEST.MF
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..e69de29
diff --git a/doc/LICENSE.txt b/doc/LICENSE.txt
new file mode 100644
index 0000000..2ec25a6
--- /dev/null
+++ b/doc/LICENSE.txt
@@ -0,0 +1,30 @@
+The BSD License
+
+Copyright (c) 2013 RIPE NCC
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ - Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+ - Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ - Neither the name of the RIPE NCC nor the names of its contributors may be
+ used to endorse or promote products derived from this software without
+ specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..c55ee68
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,95 @@
+
+
+ 4.0.0
+
+ com.bol
+ spring-data-mongodb-encrypt
+ jar
+ spring-data-mongodb-encrypt
+ 1.0
+ High performance, per-field encryption for spring-data-mongodb
+ https://github.com/agoston/spring-data-mongodb-encrypt
+
+
+ BSD
+ https://opensource.org/licenses/BSD-3-Clause
+
+
+
+ https://github.com/agoston/spring-data-mongodb-encrypt
+
+
+
+ Ágoston Horváth
+ github.com/agoston
+ ahorvath@bol.com
+ bol.com
+ 1
+ http://bol.com
+
+
+
+
+
+ org.springframework.data
+ spring-data-mongodb
+ 1.10.4.RELEASE
+ provided
+
+
+ org.hamcrest
+ hamcrest-library
+ 1.3
+ test
+
+
+ junit
+ junit
+ 4.12
+ test
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.3
+
+ 1.8
+ 1.8
+
+
+
+ org.apache.maven.plugins
+ maven-source-plugin
+ 2.3
+
+
+ attach-sources
+
+ jar
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+ 2.10.1
+
+
+ attach-javadocs
+
+ jar
+
+
+ -Xdoclint:none
+
+
+
+
+
+
+
diff --git a/src/main/java/com/bol/secure/Encrypted.java b/src/main/java/com/bol/secure/Encrypted.java
new file mode 100644
index 0000000..c39f918
--- /dev/null
+++ b/src/main/java/com/bol/secure/Encrypted.java
@@ -0,0 +1,11 @@
+package com.bol.secure;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE})
+public @interface Encrypted {
+}
diff --git a/src/main/java/com/bol/secure/EncryptionEventListener.java b/src/main/java/com/bol/secure/EncryptionEventListener.java
new file mode 100644
index 0000000..6836f3b
--- /dev/null
+++ b/src/main/java/com/bol/secure/EncryptionEventListener.java
@@ -0,0 +1,240 @@
+package com.bol.secure;
+
+import com.mongodb.BasicDBList;
+import com.mongodb.BasicDBObject;
+import com.mongodb.DBObject;
+import org.bson.BSONObject;
+import org.bson.BasicBSONDecoder;
+import org.bson.BasicBSONEncoder;
+import org.bson.types.Binary;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.data.mongodb.core.mapping.BasicMongoPersistentEntity;
+import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
+import org.springframework.data.mongodb.core.mapping.event.AbstractMongoEventListener;
+import org.springframework.data.mongodb.core.mapping.event.AfterLoadEvent;
+import org.springframework.data.mongodb.core.mapping.event.BeforeSaveEvent;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.ParameterizedType;
+import java.security.SecureRandom;
+import java.util.*;
+import java.util.function.Function;
+
+public class EncryptionEventListener extends AbstractMongoEventListener implements ApplicationContextAware {
+ private static final Logger LOG = LoggerFactory.getLogger(EncryptionEventListener.class);
+ static final String MAP_FIELD_MATCHER = "*";
+ static final String CIPHER = "AES/CBC/PKCS5Padding";
+
+ final int saltLength;
+ final String cipher;
+ final SecretKeySpec key;
+
+ Map encrypted;
+
+ public EncryptionEventListener(byte[] secret) {
+ this(secret, 16, CIPHER);
+ }
+
+ public EncryptionEventListener(byte[] secret, int saltLength) {
+ this(secret, saltLength, CIPHER);
+ }
+
+ public EncryptionEventListener(byte[] secret, int saltLength, String cipher) {
+ this.saltLength = saltLength;
+ this.cipher = cipher;
+ this.key = new SecretKeySpec(secret, cipher.substring(0, cipher.indexOf('/')));
+ }
+
+ public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+ encrypted = new HashMap<>();
+ MongoMappingContext mappingContext = applicationContext.getAutowireCapableBeanFactory().getBean(MongoMappingContext.class);
+
+ for (BasicMongoPersistentEntity> entity : mappingContext.getPersistentEntities()) {
+ List children = processDocument(entity.getClass());
+ if (!children.isEmpty()) encrypted.put(entity.getClass(), new Node("", children, NodeType.ROOT));
+ }
+ }
+
+ List processDocument(Class objectClass) {
+ List nodes = new ArrayList<>();
+ for (Field field : objectClass.getDeclaredFields()) {
+ try {
+ if (Modifier.isStatic(field.getModifiers()) || Modifier.isTransient(field.getModifiers())) continue;
+
+ if (field.isAnnotationPresent(Encrypted.class)) {
+ // direct @Encrypted annotation - crypt the corresponding field of BasicDbObject
+ nodes.add(new Node(field.getName(), Collections.emptyList(), NodeType.DIRECT));
+
+ } else if (Collection.class.isAssignableFrom(field.getType())) {
+ // descending into Collection
+ ParameterizedType parameterizedType = (ParameterizedType) field.getGenericType();
+ Class> genericClass = (Class>) parameterizedType.getActualTypeArguments()[0];
+
+ List children = processDocument(genericClass);
+ if (!children.isEmpty()) nodes.add(new Node(field.getName(), children, NodeType.LIST));
+
+ } else if (Map.class.isAssignableFrom(field.getType())) {
+ // descending into Values of Map objects
+ ParameterizedType parameterizedType = (ParameterizedType) field.getGenericType();
+ Class> genericClass = (Class>) parameterizedType.getActualTypeArguments()[1];
+
+ List children = processDocument(genericClass);
+ if (!children.isEmpty()) {
+ List mapKeys = Collections.singletonList(new Node(MAP_FIELD_MATCHER, children, NodeType.DOCUMENT));
+ nodes.add(new Node(field.getName(), mapKeys, NodeType.MAP));
+ }
+
+ } else {
+ // descending into sub-documents
+ List children = processDocument(field.getType());
+ if (!children.isEmpty()) nodes.add(new Node(field.getName(), children, NodeType.DOCUMENT));
+ }
+
+ } catch (Exception e) {
+ LOG.error("{}.{}", objectClass.getName(), field.getName(), e);
+ }
+ }
+
+ return nodes;
+ }
+
+ @Override
+ public void onAfterLoad(AfterLoadEvent event) {
+ try {
+ DBObject dbObject = event.getDBObject();
+
+ Node node = encrypted.get(event.getType());
+ if (node == null) return;
+
+ BasicBSONDecoder decoder = new BasicBSONDecoder();
+ cryptFields(dbObject, node, o -> decoder.readObject(decrypt((byte[]) o)));
+ } catch (Exception e) {
+ LOG.error("onAfterLoad", e);
+ throw e;
+ }
+ }
+
+ @Override
+ public void onBeforeSave(BeforeSaveEvent event) {
+ try {
+ DBObject dbObject = event.getDBObject();
+
+ Node node = encrypted.get(event.getSource().getClass());
+ if (node == null) return;
+
+ BasicBSONEncoder encoder = new BasicBSONEncoder();
+ cryptFields(dbObject, node, o -> new Binary(encrypt(encoder.encode((BSONObject) o))));
+ } catch (Exception e) {
+ LOG.error("onBeforeSave", e);
+ throw e;
+ }
+ }
+
+ void cryptFields(DBObject dbObject, Node node, Function