Firma de los requerimientos a la API (v2.0)

Firmar requerimientos

El proceso de firmar un requerimiento es algo muy sencillo. Consiste en enviar un encabezado HTTP con un valor que se calcula al momento de hacer la llamada.

Firmar las llamadas a la API permite a khipu que la llamada efectivamente viene de un servidor del comercio. Para esto se usa el valor “secret” de la cuenta de cobro. Recuerda este valor no debe ser compartido con nadie.

El encabezado de la firma

Para firmar una llamada a la API debemos enviar el siguiente encabezado HTTP:

Authorization: receiverId:hash

El valor de “Authorization” está formado por dos valores:

  • receiverId: Es tu identificador de comercio. Es parte de las credenciales.
  • hash: Es un valor que se calcula usando el “secret”, el tipo de requerimiento (POST, GET, etc), la URL del requerimiento y los valores que envías al servidor.

Cuando se envía un requerimiento a la API khipu usa el “secret” del comercio para calcular el valor de “hash”. Si el valor calculado coincide con el valor que se envía en el encabezado, el requimiento es aceptado.

Es importante notar que aunque el requirimiento sea aceptado no necesariamente funcionará pues los valores enviados podrían tener problemas, como por ejemplo un monto negativo.

Calculo del HASH

El proceso de generar consiste en varios pasos sencillos. Básicamente se debe construir un “String” usando el tipo de llamado (POST, GET, etc), la url de la llamada, los parámetros que se enviarán y sus valores.

El siguiente es un código de referencia de como construir la firma para un llamado “POST” al la URL “https://khipu.com/api/2.0/payments”, usando 3 parámetros para crear un pago.

$receiver_id = identificador_de_comercio;
$secret = 'secret-key';
$method = 'POST';
$url = 'https://khipu.com/api/2.0/payments';

$params = array('subject' => 'ejemplo de compra'
, 'amount' => '1000'
, 'currency' => 'CLP'
);

$keys = array_keys($params);
sort($keys);

$toSign = "$method&" . rawurlencode($url);
foreach ($keys as $key) {
        $toSign .= "&" . rawurlencode($key) . "=" . rawurlencode($params[$key]);
}
$hash = hash_hmac('sha256', $toSign , $secret);
$value = "$receiver_id:$hash";
print "$value\n";
Integer receiverId = identificadorDeComercio;
String secret = "secret-key";
String method = "POST";
String url = "https://khipu.com/api/2.0/payments";
String toSign = method.toUpperCase() + "&" + percentEncode(url);

HashMap map = new HashMap();
map.put("subject", "ejemplo de compra");
map.put("amount", "1000");
map.put("currency", "CLP");

List keys = new LinkedList(map.keySet());
Collections.sort(keys);

for (String key : keys) {
    toSign += "&" + percentEncode(key) + "=" + percentEncode(map.get(key));
}

String sign = hmacSHA256(secret, toSign);
String value = receiverId + ":" + sign;
System.out.println(value);

Primero debemos tener todas las variables involucradas: receiverId, secret, method, url y params (los parámetros que enviaremos al servidor).

Veamos los pasos uno a auno:

  • Debemos carar un string “toSign” que contiene el método (POST).
  • Agregamos URL al string separados por “&“. URL debe estar codificado usando percent encode. En PHP la función “rawurlencode” es el equivalente.
  • Obtenemos un listado de todos los nombres de los parámetros ordenados de manera lexicográfica.
  • Recorremos el listado de nombres y por cada uno agregarmos un “&“, el nombre, el signo “=” y el valor. Es muy importante notar que tanto el nombre como el valor deben ir codificados usando percent encode.
  • Usando el string obtenido y nuestro secret, aplicamos la función HMAC-SHA256. El resultado que se obtiene es la variable hash. En PHP se usa la función “hash_hmac”.
  • Ahora creamos el valor del header concatenando el “receiverId”, un “:” y el valor hash.

El último valor que obtenemos es el que debemos enviar en el encabezado “Authorization” de la llamada HTTP.

Cálculo de HMAC-SHA256

En PHP el cáculo de HMAC es sencillo pues se usa la función nativa “hash_hmac”. En Java debemos crear nuestra propia función para generar el Hash. El siguiente es un ejemplo de implementación:

public static String hmacSHA256(String secret, String data) {
    try {
        if (secret == null || data == null) {
            return "";
        }
        SecretKeySpec secretKeySpec = new SecretKeySpec(secret.getBytes("UTF-8"), "HmacSHA256");
        Mac mac = Mac.getInstance("HmacSHA256");
        mac.init(secretKeySpec);
        byte[] digest = mac.doFinal(data.getBytes("UTF-8"));
        return byteArrayToString(digest);
    } catch (InvalidKeyException ignored) {
        throw new RuntimeException("Invalid key exception while converting to HMac SHA256");
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    } catch (UnsupportedEncodingException e) {
        e.printStackTrace();
    }
    return null;
}

private static String byteArrayToString(byte[] data) {
    BigInteger bigInteger = new BigInteger(1, data);
    String hash = bigInteger.toString(16);
    while (hash.length() < 64) {
        hash = "0" + hash;
    }
    return hash;
}

Percent Encode

PHP provee una función nativa para hacer percentEncode llamada “rawurlencode”. La siguiente es una implementación de referencia para Java:

public static String percentEncode(String string) {
    if (string == null) {
        return "";
    }
    try {
        return URLEncoder.encode(string, "UTF-8")
                .replace("+", "%20")
                .replace("*", "%2A")
                .replace("%7E", "~");
    } catch (UnsupportedEncodingException exception) {
        throw new RuntimeException(exception.getMessage(), exception);
    }
}