Seleccionar página

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

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%' ")

Te puede interesar…

0 comentarios

Enviar un comentario

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