A new feature that I've been developing for Altos Research will require that we store the credit card information of our subscribers. This makes me nervous as hell. I am scared to death of the impact a data security breach would have on the customers whose data is compromised as well as on the financial viability of the company. My first engineering task in all of this was to come up with a reliable method of encrypting the billing information fields that we store in PostgreSQL.
The first part of the solution was fairly straightforward - implementing a utility class that would encrypt and descrupt strings using the AES encryption algorithm. I used various examples from around the net, and put together a basic encrypt/decrypt utility that looks something like this - it takes a BASE64-encoded encrypted string value and returns the unencrypted value using the encryption key provided (and yes, these implementations are not even close to fully optimized):
public static String encryptString ( String pUnencryptedString, String pKey ) {
Security.addProvider(new SunJCE());
byte[] keybBytes = Base64.decodeBase64(pKey.getBytes());
SecretKeySpec sks = new SecretKeySpec(keybBytes, "AES");
Cipher c = Cipher.getInstance("AES");
c.init(Cipher.ENCRYPT_MODE, sks);
byte[] inputBytes = pUnencryptedString.getBytes("UTF8");
byte[] outputBytes = cipher.doFinal(inputBytes);
String base64 = new String (Base64.encodeBase64(outputBytes));
return base64;
}
public static String decryptString ( String pEncryptedString, String pKey ) {
Security.addProvider(new SunJCE());
byte[] keybBytes = Base64.decodeBase64(pKey.getBytes());
SecretKeySpec sks = new SecretKeySpec(keybBytes, "AES");
Cipher c = Cipher.getInstance("AES");
c.init(Cipher.DECRYPT_MODE, sks);
byte[] inputBytes = Base64.decodeBase64(pEncryptedString.getBytes());
byte[] outputBytes = cipher.doFinal(inputBytes);
return new String(outputBytes);
}
With the basic encrypt/decrypt utility in place, I then created a Hibernate UserType named EncryptedValueUserType to perform the needed transformations at the time the values are read from (decryption) and written to (encryption) the PostgreSQL database. That UserType class looks like this - the interesting methods being 'nullSafeGet' in which the encryption is done and 'nullSafeSet' which performs the encryption:
public class EncryptedValueUserType
implements Serializable, UserType {
private static final int[] SQL_TYPES = {Types.VARCHAR};
public int[] sqlTypes() {
return SQL_TYPES;
}
public Class returnedClass() {
return java.lang.String.class;
}
public boolean equals(Object pObject1, Object pObject2) throws HibernateException {
if (pObject1 == null && pObject2 == null) return true;
if (pObject1 == null || pObject2 == null) return false;
return (( String ) pObject1).equals(( String ) pObject2);
}
public int hashCode(Object o) throws HibernateException {
return ((String) o).hashCode();
}
public Object nullSafeGet(ResultSet pResultSet, String[] pStrings, Object o)
throws HibernateException, SQLException {
String encryptedValue = pResultSet.getString(pStrings[0]);
return pResultSet.wasNull() ? null : decryptString (encryptedValue, "MY_AES_ENCRYPTION_KEY");
}
public void nullSafeSet(PreparedStatement pPreparedStatement, Object o, int i)
throws HibernateException, SQLException {
if ( o == null ) {
pPreparedStatement.setNull(i, Types.VARCHAR);
} else {
pPreparedStatement.setString(i, encryptString( (String) o ), "MY_AES_ENCRYPTION_KEY");
}
}
public Object deepCopy(Object o) throws HibernateException {
return o;
}
public boolean isMutable() {
return false;
}
public Serializable disassemble(Object o) throws HibernateException {
return (Serializable) o;
}
public Object assemble(Serializable pSerializable, Object o) throws HibernateException {
return pSerializable;
}
public Object replace(Object o, Object o1, Object o2) throws HibernateException {
return o;
}
}
Of course you should not embed the actual encryption key directly in the Java source file as I show above, but you get the idea. Now all you need to do to use this is assign the 'EncryptedValueUserType' to any database field you would like to encrypt in your Hibernate mapping file (hbm.xml):
<property name="accountNumber" type="com.altosresearch.utils.EncryptedValueUserType" column="account_number" />
And now all your business logic nees to do is call the normal getter/setter methods for this property. All of the security work is done completely transparently. This makes it VERY simple to encrypt lots of different fields besides credit card numbers, such as passwords and even address information.