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 UserType
s 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.
Written by Juraci Paixão Kröhling
Related protips
1 Response
can u please add Struts2+hibernate 3 for storing encrypted password in mySql program..