Campos encriptados en base de datos con Grails

por | Jun 24, 2010 | Java

Para comunicarse con la base de datos, grails utiliza el magnífico gorm, un wrapper de hibernate .

Para almacenar datos encriptados en base de datos que podamos desencriptar posteriormente (o sea, encriptado con un cifrado simetrico, no un hash) debemos especificar a grom en el mapping que nuestro campo password es de tipo encriptado

Lo mejor es empezar creando un Codec de grails que nos permita facilmente encriptar y desencriptar una cadena de texto con una simple sentencia como:

"mi texto a encriptar".encodeAsSecure()
"mi texto a DESencriptar".decodeSecure()

Crearemos nuestro codec SecureCodec en la carpeta grails-app/utils

class SecureCodec {
    String password = "clave_de_cifrado_que_no_deberiamos_guardar_aqui"

    static encode = { str ->
        if(['null', 'Null', 'NULL', '', null].contains(str)) str = ''
        try {
            Cipher cipher = setupCipher(Cipher.ENCRYPT_MODE, password)

            // encriptamos
            byte[] encodedBytes = cipher.doFinal(str.getBytes())

            // pasamos a hexadecimal
            String hex = new String(new Hex().encode(encodedBytes))?.toUpperCase()
            return hex;
        } catch(Exception e) {
            return str
        }
    }

    static decode = { hex ->
        try {
            // pasamos de hexadecimal a bytes
            byte[] bytes = new Hex().decodeHex((char[])hex)

            // desencriptamos
            Cipher cipher = setupCipher(Cipher.DECRYPT_MODE, password)
            def decripted = new String(cipher.doFinal(bytes))

            return decripted
        } catch(Exception e) {
            return hex
        }
    }

    private static setupCipher(mode, password) {
        Cipher cipher = Cipher.getInstance("AES");

        // recortamos el pass a 16 caracteres - 128 bits
        byte[] keyBytes = new byte[16];
        byte[] b = password.getBytes();
        int len = b.length;
        if (len > keyBytes.length)
            len = keyBytes.length;
        System.arraycopy(b, 0, keyBytes, 0, len);

        // creating SecretKeySpec 
        SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");

        cipher.init(mode, keySpec);
        return cipher
    }

}

Luego, crearemos nuestro tipo de mapeo que debe implementar la interfaz org.hibernate.usertype.UserType
en la carpeta src/groovy

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
class EncryptedStringType implements UserType {
Object nullSafeGet(ResultSet rs, String[] names, Object owner) throws SQLException {
String value = rs.getString(names[0])
if (!value) {
return ''
}
return value.decodeSecure()
}
void nullSafeSet(PreparedStatement st, Object value, int index) throws SQLException {
value = value ? : ''
if (value != null) {
String encrypted = value.toString().encodeAsSecure() st.setString index, encrypted
} else {
st.setNull(index, Types.VARCHAR)
}
}
Class returnedClass() {
String
}
int[] sqlTypes() {
[Types.VARCHAR] as int[]
}
Object assemble(Serializable cached, Object owner) {
cached.toString()
}
Object deepCopy(Object value) {
value.toString()
}
Serializable disassemble(Object value) {
value.toString()
}
boolean equals(Object x, Object y) {
x == y
}
int hashCode(Object x) {
x.hashCode()
}
boolean isMutable() {
true
}
Object replace(Object original, Object target, Object owner) {
original
}
}
class EncryptedStringType implements UserType { Object nullSafeGet(ResultSet rs, String[] names, Object owner) throws SQLException { String value = rs.getString(names[0]) if (!value) { return '' } return value.decodeSecure() } void nullSafeSet(PreparedStatement st, Object value, int index) throws SQLException { value = value ? : '' if (value != null) { String encrypted = value.toString().encodeAsSecure() st.setString index, encrypted } else { st.setNull(index, Types.VARCHAR) } } Class returnedClass() { String } int[] sqlTypes() { [Types.VARCHAR] as int[] } Object assemble(Serializable cached, Object owner) { cached.toString() } Object deepCopy(Object value) { value.toString() } Serializable disassemble(Object value) { value.toString() } boolean equals(Object x, Object y) { x == y } int hashCode(Object x) { x.hashCode() } boolean isMutable() { true } Object replace(Object original, Object target, Object owner) { original } }
class EncryptedStringType implements UserType {

  Object nullSafeGet(ResultSet rs, String[] names, Object owner) throws SQLException {
    String value = rs.getString(names[0])

    if (!value) {
      return ''
    }

    return value.decodeSecure()

  }
  void nullSafeSet(PreparedStatement st, Object value, int index) throws SQLException {
    value = value ? : ''
    if (value != null) {
      String encrypted = value.toString().encodeAsSecure() st.setString index, encrypted
    } else {
      st.setNull(index, Types.VARCHAR)
    }
  }
  Class returnedClass() {
    String
  }
  int[] sqlTypes() {
    [Types.VARCHAR] as int[]
  }
  Object assemble(Serializable cached, Object owner) {
    cached.toString()
  }
  Object deepCopy(Object value) {
    value.toString()
  }
  Serializable disassemble(Object value) {
    value.toString()
  }
  boolean equals(Object x, Object y) {
    x == y
  }
  int hashCode(Object x) {
    x.hashCode()
  }
  boolean isMutable() {
    true
  }
  Object replace(Object original, Object target, Object owner) {
    original
  }
}

Ya tenemos todo lo necesario. Ahora solo nos queda decirle a gorm que mapee nuestro campo personalData como tipo EncryptedStringType

class Usuario {
    String username
    String personalData

    static mapping = {
        personalData type:'EncryptedStringType'
    }
}

Y ya esta. Ahora si miramos el texto guardado en nuestra bd, saldrá algo como ’13B2C7531DD55C97F250A1BA50C39BB4′

De forma transparente, al cargar el objeto con gorm, tendremos acceso a nuestro campo personalData como texto en claro.

El principal problema es que perdemos la capacidad de hacer búsquedas directas usando wildcards ‘%’, debido a la naturaleza de la encriptacion. Para conseguirlo hay que dar un pequeño rodeo, comparando la cadena desencriptada de la bd con lo que queramos buscar:

sql.findAll("from Usuario where UPPER( CONVERT( AES_DECRYPT(UNHEX(u.user) , 'mi_password'), CHAR)) like " + " '%texto_a_buscar%' ")

0 comentarios

Enviar un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Blog de Neodoo Microsystems
Resumen de privacidad

Esta web utiliza cookies para que podamos ofrecerte la mejor experiencia de usuario posible. La información de las cookies se almacena en tu navegador y realiza funciones tales como reconocerte cuando vuelves a nuestra web o ayudar a nuestro equipo a comprender qué secciones de la web encuentras más interesantes y útiles. Puedes encontrar más información en nuestra Política de privacidad y Política de cookies.