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