Encrypting a Database Column in Grails

Grails and hibernate user types make it easy to encrypt tables on a column by column basis.

Note that this approach has some limitations. Most importantly, you won’t be able to make queries based on the encrypted field, since they’ll try to match against the encrypted text instead of the plaintext.

The grails mapping DSL allows the user type to be specified easily:

    import ca.redtoad.crypto.hibernate.EncryptedString

        static mapping = {
            ccNumber type: EncryptedString

Here’s the implementation of the user type. It stores the encrypted value as a JSON map containing the base64 encoded encrypted value and the salt. A text-based JSON representation is purely for convenience — it could easily be stored in binary form in a BLOB column to save space and parsing overhead.

package ca.redtoad.crypto.hibernate

import java.sql.*
import org.codehaus.groovy.grails.commons.ApplicationHolder
import org.hibernate.*
import org.hibernate.usertype.UserType
import grails.converters.JSON
import java.security.MessageDigest
import javax.crypto.*
import javax.crypto.spec.*

class EncryptedString implements UserType {

    int[] sqlTypes() { [Types.VARCHAR] as int[] }
    Class returnedClass() { String }
    boolean equals(x, y) { x == y }
    int hashCode(x) { x.hashCode() }
    Object deepCopy(value) { value }
    boolean isMutable() { false }
    Serializable disassemble(value) { value }
    def assemble(Serializable cached, owner) { cached }
    def replace(original, target, owner) { original }

    Object nullSafeGet(ResultSet resultSet, String[] names, Object owner) throws HibernateException, SQLException {
        String str = resultSet.getString(names[0])
        str ? decrypt(str) : null

    void nullSafeSet(PreparedStatement statement, Object value, int index) {
        statement.setString(index, value ? encrypt(value) : null)
    private String encrypt(String plaintext) {
        String salt = 'Salt!'
        String key = "myKey"
        Cipher c = Cipher.getInstance('AES')
        byte[] keyBytes = MessageDigest.getInstance('SHA-1').digest("$salt$key".getBytes())[0..<16]
        c.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(keyBytes, 'AES'))
        [data: c.doFinal(plaintext.bytes).encodeBase64() as String, salt: salt] as JSON

    private String decrypt(String ciphertext) {
        def json = JSON.parse(ciphertext)
        Cipher c = Cipher.getInstance('AES')
        String key = "myKey"
        byte[] keyBytes = MessageDigest.getInstance('SHA-1').digest("${json.salt}${key}".getBytes())[0..<16]
        c.init(Cipher.DECRYPT_MODE, new SecretKeySpec(keyBytes, 'AES'))
        new String(c.doFinal(json['data'].decodeBase64()))

Comments are closed.