Mira el video de la lección: Asegurando Agentes de IA con Recibos Criptográficos
(El video de la lección y la miniatura serán añadidos por el equipo de contenido de Microsoft después de la fusión, siguiendo el patrón de la lección 14 / 15.)
Esta lección cubrirá:
Después de completar esta lección, sabrás cómo:
Imagina que has desplegado un agente de IA para Contoso Travel. El agente lee solicitudes de clientes, llama a una API de vuelos para buscar opciones y reserva asientos en nombre del cliente. El último trimestre, el agente procesó 50,000 reservas.
Hoy llega un auditor. Hace una pregunta simple: “Muéstrame lo que hizo tu agente.”
Entregas tus archivos de registro. El auditor los revisa y hace una pregunta más difícil: “¿Cómo sé que estos registros no fueron editados?”
Este es el problema de la pista de auditoría. La mayoría de los despliegues de agentes hoy dependen de:
Ninguno de estos puede responder a la pregunta del auditor sin requerir que confíe en alguien (tú, tu proveedor de nube, el proveedor de tu base de datos). Para uso interno, esa confianza suele ser aceptable. Para cargas reguladas (finanzas, salud, cualquier cosa sujeta al Acta de IA de la UE), no lo es.
Los recibos criptográficos resuelven esto haciendo que cada acción del agente sea verificable de manera independiente. El auditor no necesita confiar en ti. Solo necesita tu clave pública y el recibo mismo.
Un recibo es un objeto JSON que registra lo que hizo un agente, firmado con una firma digital.
flowchart LR
A[El agente invoca una herramienta] --> B[Construir la carga útil del recibo]
B --> C[Canonicalizar JSON RFC 8785]
C --> D[Hash SHA-256]
D --> E[Firmar Ed25519]
E --> F[Recibo con firma]
F --> G[Auditor verifica sin conexión]
G --> H{¿Firma válida?}
H -- yes --> I[Prueba a prueba de manipulaciones]
H -- no --> J[Recibo rechazado]
Un recibo mínimo se ve así:
{
"type": "agent.tool_call.v1",
"agent_id": "contoso-travel-bot",
"tool_name": "lookup_flights",
"tool_args_hash": "sha256:a3f9c1...",
"result_hash": "sha256:7b2e1d...",
"policy_id": "contoso-travel-policy-v3",
"timestamp": "2026-04-25T14:30:00Z",
"sequence": 47,
"previous_receipt_hash": "sha256:9d4e6a...",
"signature": {
"alg": "EdDSA",
"sig": "c5af83...",
"public_key": "8f3b2c..."
}
}
Tres propiedades hacen el trabajo:
La firma. El recibo está firmado por el gateway del agente usando una clave privada Ed25519. Cualquiera con la clave pública correspondiente puede verificar la firma sin conexión. Manipular cualquier campo invalida la firma.
Codificación canónica. Antes de firmar, el recibo se serializa usando el Esquema de Canonicalización JSON (JCS, RFC 8785). Esto asegura que dos implementaciones que producen el mismo recibo lógico produzcan una salida idéntica byte a byte. Sin canonicalización, diferentes serializadores JSON producirían firmas diferentes para el mismo contenido.
Encadenamiento con hash. El campo previous_receipt_hash enlaza cada recibo con el anterior. Eliminar o reordenar un recibo rompe todos los recibos que vinieron después. La manipulación se vuelve visible a nivel de la cadena incluso si se evaden firmas individuales.
Juntas, estas propiedades proveen tres garantías:
No necesitas una biblioteca especial para producir un recibo. Las primitivas criptográficas están ampliamente disponibles y la lógica consta de unas pocas docenas de líneas en Python.
Los ejercicios prácticos en code_samples/18-signed-receipts.ipynb recorren el flujo completo. La versión resumida:
import json
import hashlib
import base64
from nacl import signing
from jcs import canonicalize # JSON canónico RFC 8785
def b64url_nopad(data: bytes) -> str:
return base64.urlsafe_b64encode(data).decode("ascii").rstrip("=")
def sha256_canonical(obj) -> str:
"""SHA-256 of a Python object's JCS-canonical JSON form."""
return f"sha256:{hashlib.sha256(canonicalize(obj)).hexdigest()}"
# Generar o cargar una clave de firma (en producción, almacenar en un almacén de claves)
signing_key = signing.SigningKey.generate()
verify_key = signing_key.verify_key
# Construir la carga útil del recibo (aún sin firma)
tool_args = {"origin": "SYD", "destination": "LAX"}
tool_result = [{"flight": "QF11", "price": 1850, "stops": 0}]
payload = {
"type": "agent.tool_call.v1",
"agent_id": "contoso-travel-bot",
"tool_name": "lookup_flights",
"tool_args_hash": sha256_canonical(tool_args),
"result_hash": sha256_canonical(tool_result),
"policy_id": "contoso-travel-policy-v3",
"timestamp": "2026-04-25T14:30:00Z",
"sequence": 0,
"previous_receipt_hash": None,
}
# Canonicalizar, hashear, firmar.
canonical_bytes = canonicalize(payload)
message_hash = hashlib.sha256(canonical_bytes).digest()
signature_bytes = signing_key.sign(message_hash).signature
# Adjuntar un objeto de firma estructurado.
receipt = {
**payload,
"signature": {
"alg": "EdDSA",
"sig": b64url_nopad(signature_bytes),
"public_key": b64url_nopad(bytes(verify_key)),
},
}
Esa es toda la cadena de firmado. Los ejercicios en el notebook recorren cada paso.
La verificación es la operación inversa:
import base64
import hashlib
from nacl import signing
from nacl.exceptions import BadSignatureError
from jcs import canonicalize
def b64url_decode(s: str) -> bytes:
padding = "=" * ((4 - len(s) % 4) % 4)
return base64.urlsafe_b64decode(s + padding)
def verify_receipt(receipt: dict) -> bool:
# La firma es un objeto estructurado: {"alg", "sig", "public_key"}.
sig_obj = receipt.get("signature")
if not sig_obj or sig_obj.get("alg") != "EdDSA":
return False
# Reconstruir la carga útil que fue realmente firmada (todo excepto la firma).
payload = {k: v for k, v in receipt.items() if k != "signature"}
canonical_bytes = canonicalize(payload)
message_hash = hashlib.sha256(canonical_bytes).digest()
try:
verify_key = signing.VerifyKey(b64url_decode(sig_obj["public_key"]))
verify_key.verify(message_hash, b64url_decode(sig_obj["sig"]))
return True
except BadSignatureError:
return False
Esta función toma un recibo y devuelve True si la firma es válida, False en caso contrario. Sin llamadas de red, sin dependencia en servicios, sin necesidad de confianza en terceros.
Para ver la detección de manipulaciones en acción, el notebook recorre:
tool_args_hash.Esta es la demostración práctica de que los recibos son evidentes a manipulaciones: cualquier modificación, por pequeña que sea, rompe la firma.
Un solo recibo firmado protege una acción. Una cadena de recibos protege una secuencia.
flowchart LR
R0[Recibo 0<br/>génesis] --> R1[Recibo 1]
R1 --> R2[Recibo 2]
R2 --> R3[Recibo 3]
R1 -. previous_receipt_hash .-> R0
R2 -. previous_receipt_hash .-> R1
R3 -. previous_receipt_hash .-> R2
Cada recibo registra el hash del recibo anterior. Para eliminar el recibo 2 silenciosamente, un atacante necesitaría:
previous_receipt_hash del recibo 3 (rompe la firma del recibo 3), OSi la clave privada está en un almacén de claves hardware y publicas la clave pública con cada recibo, ninguno de estos ataques es factible sin detección.
El notebook recorre:
previous_receipt_hash de cada recibo coincida con el hash real del recibo anterior.Así es como produces una pista de auditoría que un auditor externo puede verificar sin confiar en ti.
Esta es la sección más importante de esta lección. Los recibos son poderosos pero su poder es limitado.
Los recibos prueban tres cosas:
Los recibos NO prueban:
policy_id fue efectivamente evaluada, o que habría permitido esta acción si se hubiera verificado. El recibo registra lo que se afirmó, no lo que se hizo cumplir.Este límite importa por dos razones:
Un error común es asumir que “tenemos recibos” significa “estamos gobernados.” No es así. Los recibos son una base. La gobernanza es el sistema que construyes encima.
El punto 3 arriba vale su propia sección: un recibo de acción dice “esta clave firmó este contenido,” nunca “un humano autorizó esto.” Para acciones de alto riesgo (reembolsos, eliminaciones, transferencias bancarias), los marcos de gobernanza requieren cada vez más esa declaración que falta, y es producible con las mismas primitivas que ya construiste en esta lección.
El notebook complementario code_samples/human-authorization-receipts.ipynb agrega un segundo tipo de recibo, human.approval.v1, con la misma forma de sobre que los recibos de la lección (una carga útil tipada firmada con Ed25519 sobre su SHA-256 canónico, con el objeto signature fuera de los bytes firmados). Un aprobador nombrado firma la acción canónica completa y su resumen antes de la ejecución; el recibo de acción del agente lleva el mismo resumen de acción y un parent_approval_ref, el receipt_hash de la aprobación, la misma convención que previous_receipt_hash en la cadena que construiste arriba. Una llamada verify_chain recorre ambos artefactos bajo registros de claves fijados separados (claves de aprobadores vs claves de agentes), de modo que la ruta de código se comparte, pero las autoridades nunca.
La propiedad que esto garantiza, expresada cuidadosamente: el humano aprobó esta acción exacta, y el agente ejecutó exactamente esa acción aprobada. Las pruebas de rechazo en el notebook son lo que hace que la propiedad sea real y no solo una afirmación:
Cada falla rechaza con una razón distinta, así un auditor que lee un rechazo puede saber si la autoridad caducó o si la acción ejecutada cambió. La regla que enseña el notebook: una aprobación firmada no es autoridad por sí misma. La autoridad existe solo si ambos recibos aún están vinculados a la misma acción canónica al momento de la ejecución. El camino de co-firma en el mismo Borrador de Internet en que esta lección se basa (draft-farley-acta-signed-receipts) es la forma de estándar para este patrón.
El código Python en esta lección es intencionalmente mínimo para que puedas leer cada línea y entender exactamente qué está pasando. En producción, tienes dos opciones:
Construir directamente sobre las primitivas criptográficas. Las 50 líneas que viste arriba son suficientes para muchos casos de uso. PyNaCl (Ed25519) y el paquete jcs (JSON canónico) son bibliotecas bien mantenidas y auditadas.
Usar una biblioteca de recibos para producción. Varios proyectos de código abierto implementan el mismo patrón con características adicionales (rotación de claves, verificación por lotes, distribución del conjunto JWK, integración con motores de políticas):
draft-farley-acta-signed-receipts, revisión 02) actualmente en proceso estándar, con una suite de conformidad compartida (agent-governance-testvectors) con la que implementaciones independientes se verifican entre sí para producción canónica idéntica a nivel de bytes.protect-mcp (npm) y @veritasacta/verify (npm) proporcionan una implementación en Node de firmado de recibos y verificación offline, pensada para envolver cualquier servidor MCP con una pista de auditoría evidente a manipulaciones, incluyendo un flujo de co-firma retenida en la que una acción pausada emite un recibo de aprobación enlazado al resumen de la acción (con soporte WebAuthn en el flujo de escritorio), el mismo patrón de recibo de aprobación que el notebook de autorización humana arriba.pip install nobulex) provee el mismo patrón de firmado Ed25519 + JCS en Python con integraciones LangChain y CrewAI, incluyendo vectores de prueba de validación cruzada publicados y un mapeo de cumplimiento aportado vía OWASP PR #2210.La decisión entre implementar tú mismo o usar una biblioteca es similar a elegir entre escribir tu propia biblioteca JWT o usar una probada: ambas son razonables; la biblioteca ahorra tiempo y reduce la superficie de auditoría; el enfoque propio te obliga a entender cada primitiva. Esta lección enseña el camino propio para que tengas la base para cualquiera de las dos opciones.
Pon a prueba tu comprensión antes de pasar al ejercicio práctico.
1. Un recibo está firmado con la clave privada Ed25519 del agente. El auditor solo tiene la clave pública. ¿Puede el auditor verificar el recibo sin conexión?
2. Un atacante modifica el campo policy_id de un recibo para afirmar que estuvo gobernado por una política más permisiva. La firma fue sobre la carga útil original. ¿Qué sucede durante la verificación?
3. ¿Por qué el recibo incluye un tool_args_hash y result_hash en lugar de los argumentos y resultados en bruto?
4. El campo previous_receipt_hash enlaza cada recibo con su predecesor. Si un atacante elimina silenciosamente un recibo del medio de una cadena, ¿qué se vuelve inválido?
5. Un recibo se verifica correctamente. ¿Eso prueba que la acción del agente fue correcta, sólida o cumplió con la política?
Abre code_samples/18-signed-receipts.ipynb y completa las cuatro secciones:
Desafío adicional 1: extiende el esquema del recibo con un campo adicional de tu elección (por ejemplo, un ID de solicitud para rastreo), actualiza la lógica de firma canónica para incluirlo, y confirma que el recibo aún se puede verificar con éxito. Luego modifica el campo después de la firma y confirma que la verificación falla. Esto te obliga a entender cómo cada byte de la codificación canónica contribuye a la firma.
Desafío adicional 2: Calcula el hash SHA-256 de dos de tus recibos juntos (concatenando sus bytes canónicos en un orden determinista) y embebe el resumen resultante como un nuevo campo en un tercer recibo antes de firmarlo. Verifica que los tres recibos aún se pueden verificar. Acabas de construir una prueba de inclusión de un paso: cualquiera que tenga el tercer recibo puede probar que los dos primeros existían al momento de firmarlo, sin necesidad de revelar su contenido. Este es el patrón que los recibos de divulgación selectiva usan a gran escala (compromisos Merkle, RFC 6962).
Los recibos criptográficos ofrecen a los agentes de IA una pista de auditoría que es:
No son un sustituto para la validación de entradas, la aplicación de políticas o la infraestructura de identidad. Son una base para esas capas. Cuando despliegas agentes en cargas de trabajo reguladas, flujos de trabajo multi-organización, o cualquier entorno donde un auditor futuro no pueda asumir que confía en ti, los recibos son cómo haces que la pista de auditoría sea honesta.
La lección más importante: los recibos prueban quién dijo qué y cuándo. No prueban que lo dicho sea verdad o correcto. Mantén esa distinción con claridad. Es la diferencia entre un sistema de procedencia honesto y uno engañoso.
Cuando estés listo para avanzar desde esta lección a desplegar agentes con recibos firmados en un entorno real:
https://your-org.example.com/.well-known/agent-keys.json.Únete al Microsoft Foundry Discord para encontrarte con otros estudiantes, asistir a horas de oficina y obtener respuestas a tus preguntas sobre Agentes de IA.
Esta lección cubre la firma de un solo recibo y secuencias encadenadas por hash. Las mismas primitivas componen varios patrones más avanzados que puedes encontrar a medida que madura tu postura de gobernanza:
authorization_*) y post-ejecución (result_*) con firmas independientes, útil cuando la decisión de autorización y el resultado observado son producidos por actores diferentes o en tiempos distintos. Esto se suma al formato de recibo enseñado en esta lección.result_hash. Las cargas reales a menudo son más ricas que el resultado de una llamada a herramienta: razonamiento pre-decisional (predicción del modelo, opciones consideradas, evidencia y su completitud, postura de riesgo, cadena de responsabilidad, resultado de la puerta) puede vivir dentro de la carga, sellado por un único recibo. Esto mantiene el formato del recibo mínimo mientras permite que los esquemas de carga evolucionen por dominio.signature.alg puede llevar ML-DSA-65 (el estándar de firma post-cuántico NIST) cuando necesites migrar. Planifica un período de transición donde los recibos estén firmados doblemente.Descargo de responsabilidad: Este documento ha sido traducido utilizando el servicio de traducción automática Co-op Translator. Aunque nos esforzamos por la precisión, tenga en cuenta que las traducciones automatizadas pueden contener errores o inexactitudes. El documento original en su idioma nativo debe considerarse la fuente autorizada. Para información crítica, se recomienda una traducción profesional humana. No somos responsables de cualquier malentendido o interpretación errónea que surja del uso de esta traducción.