¿Qué es un HMAC?
Un HMAC (Hash-based Message Authentication Code) es una firma que se calcula con un mensaje y una clave secreta, usando por dentro una función hash como SHA-256. Quien tenga la misma clave puede volver a calcularla y comprobar dos cosas a la vez: que el mensaje no se ha modificado por el camino y que lo ha enviado alguien que conoce la clave.
Se diferencia de un hash normal en la clave: el SHA-256 de un mensaje lo puede calcular cualquiera, pero su HMAC-SHA256 solo quien tenga la clave. Y se diferencia de una firma digital (RSA, ECDSA) en que la clave es la misma para firmar y para comprobar, así que solo sirve entre dos partes que confían entre sí.
Comprobar la firma de un webhook
Cuando un servicio te avisa de algo enviando una petición a tu web (un webhook), firma el cuerpo con un HMAC y una clave que compartís, para que puedas rechazar las peticiones falsas. Cada uno lo hace a su manera:
| Servicio | Cabecera | Qué se firma | Formato |
|---|---|---|---|
| GitHub | X-Hub-Signature-256 | El cuerpo de la petición | sha256= y la firma en hex |
| Stripe | Stripe-Signature | El valor t, un punto y el cuerpo: t.cuerpo | Hex, en v1= |
| Shopify | X-Shopify-Hmac-Sha256 | El cuerpo de la petición | Base64 |
| Slack | X-Slack-Signature | v0:, la marca de tiempo, dos puntos y el cuerpo | v0= y la firma en hex |
Todos usan HMAC-SHA256. Para probar a mano, copia el cuerpo exacto de la petición (sin formatearlo ni añadirle un salto de línea al final), pega tu clave y compara la firma con la de la cabecera. Si no cuadra, casi siempre es porque el cuerpo no es idéntico byte a byte al que se firmó.
Consejos para tu código
- Calcula la firma sobre el cuerpo tal cual llega, antes de decodificar el JSON: al volver a convertirlo en texto cambian los espacios o el orden y la firma deja de coincidir.
- Compara las firmas con una función de tiempo constante, como
hash_equals()en PHP ocrypto.timingSafeEqual()en Node.js, y no con==. - Si el servicio envía una marca de tiempo, rechaza las peticiones antiguas para que nadie pueda reenviar una petición válida capturada.
- Guarda la clave como un secreto, en variables de entorno, nunca en el código ni en el repositorio.
Los tokens JWT firmados con HS256 también usan HMAC-SHA256, con la firma en Base64url. Puedes comprobarlos con el decodificador de JWT.