Validator - API Keys y Firmas

Cómo manejamos la seguridad en el validador

Validator: API Keys

Los API Keys de Validator son diferentes que tus API Keys de otros productos de Shinkansen.

Validator: Firmas

Las peticiones de validación que envías a Shinkansen están protegidas por API Keys y por el transporte TLS. No debes firmarlas.

Las respuestas a las validaciones son enviadas a un webhook de tu aplicación con una cabecera Shinkansen-Validator-Signature que contiene una firma HMAC del contenido de la respuesta. Para verificar esa firma debes:

  • Generar el secreto compartido en tu dashboard: en Configuración > API key, en la tarjeta Validator, presiona Rotar shared secret (o Rotate shared secret si usas el dashboard en inglés). Necesitas un perfil que pueda administrar la configuración de la organización (ver Roles y permisos del dashboard). El secreto solo se muestra al generarlo, así que guárdalo en ese momento. Cada ambiente (producción y modo test) tiene su propio secreto, y si lo rotas el anterior deja de servir.

  • Tener almacenado de manera segura el secreto compartido de Shinkansen para el producto Validator.

  • Calcular la firma HMAC SHA256 del contenido de la respuesta usando el algoritmo SHA-256 y el secreto compartido. Es muy importante que calcules la firma HMAC desde el contenido_raw_ del body HTTP. Si parseas el body como JSON y luego lo vuelves a convertir en string, el parser puede cambiar el orden de los campos y la firma no va a coincidir.

  • Llevar el valor de la firma HMAC a hexadecimal (base 16).

  • Comparar el resultado con el valor enviado en la cabecera Shinkansen-Validator-Signature (normalizando a mayúsculas o minúsculas).

Sin secreto compartido no hay webhook

Si tu organización no ha generado el secreto compartido de Validator, Shinkansen no envía el resultado de las validaciones a tu webhook. Mientras no lo generes, solo podrás obtener el resultado consultando la validación (GET /v1/validation/requests/{id}).

Ejemplos de código para validar HMAC 256 en formato hexadecimal

Via https://github.com/danharper/hmac-examples/

var crypto = require("crypto");

var key = "the shared secret key here";
var message = "the message to hash here";

var hash = crypto.createHmac("sha256", key).update(message);

// to lowercase hexits
hash.digest("hex");
const key = "the shared secret key here";
const message = "the message to hash here";

const getUtf8Bytes = (str) =>
  new Uint8Array(
    [...unescape(encodeURIComponent(str))].map((c) => c.charCodeAt(0)),
  );

const keyBytes = getUtf8Bytes(key);
const messageBytes = getUtf8Bytes(message);

const cryptoKey = await crypto.subtle.importKey(
  "raw",
  keyBytes,
  { name: "HMAC", hash: "SHA-256" },
  true,
  ["sign"],
);
const sig = await crypto.subtle.sign("HMAC", cryptoKey, messageBytes);

// to lowercase hexits
[...new Uint8Array(sig)].map((b) => b.toString(16).padStart(2, "0")).join("");
require 'openssl'
require 'base64'

key = 'the shared secret key here'
message = 'the message to hash here'

# to lowercase hexits
OpenSSL::HMAC.hexdigest('sha256', key, message)

import hashlib
import hmac
import base64

message = bytes('the message to hash here', 'utf-8')
secret = bytes('the shared secret key here', 'utf-8')

hash = hmac.new(secret, message, hashlib.sha256)

# to lowercase hexits
hash.hexdigest()
using System;
using System.Security.Cryptography;
using System.Text;

class MainClass {
  public static void Main (string[] args) {
    string key = "the shared secret key here";
    string message = "the message to hash here";

    byte[] keyByte = new ASCIIEncoding().GetBytes(key);
    byte[] messageBytes = new ASCIIEncoding().GetBytes(message);

    byte[] hashmessage = new HMACSHA256(keyByte).ComputeHash(messageBytes);

    // to lowercase hexits
    String.Concat(Array.ConvertAll(hashmessage, x => x.ToString("x2")));
  }
}
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.NoSuchAlgorithmException;
import java.security.InvalidKeyException;
import javax.xml.bind.DatatypeConverter;

class Main {
  public static void main(String[] args) {
  	try {
	    String key = "the shared secret key here";
	    String message = "the message to hash here";

	    Mac hasher = Mac.getInstance("HmacSHA256");
	    hasher.init(new SecretKeySpec(key.getBytes(), "HmacSHA256"));

	    byte[] hash = hasher.doFinal(message.getBytes());

	    // to lowercase hexits
	    DatatypeConverter.printHexBinary(hash);
  	}
  	catch (NoSuchAlgorithmException e) {}
  	catch (InvalidKeyException e) {}
  }
}
<?php

$key = 'the shared secret key here';
$message = 'the message to hash here';

// to lowercase hexits
hash_hmac('sha256', $message, $key);

Docs detallados de APIs relacionadas