Cifrado de datos Javascript

Tengo que realizar un cifrado en específico para conectarme con mi pasarela de pago.
Estoy usando la librería crypto-js ya que pensé que al ser Javascript funcionaría bien con Velneo.

Este es el código que me han enviado de ejemplo desde la pasarela de pago (he cambiado los datos por seguridad).

<html>
<head>
<script type='text/javascript' src='https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js'></script>
<script type='text/javascript' src='https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/tripledes.min.js'></script>
<script type='text/javascript' src='https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/hmac-sha256.min.js'></script>

<script>

    function convertArrayToASCII(ivArray) {
        let IV = '';
        for (let i = 0; i < ivArray.length; i++) {
            IV += String.fromCharCode(ivArray[i]);
        }
        console.log(IV);
        return IV;
    }

    function des_encrypt(message, key) {
        let ivArray = [0,0,0,0,0,0,0,0];

        let encode_str = CryptoJS.TripleDES.encrypt(message , key, {
            iv: CryptoJS.enc.Utf8.parse(convertArrayToASCII(ivArray)),
            mode: CryptoJS.mode.CBC,
            padding: CryptoJS.pad.ZeroPadding
        });
        return encode_str.toString();
    }

    function stringBase64Encode(input) {
        let utf8Input = CryptoJS.enc.Utf8.parse(input);
        return CryptoJS.enc.Base64.stringify(utf8Input);
    }

    function bytesBase64Encode(input) {
        return CryptoJS.enc.Base64.stringify(input);
    }

    function base64Decode(input) {
    	//Decodifica el Base64 y devuelve el array de bytes directamente
        return CryptoJS.enc.Base64.parse(input);
    }


function calcularFirma() {
       let merchantOrder = '123456789';

            console.log(merchantOrder);

            let data = {
                'DS_MERCHANT_AMOUNT': '1',
                'DS_MERCHANT_CURRENCY': '978',
                'DS_MERCHANT_MERCHANTCODE': '353264575',
                'DS_MERCHANT_MERCHANTURL': 'http://192.168.1.139/prueba_tpv/URLNOTI.pro',
                'DS_MERCHANT_ORDER': merchantOrder,
                'DS_MERCHANT_TERMINAL': '100',
                'DS_MERCHANT_TRANSACTIONTYPE': '0'
            }

 	    console.log(data);
            let encodedParameters = stringBase64Encode(JSON.stringify(data));

            console.log('base64', encodedParameters)

            //No hay que pasar la firma a base64 puesto que ya está en base64
            //Lo que hay que hacer es decodificarla de bas64 y tal cual pasarla como clave para encriptar el Nº de pedido
            //La encriptación tiene que ser con ZeroPadding
	    
	    let encodedSignature = 'SGVsbG8sIFdvcmxkIQ=='; //Valor de la clave en el portal de administración
            
            let encodedSignatureDES = des_encrypt(merchantOrder, base64Decode(encodedSignature)); //Se cifra el número de pedido con la clave para obtener la clave de operación
            console.log('base64Decode(encodedSignature):', base64Decode(encodedSignature))
            console.log('Clave de la operación:', encodedSignatureDES);


            let encodedDsSignature = CryptoJS.HmacSHA256(encodedParameters, base64Decode(encodedSignatureDES)); //Se calcula el HMAC de los parámetros en Base64 con la clave de operación
            console.log('base64Decode(encodedSignatureDES):', base64Decode(encodedSignatureDES))
            console.log('HMAC', encodedDsSignature);
            
            let dsSignature = CryptoJS.enc.Base64.stringify(encodedDsSignature); //Se pasa a Base 64
            console.log('HMAC base64', dsSignature)


            let encodedRequest = { 'Ds_MerchantParameters': encodedParameters, 'Ds_Signature': dsSignature, 'Ds_SignatureVersion': 'HMAC_SHA256_V1' }

            console.log(encodedRequest)
            
            
            //Esto es para validarlo contra test por redirección. A ellos no les sirve
            document.pago.datos.value=JSON.stringify(data);
            document.pago.Ds_MerchantParameters.value=encodedParameters;
            document.pago.Ds_Signature.value=dsSignature;
            
}
</script>

</head>
<body>
<a href='javascript:calcularFirma()'>Calcular Firma</a>
<form name='pago' action='https://sis-t.redsys.es:25443/sis/realizarPago' method='POST'>
Datos en claro:
<textarea name='datos' cols='80' rows='5'>
</textarea></br>

Datos en Base64:
<textarea name='Ds_MerchantParameters' cols='8' rows='5'>
</textarea></br>
Firma calculada:
<input type='text' name='Ds_Signature' value='' size='100'/></br>
Versión Firma:
<input type='text' name='Ds_SignatureVersion' value='HMAC_SHA256_V1'/></br>
<input type='submit' value='Probar contra tes'/>
</form>

</body>
</html>

Y este es mi código de Velneo

#include "(CurrentProject)/crypto-js-4.1.1/crypto-js.js"
#include "(CurrentProject)/crypto-js-4.1.1/tripledes.js"
#include "(CurrentProject)/crypto-js-4.1.1/hmac-sha256.js"

// -----------------------------------------
// Variables Velneo a variables JavaScript
// -----------------------------------------
var clave  = theRoot.varToString("CLAVE");
var num_ped = theRoot.varToString("NUM_PED");
var ds_mp  = theRoot.varToString("DS_MP");

// -----------------------------------------
function stringBase64Encode(input) {
	var utf8Input = CryptoJS.enc.Utf8.parse(input);
	return CryptoJS.enc.Base64.stringify(utf8Input);
}

// -----------------------------------------
function convertArrayToASCII(ivArray) {
	var IV = "";
	for (var i = 0; i < ivArray.length; i++) {
		IV += String.fromCharCode(ivArray[i]);
	}
	alert("IV:" + IV);
	return IV;
}

// -----------------------------------------
function encryptByDES(message, key) {
	
    var ivArray = [0, 0, 0, 0, 0, 0, 0, 0];
 
	var encrypted_tresdes= CryptoJS.TripleDES.encrypt(message, key, {
		iv: CryptoJS.enc.Utf8.parse(convertArrayToASCII(ivArray)),
		mode: CryptoJS.mode.CBC,
        padding: CryptoJS.pad.ZeroPadding
	});
	
	return encrypted_tresdes.toString();
		
}

// -----------------------------------------

function base64Decode(input) {
	//Decodifica el Base64 y devuelve el array de bytes directamente
	return CryptoJS.enc.Base64.parse(input);
}

// -----------------------------------------
var encodedParameters = stringBase64Encode(ds_mp);
//Se cifra el número de pedido con la clave para obtener la clave de operación
var clave64 = base64Decode(clave);
var signatureDES = encryptByDES(num_ped, base64Decode(clave));
//Se calcula el HMAC de los parámetros en Base64 con la clave de operación
var signatureDES64 = base64Decode(signatureDES);
var signatureHMAC  = CryptoJS.HmacSHA256(encodedParameters, signatureDES64);
//Se pasa a Base64
var dsSignature = CryptoJS.enc.Base64.stringify(signatureHMAC);

theRoot.setVar("DS_MP_64", encodedParameters);
theRoot.setVar("DS_SIGNATURE", dsSignature);

Lo que he hecho para poder importarlo ha sido copiar el código de la propia url y pegarlo en un archivo en Velneo

https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js

Pero esta versión del código .min, por alguna razón no compila y tampoco sé como resolver el error


2023-03-12_12h20_25

Lo he intentado entonces con el código que hay aquí
https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.js
También da error de compilación

Pero quitando el urlsafe = true y dejando solo urlsafe, me deja compilar.

Sin embargo, a la hora de ejecutar el código veo resultados dierentes entre el que me han enviado de ejemplo y el que estoy intentando ejecutar en velneo

Mientras que esta función de codificar a Base64 funciona bien y me devuelve el resultado esperado

2023-03-12_12h28_09

Esta otra de decodificar de base64 no devuelve el mismo resultado que en el ejemplo que me han pasado

2023-03-12_12h28_50

Resultado del html de ejemplo

Resultado en Velno

Este dato erróneo me condiciona el valor del resto de los datos cifrados y por tanto la pasarela de pago me devuelve error.

He conseguido conectarme a la pasarela de pago de esta forma

Pero ni me gusta la idea de que haya datos accesibles desde el html, ni me sirve después para desencriptar los datos de respuesta, ya que me los envía a la url que estoy indicando en el JSON.

¿Podría afectar que no sea la versión . min del archivo? ¿Porque no compila en Velneo este código de Javascript?
¿Es posible que haya otra forma de hacer esta encriptación?
¿Alguien que haya hecho algo parecido?

Gracias por leer hasta el final
Espero que puedan ayudarme

Que tal Informatica97, nosotros hemos trabajado con coinpayments, te puedo decir que ha sido la mejor opcion que hemos encontrado, por nustra parte ya tenemos integrada una pasarela de pagos con dicho proveedor. Nosotros nos encontramos en Mexico, aunque ya anteriormente hemos trabajado con personas de España. Si gustas te dejo mi contacto para ponernos de acuerdo y poder apoyarte en tu proyecto.

Gabriel Gutierrez Hermoso
g.gutierrez@cloudglp.com

Hola, algo interesante:
Por lo que veo es posible que el problema con el proceso normal sea que no esté cargando correctamente la biblioteca CryptoJS en el ámbito local, lo que impide que se acceda a ella desde el proceso. En cambio, al utilizar la función script (File Script) , se está cargando el archivo JavaScript que contiene la biblioteca en el ámbito global de la aplicación, lo que permite acceder a ella desde cualquier parte del código, incluyendo el script dinámico que se está ejecutando.
Al igual que tu caso, no me funcionaba, así que empecé a insertar variables globlales en memoria (con los pro y contra) y ejecutar el script con script (File Script). El resultado es que me devuelve la respuesta, lo más importante, usando la bilbioteca oficial de crypto.js.

Un cordial saludo.
Fernando

1 me gusta

Hola
No acabo de entender el enfoque de meter variables globales.
¿Podrías darme más detalle?

Gracias

Hola;

Perdón no me he explicado bien.

He tratado de encriptar variables de texto en un contexto local (mismo formulario, pasándole variables a un proceso) y no lo he podido lograr (me sale error).

Sin embargo, pasando variables globales en memoria al mismo proceso y ejecutando el comando de instrucción de proceso “ejecutar script” (filescript), si he logrado encriptar con la librería crypto.js.

Ejemplo:

Paso1. Despues de crear la librería crypto.js dentro el proyecto de datos, creo otro archivo llamado proceso.js que tiene este código:

#include “(CurrentProject)/js/crypto/crypto.js”

// El texto de quiero encriptar esta en una variable global llamada CLAVE
var password = CryptoJS.SHA256(theApp.globalVarToString(“proyecto_dat/CLAVE”)).toString();

//Asigno la variable password a otra variable global llamada CLAVE_GLOBAL_ENCRIPTADA
theApp.setGlobalVar(“proyecto_dat/CLAVE_GLOBAL_ENCRIPTADA”, password).

Paso 2. Ejecuto el proceso desde mi proyecto de aplicación con el comando de instrucción Ejecutar script (filescript) (proyecto_dat/…/proceso.js)
alert (theApp.globalVarToString(CLAVE_GLOBAL_ENCRIPTADA));

Obviamente, esto es solo un ejemplo, hay que trasladar al contexto que uno quiere.

Un cordial saludo.
Fernando

1 me gusta

Hola @informatica97
No se si has resuelto ya el problema, pero te comento que a mi me funciona muy bien CryptoJS.
Tienes que incluir en tu proyecto Velneo los ficheros CryptoJS.js, core-min.js y aes.js
Aparte de eso yo me he creado dos ficheros, uno para desencriptar como este:

#include “(CurrentProject)/js/CryptoJS/rollups/aes.js”
#include “(CurrentProject)/js/CryptoJS/components/core-min.js”

var encrypted = theRoot.varToString(“PWD”);
var decrypted = CryptoJS.AES.decrypt(encrypted, theRoot.varToString(“KEY”)).toString(CryptoJS.enc.Utf8);
theRoot.setVar(“RET”, decrypted);

y otro para encriptar como este:

#include “(CurrentProject)/js/CryptoJS/rollups/aes.js”
#include “(CurrentProject)/js/CryptoJS/components/core-min.js”

var encrypted = CryptoJS.AES.encrypt(theRoot.varToString(“PWD”), theRoot.varToString(“KEY”));
theRoot.setVar(“RET”, encrypted.toString(CryptoJS.enc.Alpha256));

Obviamente ambos ficheros están asociados a procesos Velneo JavaScript a los que les paso el dato a encriptar-desecriptar (en este caso un password) y la key compartida para encriptar-desencriptar y luego obtengo el retorno en una variable.
Espero que te sirva.