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:

  • 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).

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);