sábado, 4 de marzo de 2017

Cifrado Triple DES (3DES) en Java desde Genexus

¡Hola a todos!

Hace ya un tiempo tuve la nececidad de implementar la comunicación con una pasarela de pagos para un cliente que quería realizar ventas desde su sitio web. Una de las exigencias en cuanto a seguridad al momento de consumir los servicios web era el encriptado de los datos con el alogoritmo de cifrados Triple DES.
En ese momento, los desarrollos para ese cliente estaban hechos en GX9 (Dev. U7 - Java U6, actualmente está en Evolution) y por lo tanto no había una solución nativa para resolver el tema de la encriptación con ese algoritmo.
La solución entonces fuer desarrollar la encriptación y desencriptación directamente en java y luego utilizar esa clase desde genexus como una clase externa.

La opción que se utilizó del algoritmo de cifrado triple DES consiste basicamente en tener un mensaje a encriptar/desencriptar, una clave compartida y un vector de inicialización compartido. A partir de la combinación de estos tres datos se obtiene el texto encriptado/desencriptado que se necesita.


Para quienes quieran familiarizarse con el algoritmo de cifrado Ttiple DES, hay mucha información en la web, pero para ir rápido a una reseña se pueden ver estos dos links:

http://www.ibm.com/support/knowledgecenter/SSLTBW_2.1.0/com.ibm.zos.v2r1.csfb400/tdes1.htm

https://en.wikipedia.org/wiki/Triple_DES

Es sabido que 3DES en la actualidad no es de los algoritmos más seguros, pero eso era lo que pedia el proveedor del servicio y no había ninguna probabilidad de que eso cambiara  en el corto plazo.

Antes de ir a la solución en concreto, para quienes quieran familiarizarse con las llamadas a clases externas desde GX9, aquí dejo un link con la documentación de Genexus (aunque la especificación aparece como para la versión 8 de Genexus, también sirve para la versión 9).

http://library.gxtechnical.com/gxdlsp/pub/genexus/java/docum/manuals/8.0/mjavai2.htm

Veamos ahora como ejemplo la llamada a nuestra clase externa -seguridad3des- desde GX9.
  
// ENCRIPTACIÓN
call('seguridad3des',&textoPlano,&key,&vectorInicializacion,'ENC',&textoEncriptado) 


// DESENCRIPTACIÓN
call('seguridad3des',&textoEncriptado,&key,&vectorInicializacion,'DES',&textoDesencriptado)



Esta es la vista de nuestro proyecto en Java-Eclipse, donde se puede ver que 'seguridad3des' que está en los ejemplos de llamada desde Genexus es el nombre de la clase en Eclipse.

Aquí va el codigo java de la implementacion y un caso de ejemplo con valores en el texto, clave y vector de inicialización. La idea no es explicar las líneas de este código pero al final haré algunos comentarios que me parece interesante destacar.

import com.genexus.ModelContext;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;

public class seguridad3des {

    public static void main(String[] args) throws Exception {

        String[] parm1 = new String[1];
        String[] parm2 = new String[1];
        String[] parm3 = new String[1];
        String[] parm4 = new String[1];
        String[] parm5 = new String[1];
        String[] parm6 = new String[1];

        // Texto a encriptar
        parm1[0] = "VentaAprobada,12.00,2017-01-1909:09:07";

        // "compartida" en MD5
        parm2[0] = "5a3de55753ab1d33dcd0c76a662a0d28";
         
        // Vector de inicializacion
        parm3[0] = "12345678901=";

        // Resultado
        parm5[0] = "";
         
        // Es el resultado de la encriptacion del parm1
        // que utilizaremos para probar la desencriptacion
        parm6[0] =
          "vt+oxW6yeKglguEW7mbj7GXQXbbT4TEwI9gto/eg1aF7Rgt5grj/SA==";
       
        parm4[0] = "ENC"; // Encriptar
        execute(parm1,parm2,parm3, parm4, parm5);
       
        parm4[0] = "DEC"; // Desencriptar
        execute(parm6,parm2,parm3, parm4, parm5);
       
    }

    public seguridad3des(int remoteHandle, ModelContext context)
    {
        // Simplemente tiene que estar esta definición
    }
   
    public static void execute(String[] textoin, String[] skey,
                               String[] siv, String[] metodo,
                               String[] textoout)
    {
        // metodo[0]: ENC/DEC que son las opciones ENCRIPTAR/DESENCRIPTAR
        try{
            if (metodo[0] == "ENC") {
                textoout[0] = encrypt(textoin[0], skey[0], siv[0]);
            }
            else {
                textoout[0] = decrypt(textoin[0], skey[0], siv[0]);
            }
           
           System.out.println(metodo[0] + ": ");
           System.out.println(textoin[0]);
           System.out.println(textoout[0]);
        } catch(Exception e){
            e.printStackTrace();
        }
    }
   
   public static String encrypt(String message, String skey, String siv)
     throws Exception {
       byte[] keyBytes = DatatypeConverter.parseBase64Binary(skey);
       byte[] ivBytes = DatatypeConverter.parseBase64Binary(siv);

       SecretKey key = new SecretKeySpec(keyBytes, "DESede");
       IvParameterSpec iv = new IvParameterSpec(ivBytes);
       Cipher cipher =
         Cipher.getInstance("DESede/CBC/PKCS5Padding","SunJCE");
       cipher.init(Cipher.ENCRYPT_MODE, key, iv);

       byte[] plainTextBytes = message.getBytes("utf-8");
       byte[] cipherText = cipher.doFinal(plainTextBytes); // Encriptar
             
       String strreturn = DatatypeConverter.printBase64Binary(cipherText);
       
       // Para quitar caracteres de control
       strreturn.replaceAll("\\p{Cntrl}", "?");

       return strreturn;
   }

   public static String decrypt(String smessage, String skey, String siv)
     throws Exception {
       byte[] message = DatatypeConverter.parseBase64Binary(smessage);

       byte[] keyBytes = DatatypeConverter.parseBase64Binary(skey);
       byte[] ivBytes = DatatypeConverter.parseBase64Binary(siv);
      
       SecretKey key = new SecretKeySpec(keyBytes, "DESede");
       IvParameterSpec iv = new IvParameterSpec(ivBytes);
       Cipher decipher =
         Cipher.getInstance("DESede/CBC/PKCS5Padding","SunJCE");
       decipher.init(Cipher.DECRYPT_MODE, key, iv);

       byte[] plainText = decipher.doFinal(message); // Desencriptar

       String strreturn = new String(plainText, "UTF-8");
       
       // Para quitar caracteres de control
       strreturn.replaceAll("\\p{Cntrl}", "?");

       return strreturn;
   }    
}

Este código está pronto para ser ejecutado desde Eclupse. El resultado de esta ejecución sería:

ENC:
VentaAprobada,12.00,2017-01-1909:09:07
vt+oxW6yeKglguEW7mbj7GXQXbbT4T
EwI9gto/eg1aF7Rgt5grj/SA==
 

DEC:
vt+oxW6yeKglguEW7mbj7GXQXbbT4T
EwI9gto/eg1aF7Rgt5grj/SA==
VentaAprobada,12.00,2017-01-1909:09:07

Cabe aclarar que al utilizar este código desde Genexus se utilizará el método execute(...) y que en este caso, como lo utilizaremos desde GX9 simplemente debemos llevarnos el .class (no es necesario generar el .jar) tanto para la carpeta correspondiente de la KB como para el servidor de aplicaciones que estemos utilizando.
Es necesaria la primera línea import com.genexus.ModelContext; para que despues funcione en genexus (por eso la referencia al gxclassR.jar)

Otro punto importante es que en la encriptación/desencriptación se utiliza Base64, en nuestro caso utilizamos org-apache-commons-codec.jar y es necesario incluirlo tanto en el proyecto java como en el lib de la web app.

Para finalizar, está bueno dejar en claro que la integración con Genexus no presenta dificultades, ya que se utiliza un .class como si fuera cualquier otra clase.

Espero te que haya resultado interesante el artículo.
Si tienes preguntas, comentarios y/o sugerencias, puedes dejar un comentario después de la publicación.


¡Hasta la próxima!

2 comentarios:

  1. Buenas tardes Guillermo,

    Estaba probando el ejemplo que pusiste,yo estoy trabajando con GX Evo 3, ya lo pude correr en netbeans, pero por lo que estaba entendiendo esta clase la mandas llamar desde algún objeto en genexus y te debe devolver un valor, en este caso lo que hice fue pasarlo a un jar, ese jar lo importé a genexus, lo copié a la carpeta lib de la aplicación, generé una variable basada en ese jar pero al momento de tratar de generar me manda un error: "does not return a value", esto por este método:
    public seguridad3des(int remoteHandle, ModelContext context)
    {
    // Simplemente tiene que estar esta definición
    }

    Además cuando lo pruebo en línea de comandos no me recibe ningún parámetro, entonces cómo se debe utilizar en genexus para que me pueda recibir paráemtros y me regrese otro parámetro con el resultado?, muchas gracias.

    ResponderEliminar
    Respuestas
    1. Hola Ignacio,
      estás utilizando el método execute? Porque una de las cosas que cambia al utilizar la solución en Evolution con un external object es que te permite utilizar directamente los métodos encrypt y decrypt. Estos dos métodos devuelven un string, pero execute es un void.

      En cuanto al jar, es raro que no te haya dado problemas la importación, porque Evolution necesita que el paquete tenga un nombre distinto a default. En mi caso si genero el jar así como está definido el proyecto e intento importarlo en Evo3 U4 me da problemas, sin embargo si importo directamente la clase me funciona bien (tanto la importación como la ejecución).

      Aguardo tus comentarios, saludos!

      Eliminar