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