Last Updated: November 21, 2017
·
11.98K
· jpkrohling

Encrypting your user's data in the DB

Almost every week, we read that a website that some of us used to trust got breached. And usually, they just say something like: "don't worry, only your personal data has been breached, not your payment information". Honestly, I'd prefer to have my payment data leaked than my personal information, as I can easily ask my bank to cancel my credit card and issue a new credit card.

Wouldn't you, as an user, prefer to hear something like this instead? "All our data has been leaked, but we've encrypted everything with a private key stored on the application server, not on the database. We've changed the key and re-encrypted the data it since we've learned about the breach". You, as a service provider, have this option, and it's not as hard as it sounds.

In Java, you can use Hibernate's UserType feature to tell Hibernate how to store and restore data from the database, without any effort for the consumer of this data (like, your front end). The following example uses JPA, Hibernate UserType and Jasypt, a very good library to deal with encryption and hashing.

The first step is to add Jasypt's custom UserTypes accessible to Hibernate (they are located in the Maven module org.jasypt:jasypt-hibernate4):

File: src/main/java/.../entity/package-info.java

@TypeDefs({
        @TypeDef(
                name="encryptedString",
                typeClass=EncryptedStringType.class,
                parameters= {
                        @Parameter(name="encryptorRegisteredName", value="defaultStringEncryptor")
                }
        )
})
package com.cascaio.appinfo.v1.entity;

import org.hibernate.annotations.Parameter;
import org.hibernate.annotations.TypeDef;
import org.hibernate.annotations.TypeDefs;
import org.jasypt.hibernate4.type.EncryptedStringType;

Now, we tell Jasypt what do we mean by defaultStringEncryptor. If you are deploying your application in a modern Java EE application server (like JBoss AS 7.x / Wildfly), you can do it as a @Startup @Singleton EJB. In this example, we have two encryptors: a password encryptor and our defaultStringEncryptor. For our specific implementation, we make use of PBEWITHSHA256AND256BITAES-CBC-BC", from BouncyCastle (org.bouncycastle:bcprov-jdk15on). Note that we pass the private key via an environment variable. As a fallback, we also look for a specific system property (-Dfoo=bar), useful for our unit tests.

File: src/main/java/.../control/JasyptConfigurator.java

package com.cascaio.appinfo.v1.control;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.jasypt.encryption.pbe.StandardPBEStringEncryptor;
import org.jasypt.hibernate4.encryptor.HibernatePBEEncryptorRegistry;
import org.jasypt.util.password.PasswordEncryptor;
import org.jasypt.util.password.StrongPasswordEncryptor;

import javax.annotation.PostConstruct;
import javax.ejb.Singleton;
import javax.ejb.Startup;
import javax.enterprise.inject.Produces;

/**
 * User: jpkrohling
 * Date: 2013-05-11 1:19 PM
 */
@Startup
@Singleton
public class JasyptConfigurator {

    private StandardPBEStringEncryptor defaultStringEncryptor;
    private PasswordEncryptor passwordEncryptor;

    @PostConstruct
    public void configureJasypt() {
        // by default, we get from the system's ENV vars
        String password = System.getenv("CASCAIO_APPINFO_JASYPT_PASSWORD");

        // fallback: system properties (like, -Dcascaio.....=bla)
        if (null == password || password.isEmpty()) {
            password = System.getProperty("cascaio.appinfo.jasypt.password");
        }

        // cannot determine the password, fail! we don't want to set an empty password
        if (null == password || password.isEmpty()) {
            throw new RuntimeException("Cannot configure Jasypt, as the encryption password is empty!");
        }

        this.defaultStringEncryptor = new StandardPBEStringEncryptor();
        this.defaultStringEncryptor.setProvider(new BouncyCastleProvider());
        this.defaultStringEncryptor.setAlgorithm("PBEWITHSHA256AND256BITAES-CBC-BC");
        this.defaultStringEncryptor.setKeyObtentionIterations(1000);
        this.defaultStringEncryptor.setPassword(password);

        HibernatePBEEncryptorRegistry registry = HibernatePBEEncryptorRegistry.getInstance();
        registry.registerPBEStringEncryptor("defaultStringEncryptor", defaultStringEncryptor);

        this.passwordEncryptor = new StrongPasswordEncryptor();
    }

    @Produces
    public StandardPBEStringEncryptor getDefaultStringEncryptor() {
        if (null == defaultStringEncryptor) {
            configureJasypt();
        }
        return defaultStringEncryptor;
    }

    @Produces
    public PasswordEncryptor getPasswordEncryptor() {
        if (null == passwordEncryptor) {
            configureJasypt();
        }

        return passwordEncryptor;
    }
}

Then, you are ready to annotate the fields that you want to encrypt in your database, like this:

File: src/main/java/.../Application.java

package com.cascaio.appinfo.v1.entity;

import com.cascaio.appinfo.v1.control.JasyptConfigurator;
import org.hibernate.annotations.Type;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.validation.constraints.NotNull;
import java.util.UUID;

/**
 * User: jpkrohling
 * Date: 2013-05-11 1:11 PM
 */
@Entity
public class Application {

    @Id
    private String id = UUID.randomUUID().toString();

    @NotNull
    @Type(type = "encryptedString")
    private String name;

    @NotNull
    private String accessKey;

    @NotNull
    @Type(type = "encryptedString")
    private String secretKey;

    protected Application() {}

    public Application(String name, String accessKey, String secretKey) {
        this(UUID.randomUUID().toString(), name, accessKey, secretKey);
    }

    public Application(String id, String name, String accessKey, String secretKey) {
        this.id = id;
        this.name = name;
        this.accessKey = new JasyptConfigurator().getPasswordEncryptor().encryptPassword(accessKey);
        this.secretKey = secretKey;
    }

    public String getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public String getAccessKey() {
        return accessKey;
    }

    public String getSecretKey() {
        return secretKey;
    }
}

In this example, the database representation for the properties name and secretKey are encrypted when being stored in the database and decrypted when being restored. You might also want to take a look at the unit test testNameAndAccessKeyAreEncrypted, to check that the entity itself have the properties encrypted/decrypted on demand.

There are a couple gotchas, though:

  • You cannot use the encrypted fields in where clauses. Kinda obvious, but worth mentioning.
  • It reduces your ability to do manual changes in the database.
  • It will slow down your database operations and consume more space, but you can always fine tune the encryptor and/or the database. Also, it's a fair price to pay for the extra security.

1 Response
Add your response

can u please add Struts2+hibernate 3 for storing encrypted password in mySql program..

over 1 year ago ·