Tus paneles están verdes. Tu cola de correo está vacía. Y aun así Gmail (o Microsoft, o el gateway de un socio que sigue pensando que es 2009)
dice: “DKIM body hash did not verify”. El mensaje llega. El destinatario puede leerlo. Pero la firma falla,
la alineación DMARC se viene abajo y de repente tu gráfico de entregabilidad parece una pista de esquí.
Lo más frustrante: “¿DKIM no es solo una cabecera, verdad?” No. DKIM es una apuesta a que nadie tocará el mensaje entre la firma y la verificación.
En el mundo real, todo el mundo lo toca—MTAs, gateways de seguridad, archivadores, pies de página, proxies “útiles” y ese proveedor que jura que solo añade
un píxel de seguimiento “en las cabeceras”.
Qué significa realmente “body hash mismatch”
Una firma DKIM incluye una etiqueta llamada bh= (hash del cuerpo). El firmante canonicaliza el cuerpo (según la cabecera DKIM),
lo hashea, codifica ese hash en base64 y lo almacena en bh=. El receptor repite ese proceso sobre el cuerpo del mensaje recibido.
Si el hash calculado no coincide con bh=, obtienes un desajuste del hash del cuerpo.
Esto es distinto de una falla de firma de cabeceras. Los cambios en cabeceras también pueden romper DKIM, pero un desajuste del hash del cuerpo apunta directamente a:
los bytes del cuerpo—después de la canonicalización—no son idénticos a lo que vio el firmante.
La canonicalización es la trampilla
DKIM tiene dos modos de canonicalización para cabeceras y cuerpo: simple y relaxed. Muchas implementaciones usan
relaxed/relaxed porque tolera algunos cambios de espacios en cabeceras y algunas diferencias de espacios en el cuerpo.
“Tolerar algunos” hace mucho trabajo en esa frase.
- canonicalización del cuerpo simple: casi ninguna tolerancia. Cualquier cambio de byte (incluyendo finales de línea) lo rompe.
- canonicalización del cuerpo relaxed: ignora espacios al final de línea, colapsa secuencias WSP, normaliza finales de línea e ignora líneas vacías al final.
Pero incluso relaxed no te salvará si algo inserta un pie de página, recodifica quoted-printable, cambia una boundary MIME o “normaliza”
el ajuste de líneas dentro de un bloque base64. DKIM tolera espacios descuidados, no ediciones creativas.
Un modelo mental práctico: el hash del cuerpo DKIM es una suma de comprobación de “lo que lee un humano”, más un montón de cosas que un humano nunca ve—estructura MIME,
codificación de transferencia, boundarys y la colocación exacta de CRLF.
Datos e historia interesantes que explican las rarezas actuales
- DKIM no nació perfecto. Surgió de dos propuestas rivales (DomainKeys e Identified Internet Mail) que se combinaron para evitar una pelea de estándares.
- Los modos “relaxed” existen porque los MTAs seguían “formateando amablemente” el correo. Los estándares no suponían que un flujo de bytes intacto sobreviviría a la realidad.
- CRLF no es negociable en SMTP. Una cantidad sorprendente de sistemas todavía filtran LF simples en las canalizaciones, y la canonicalización DKIM es donde se juzga ese delito.
- Quoted-printable es una zona peligrosa para DKIM. Está diseñado para volver a ajustar líneas, y diferentes componentes pueden envolver a distintas columnas.
- Algunos productos de seguridad modifican intencionadamente mensajes. Añaden banners, reescriben URLs, inyectan disclaimers o detonan adjuntos. DKIM no “entiende” la intención.
- ARC existe porque el reenvío rompe DKIM. Cuando un reenviador modifica el correo, DKIM falla; ARC se introdujo para preservar resultados de autenticación a través de intermediarios.
- “l=” (longitud del cuerpo) es tanto una característica como una trampa. Puede prevenir roturas al ignorar contenido añadido, pero también permite que atacantes apenden contenido que no está firmado.
- Las listas de correo fueron una razón importante por la que la adopción de DKIM fue dolorosa. Las listas que añaden pies de página o reescriben asuntos invalidan firmas rutinariamente.
- DKIM se aplica por mensaje, no por conexión. Cualquier transformación en medio puede producir fallos por destinatario que parecen aleatorios.
Guía rápida de diagnóstico (orden de triage que ahorra horas)
Cuando ves un desajuste del hash del cuerpo, la tentación es mirar DNS y llaves. No lo hagas. Las llaves y DNS suelen romper la verificación por completo.
Body hash mismatch grita “el mensaje cambió”. Aquí está el orden que encuentra el cuello de botella con rapidez.
1) Confirma que realmente es un desajuste del cuerpo (no de cabeceras)
- Busca en logs del verificador menciones explícitas de
body hash did not verifyo desajuste debh=. - Si solo ves “bad signature”, necesitas más visibilidad: captura el mensaje y verifica localmente.
2) Identifica dónde se firmó el mensaje y dónde se verificó
- Busca la cabecera
DKIM-Signature: te indicad=(dominio),s=(selector),c=(canonicalización) y a vecesl=(longitud del cuerpo). - Traza las cabeceras
Received:: tu modificación probablemente ocurre entre dos saltos adyacentes.
3) Compara los cuerpos “pre-cambio” y “post-cambio”
- Obtén el mensaje tal como lo vio el firmante (o lo más cercano posible: en el MTA que firma). Obtén el mensaje tal como lo vio el receptor (o en tu último salto de salida).
- Haz un diff con herramientas que respeten CRLF y MIME.
4) Busca los modificadores habituales en la cadena
- Filtros de contenido (Amavis, Rspamd, gateways antivirus, DLP).
- Pies de página y disclaimers de cumplimiento de marca en salida.
- Reescritura de URLs y proxies de seguimiento de clics.
- Proxies SMTP que re-chunkean o re-ajustan líneas.
- Archivadores / sistemas de journaling que vuelven a inyectar correo.
5) Arregla el proceso, no el síntoma
- Asegura que la firma ocurra después de todas las modificaciones.
- O deja de modificar el correo firmado y firma en el último salto responsable.
- Si debes modificar después de firmar, acepta que DKIM fallará y usa ARC para confianza aguas abajo (cuando corresponda).
Las causas ocultas que nadie te cuenta (y cómo probar cada una)
1) Pies de página y disclaimers añadidos “tarde”
El clásico. Legal quiere un disclaimer. Marketing quiere un banner. Seguridad quiere una advertencia de “Correo externo”.
Si se inyecta después de la firma DKIM, el hash del cuerpo no coincidirá. Si se inyecta antes de la firma, estás bien—hasta que otro sistema inyecte otro.
Cómo probarlo: localiza el texto añadido en el cuerpo final y confirma que no estaba presente en el salto de firma. La pista es una línea de pie
que aparece después del último boundary MIME o solo en la parte text/plain.
2) Reajuste de quoted-printable
Quoted-printable (QP) codifica líneas largas con quiebres suaves (= al final de línea). Algunos gateways reenvuelven líneas a otra columna,
o “normalizan” QP convirtiendo espacios/tabulaciones o cambiando cómo rompen líneas largas.
DKIM firma los bytes del cuerpo codificado, no el contenido decodificado. Si la codificación QP cambia, el hash cambia.
Puedes tener el mismo texto renderizado y aun así fallar DKIM.
3) Cambios en boundary MIME u orden de partes
MIME es un documento estructurado. Si un gateway reordena partes, cambia boundarys o convierte un multipart/alternative en
otra estructura, DKIM se rompe. Esto puede suceder con filtros que “sanitizan HTML” o detonadores de adjuntos que reconstruyen el árbol MIME.
4) Cambios de Content-Transfer-Encoding (8bit ↔ quoted-printable ↔ base64)
Algunos MTAs degradan 8bit a quoted-printable cuando piensan que el siguiente salto no puede manejar 8bit (o cuando están configurados para ser conservadores).
Otros hacen lo contrario: detectan QP y la “limpian”. En cualquier caso, DKIM hace hash de lo que ve. Cambiar CTE cambia los bytes del cuerpo.
5) Normalización de finales de línea y la guerra CRLF/LF
SMTP usa CRLF. Pero aún encontrarás componentes que almacenan mensajes con LF y los reconstituyen después. Si lo hacen incorrectamente,
puedes terminar con finales mezclados o diferencias de canonicalización que pasen desapercibidas.
Con relaxed la mayoría de problemas de finales de línea se toleran. Con simple, juegas con fuego en un centro de datos.
6) Recorte “útil” de espacios en blanco dentro de partes MIME
Algunos filtros de contenido recortan espacios finales, eliminan “líneas en blanco extra” o normalizan tabs/espacios. La canonicalización relaxed ignora algo de WSP al final de línea,
pero no reformatos arbitrarios a lo largo del cuerpo, y no cambios en cabeceras MIME dentro de partes.
7) Proxies de chunking SMTP y casos límite de dot-stuffing
Normalmente, dot-stuffing es correcto y seguro para DKIM. Pero un proxy SMTP con bugs puede manejar mal líneas que empiezan con un punto, o convertir puntos iniciales incorrectamente al re-inyectar.
Es raro, pero cuando ocurre, la diferencia es diminuta y el dolor enorme.
8) Firmar la representación equivocada (orden de milters / pipeline de filtros)
Si firmas antes de que un filtro modifique el cuerpo, DKIM falla. Si firmas después, pasa. Muchos stacks firman accidentalmente en el punto equivocado:
un milter firma temprano, luego otro milter añade una cabecera, o un filtro de contenido posterior a la cola reescribe el cuerpo.
Este modo de falla aparece como “pasa para algunos destinatarios pero no para otros”, porque solo algunas rutas pasan por el componente modificador.
9) Reescritura de URLs para seguimiento de clics
Reescribir URLs cambia el cuerpo. Punto. Algunos proveedores dicen que lo hacen “sin tocar DKIM”. Lo que quieren decir es:
pueden preservar la DKIM de cabecera para mensajes que no reescriben, o añaden su propia firma DKIM después de reescribir.
Pero si esperas que tu DKIM sobreviva, no permitas que nadie reescriba el cuerpo después de que lo firmes.
10) Gestores de listas y reenviadores
Las listas de correo añaden pies de página, modifican cabeceras y a veces reformatean contenido. Los reenviadores pueden recodificar contenido o añadir cabeceras list-unsubscribe.
DKIM se rompe; DMARC se rompe; luego la gente culpa al DNS.
11) La etiqueta l=: “arreglos” que crean agujeros de seguridad
Verás consejos: “Simplemente pon l= para que las adiciones de pies de página no importen.” Esto indica a los verificadores que hagan hash solo de los primeros N bytes del cuerpo.
Sí, puede evitar desajustes del hash del cuerpo cuando se añade contenido al final.
También permite a un atacante añadir contenido después de la porción firmada—contenido que parece legítimo porque la firma DKIM sigue pasando.
Muchos receptores desconfían o ignoran l= por buenas razones. Úsalo solo cuando hayas pensado el modelo de riesgo y controles todo el camino.
12) Reconstitución de mensajes por sistemas de journaling/archivado
Algunos archivadores de cumplimiento capturan correo y luego lo vuelven a inyectar a otros sistemas (journaling, supervisión, escaneo aguas abajo).
Si reconstituyen el mensaje (incluso “sin pérdida”), se incrustan pequeñas diferencias: folding de cabeceras, regeneración de boundarys MIME, cambios de CTE.
Sigue el desajuste del hash del cuerpo.
Una idea parafraseada de Richard Cook (ingeniería de confiabilidad): El éxito oculta el riesgo; las fallas revelan el sistema real que en realidad operas.
Los desajustes del hash del cuerpo DKIM son la falla que revela tu canal de correo real.
Broma n.º 1: DKIM es como un sello a prueba de manipulaciones—genial hasta que tu propio equipo sigue “solo mirando qué hay dentro”.
Tareas prácticas: comandos, salidas y decisiones (12+)
Estas son las cosas que haces a las 02:10 cuando un VP dice “nuestras facturas van a spam” y no puedes permitirte un debate filosófico.
Cada tarea incluye un comando, salida de ejemplo, qué significa y la decisión que tomas.
Task 1: Extraer el mensaje crudo de la cola de Postfix (para no adivinar)
cr0x@server:~$ sudo postcat -q 3F2A91C02E
*** ENVELOPE RECORDS ***
message_size: 48231 704 1 0
message_arrival_time: Sat Jan 3 01:12:09 2026
sender: billing@example.com
*** MESSAGE CONTENTS ***
Received: from app01 (app01.internal [10.0.12.34])
by mx-out01.example.com (Postfix) with ESMTP id 3F2A91C02E
for <user@recipient.tld>; Sat, 3 Jan 2026 01:12:08 +0000 (UTC)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=example.com; s=mail;
h=from:to:subject:date:mime-version:content-type;
bh=VQeN9v5kG7WJQm...snip...=;
b=Q0Vn...snip...
Content-Type: multipart/alternative; boundary="=_b4c1a7f1d1"
...snip...
Qué significa: Ahora tienes el cuerpo exacto del mensaje tal como lo vio tu MTA de salida. Este es tu “input firmado”.
Decisión: Guarda esto en un archivo y verifica DKIM localmente. Si verifica aquí pero falla en el destinatario, algo cambia después de este salto.
Task 2: Guardar el mensaje en un archivo para pruebas repetibles
cr0x@server:~$ sudo postcat -q 3F2A91C02E > /tmp/msg.eml
cr0x@server:~$ ls -l /tmp/msg.eml
-rw-r--r-- 1 root root 48231 Jan 3 01:14 /tmp/msg.eml
Qué significa: Has congelado la evidencia.
Decisión: Usa el mismo archivo para probar OpenDKIM, analizar MIME y calcular hashes del cuerpo canonicalizado de forma consistente.
Task 3: Extraer la cabecera DKIM-Signature y anotar la canonicalización
cr0x@server:~$ grep -n '^DKIM-Signature:' -A2 /tmp/msg.eml
8:DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=example.com; s=mail;
9- h=from:to:subject:date:mime-version:content-type;
10- bh=VQeN9v5kG7WJQm...snip...=; b=Q0Vn...snip...
Qué significa: La canonicalización del cuerpo es relaxed. Eso reduce las causas probables: no es un simple problema CRLF, es más probable que haya ediciones del cuerpo o reescrituras de codificación.
Decisión: Concéntrate en filtros que modifiquen contenido, no solo en espacios en blanco.
Task 4: Verificar DKIM localmente con OpenDKIM (línea base)
cr0x@server:~$ opendkim-testmsg -d example.com -s mail -k /etc/opendkim/keys/example.com/mail.private < /tmp/msg.eml
opendkim-testmsg: signature ok
Qué significa: El mensaje tal como fue capturado coincide con lo que esperaba el firmante. Tu llave y configuración de firma están bien.
Decisión: Deja de tocar DNS. Empieza a buscar modificaciones aguas abajo después del salto de firma.
Task 5: Inspeccionar cabeceras Received para identificar la ventana de modificación
cr0x@server:~$ grep -n '^Received:' -n /tmp/msg.eml
1:Received: from app01 (app01.internal [10.0.12.34])
3:Received: by mx-out01.example.com (Postfix) with ESMTP id 3F2A91C02E
Qué significa: En este ejemplo la cadena es corta. En incidentes reales verás muchos saltos (relays internos, gateways de seguridad, MTAs en la nube).
Decisión: Identifica el último salto que controlas antes del destinatario. Captura allí también para comparar.
Task 6: Capturar tráfico SMTP en la interfaz de salida (si hace falta)
cr0x@server:~$ sudo tcpdump -i eth0 -s 0 -w /tmp/smtp.pcap 'tcp port 25 and host 203.0.113.55'
tcpdump: listening on eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
^C
124 packets captured
Qué significa: Has capturado los bytes reales en la wire hacia el siguiente salto. Es costoso y ruidoso; úsalo cuando los logs no basten.
Decisión: Reconstruye la porción DATA y compárala con tu .eml almacenado. Si difiere, algo entre la cola y la wire está reescribiendo.
Task 7: Verificar filtros after-queue en Postfix (culpables comunes)
cr0x@server:~$ postconf | egrep 'content_filter|smtp_tls_policy_maps|receive_override_options'
content_filter = smtp-amavis:[127.0.0.1]:10024
receive_override_options = no_address_mappings
Qué significa: Hay un content filter configurado. Amavis (o similar) suele recodificar MIME, especialmente alrededor de adjuntos y QP.
Decisión: Asegura que la firma DKIM ocurra después de este filtro, o configura el filtro para mínima reescritura.
Task 8: Comprobar modo de OpenDKIM y colocación del milter
cr0x@server:~$ grep -E '^(Mode|Socket|Canonicalization|OversignHeaders|SignHeaders)' /etc/opendkim.conf
Mode sv
Socket inet:8891@localhost
Canonicalization relaxed/relaxed
OversignHeaders From
SignHeaders From,To,Subject,Date,Message-ID,MIME-Version,Content-Type
Qué significa: OpenDKIM está firmando y verificando (sv) y usa relaxed/relaxed. OversignHeaders From es buena práctica.
Decisión: Confirma que este milter corre en la etapa de inyección final de salida, no en un salto interno anterior.
Task 9: Identificar si un gateway está inyectando banners/pies
cr0x@server:~$ grep -nE 'External Email|DISCLAIMER|This message originated' /tmp/msg.eml
cr0x@server:~$ echo $?
1
Qué significa: El mensaje en cola no contiene textos comunes de banner inyectados.
Decisión: Si el destinatario ve un banner, se está añadiendo después de tu captura de cola—probablemente en un gateway de seguridad saliente o en el lado del receptor.
Task 10: Revisar soft breaks de quoted-printable y comportamiento de ajuste de línea
cr0x@server:~$ grep -n '^Content-Transfer-Encoding:' -n /tmp/msg.eml | head
22:Content-Transfer-Encoding: quoted-printable
cr0x@server:~$ sed -n '30,60p' /tmp/msg.eml | sed -n '1,10p'
Dear customer,=0D=0A=0D=0APlease remit payment within 30 days.=0D=0A=
If you have questions, reply to this email.=0D=0A
Qué significa: El cuerpo del mensaje está codificado en QP; la presencia de quiebres = y =0D=0A explícitos lo hace frágil.
Decisión: Sospecha de cualquier gateway que decodifique/re-codifique QP, o de cualquier sistema que “normalice” la longitud de línea.
Task 11: Detectar CRLF vs LF (no confíes en tu editor)
cr0x@server:~$ python3 - < /tmp/msg.eml
import sys
data = sys.stdin.buffer.read()
print("CRLF count:", data.count(b"\r\n"))
print("LF count:", data.count(b"\n"))
print("Bare LF count:", data.count(b"\n") - data.count(b"\r\n"))
PY
CRLF count: 812
LF count: 812
Bare LF count: 0
Qué significa: Este archivo tiene CRLF limpio. Si una captura posterior muestra LF simples, encontraste un vector de mutación del cuerpo.
Decisión: Si aparecen LF simples después de un salto, arregla el relay/proxy culpable o asegúrate de firmar DKIM después de él.
Task 12: Extraer y comparar boundarys MIME (los cambios de boundary son prueba inequívoca)
cr0x@server:~$ grep -n '^Content-Type: multipart' -n /tmp/msg.eml
15:Content-Type: multipart/alternative; boundary="=_b4c1a7f1d1"
cr0x@server:~$ grep -n '^--=_b4c1a7f1d1' /tmp/msg.eml | head
27:--=_b4c1a7f1d1
88:--=_b4c1a7f1d1--
Qué significa: Conoces el token boundary esperado. Si una copia aguas abajo tiene un boundary distinto, algo reconstruyó el MIME.
Decisión: Trata a los reconstruidores MIME como hostiles a DKIM. O mueve la firma después de ellos o configúralos para que no reserialicen el contenido.
Task 13: Comprobar si el firmante usó la peligrosa etiqueta l=
cr0x@server:~$ grep -o ' l=[0-9]\+' -n /tmp/msg.eml
cr0x@server:~$ echo $?
1
Qué significa: No se usa límite de longitud del cuerpo. DKIM espera que todo el cuerpo se mantenga estable.
Decisión: Bueno para la seguridad; malo para canales con pies de página. Arregla el pipeline, no la firma, salvo que aceptes explícitamente el riesgo de l=.
Task 14: Comparar dos cuerpos de mensaje de dos saltos (diff que respete bytes)
cr0x@server:~$ python3 - << 'PY'
import email, sys
from email import policy
def body_bytes(path):
with open(path,'rb') as f:
msg = email.message_from_binary_file(f, policy=policy.default)
if msg.is_multipart():
# take full serialized body (post headers) for byte diff
raw = open(path,'rb').read()
return raw.split(b"\r\n\r\n",1)[1]
else:
raw = open(path,'rb').read()
return raw.split(b"\r\n\r\n",1)[1]
a = body_bytes("/tmp/msg.eml")
b = body_bytes("/tmp/msg-from-gateway.eml")
print("body lengths:", len(a), len(b))
print("first differing byte index:", next((i for i in range(min(len(a),len(b))) if a[i]!=b[i]), -1))
PY
body lengths: 41802 42110
first differing byte index: 21794
Qué significa: El cuerpo cambió entre saltos; incluso tienes la posición aproximada para inspeccionar.
Decisión: Abre ambos alrededor de ese índice, identifica contenido insertado/reescrito y relaciona eso con una función específica del gateway.
Broma n.º 2: El correo es el único sistema donde añadir un pie de página amable puede ser tratado como manipulación—porque lo es.
Tres microhistorias corporativas desde la trinchera
Microhistoria 1: El incidente causado por una suposición equivocada
Una compañía mediana ejecutaba una pila outbound Postfix con OpenDKIM limpia y aburrida. Su política DMARC era quarantine, y estaban orgullosos de ello.
Luego desplegaron un producto de “safe links” en la ruta de salida—comercializado como “transparente”.
La suposición equivocada fue simple: “Solo toca enlaces en HTML; DKIM es relaxed; estará bien.” El primer síntoma no fue una caída dramática.
Fue una hemorragia lenta: socios con DMARC estricto empezaron a rechazar órdenes de compra. Internamente, todo parecía entregado. SMTP 250 OK por todas partes.
El avance vino al comparar el mensaje en cola con el mensaje recibido por un buzón de prueba en otro dominio.
La parte HTML había reescrito enlaces, y la parte text/plain había ganado un parámetro de seguimiento en URLs sin formato. Ambas partes cambiaron, así que el desajuste del hash del cuerpo DKIM era seguro.
El producto también recodificó quoted-printable de forma distinta, así que incluso mensajes sin enlaces a veces rompían.
Arreglarlo requirió una decisión incómoda: o firmar después de la reescritura (lo que implica que el gateway de seguridad debe firmar DKIM como el dominio organizacional),
o dejar de reescribir correo saliente por completo. Eligieron firmar después de la reescritura moviendo la “firma DKIM final” al último salto y asegurando que el gateway tuviera
acceso a las claves del selector correcto. También añadieron monitorización para mutaciones inesperadas de contenido hasheando el cuerpo en múltiples saltos.
Microhistoria 2: La optimización que salió mal
Otra organización quiso reducir la CPU en sus MTAs. Alguien notó que su filtro de contenido decodificaba y recodificaba todo,
incluso cuando no se encontraba malware. La “optimización” fue habilitar un ajuste que “normalizaba” MIME y consolidaba codificaciones para mejor compresión.
Parecía brillante en micro-benchmarks: menor ancho de banda saliente, mensajes archivados más pequeños, menos codificaciones raras. Luego DKIM empezó a fallar solo para
ciertas clases de mensajes: facturas de una app y respuestas de soporte de otra. Porque esas apps usaban bibliotecas distintas, el MIME difería lo justo
para activar la ruta de reescritura del filtro a veces.
Los fallos fueron intermitentes por destinatario porque no todas las rutas de salida pasaban por el mismo clúster de filtros. Algunos destinatarios veían DKIM pasar. Otros veían
desajuste del hash del cuerpo. La org pasó una semana culpando a la propagación DNS y a la “caché del receptor”, porque esa es la historia que se cuenta cuando no se tiene un diff.
La solución fue revertir el ajuste “normalize” y cambiar la arquitectura: escanear y reescribir contenido antes de la firma DKIM, y tratar el salto de firma como sacrosanto.
Volvió la carga de CPU, pero la entregabilidad dejó de ser una tirada de dados.
Microhistoria 3: La práctica aburrida pero correcta que salvó el día
Un negocio regulado tenía una regla: “No modificar correo después de la firma. Nunca.” Era impopular. La gente quería banners, pies específicos por departamento
y cambios de marca de última hora. El equipo de correo seguía diciendo no como pasatiempo.
Permitían filtrado de contenido, pero solo en una canalización definida previa a la firma. Su relé de salida final hacía exactamente tres cosas:
aplicar política TLS, limitar tasa de clientes abusivos y firmar DKIM. Nada de inyección de banners. Nada de reescritura de URLs. Nada de re-inyección a archivos.
Durante un incidente del ecosistema de socios—varios receptores endureciendo DMARC y reglas de filtrado—esta org casi no sufrió interrupciones.
Mientras otros corrían a explicar desajustes del hash del cuerpo, ellos podían demostrar rápidamente que sus bytes de mensaje permanecían estables desde la firma hasta la entrega.
La práctica aburrida no era glamorosa; era higiene operativa.
El detalle que “salvó el día”: tenían un punto estándar de captura en el relé final que almacenaba el mensaje exacto post-firma por 48 horas para comparación forense.
Cuando un receptor afirmaba “tu DKIM falla”, podían probar si la cadena de entrada del receptor modificó el mensaje después de recibirlo,
o si la falla estaba en su propia cadena. La mayoría de disputas se resolvieron rápidamente. Las victorias silenciosas siguen siendo victorias.
Errores comunes: síntoma → causa raíz → solución
1) Síntoma: DKIM falla sólo al enviar a ciertos dominios socios
Causa raíz: Reescritura dependiente de ruta (relay de salida distinto, smart host, gateway TLS o appliance DLP para esos dominios).
Solución: Compara el mensaje capturado en el salto de firma final vs el observado después del gateway específico de la ruta. Estandariza el enrutamiento o mueve la firma después del último modificador.
2) Síntoma: DKIM falla solo en correos HTML, no en texto plano
Causa raíz: Sanitización HTML, seguimiento de clics o inyección de banners en la parte HTML que modifica una parte MIME.
Solución: Deshabilita la reescritura HTML en salida, o asegúrate de que el sistema que reescribe firme DKIM como el dominio organizacional después de la modificación.
3) Síntoma: Desajuste del hash del cuerpo aparece tras habilitar un nuevo antivirus o producto DLP
Causa raíz: El producto reserializa MIME o cambia Content-Transfer-Encoding incluso cuando “no encontró amenaza”.
Solución: Configura modo “pass-through” que preserve bytes, o coloca la firma DKIM después del producto, o cambia a un producto que soporte “no reescribir salvo que sea necesario”.
4) Síntoma: DKIM falla de forma intermitente para la misma plantilla de mensaje
Causa raíz: Reescritura no determinista (boundarys aleatorios, envoltura QP variable o diferencias por nodo).
Solución: Haz la serialización determinista: fija bibliotecas, deshabilita normalización y firma en el último salto. Además asegura que todos los nodos tengan configuraciones idénticas.
5) Síntoma: DKIM pasa en pruebas internas pero falla “en el mundo real”
Causa raíz: Tu ruta de prueba evita un componente outbound real (un conector en la nube, servicio de journaling, relay de socios o proxy saliente).
Solución: Prueba usando la misma ruta de salida que los destinatarios en producción. Captura en cada salto y haz diff.
6) Síntoma: Mensajes con adjuntos fallan DKIM más que otros
Causa raíz: Escaneo/detonación de adjuntos reconstruye MIME o cambia el envoltorio base64.
Solución: Mueve la firma después del procesamiento de adjuntos, o configura el escaneo para evitar recodificar cuando sea posible.
7) Síntoma: DKIM se rompe tras habilitar “añadir disclaimer a correo saliente” en Exchange o gateway
Causa raíz: El disclaimer se inserta después de la firma, o la firma ocurre en un salto distinto al que crees.
Solución: Reordena las reglas de transporte para que los disclaimers ocurran antes de la firma DKIM. Si no puedes, acepta que DKIM fallará y re-arquitecta.
8) Síntoma: La falla DKIM menciona “body canonicalization simple”
Causa raíz: Estás usando c=simple/simple o simple en la canonicalización del cuerpo y algo toca espacios/ finales de línea.
Solución: Usa relaxed/relaxed salvo que controles cada salto y puedas probar estabilidad byte a byte.
Listas de verificación / plan paso a paso
Checklist A: Primeros 30 minutos de un incidente por desajuste del hash del cuerpo DKIM
- Consigue un mensaje que falle con cabeceras completas y origen crudo (del receptor si es posible).
- Extrae el mismo mensaje de tu cola de salida o logs en el mismo periodo (postcat, copia de journaling o punto de captura).
- Verifica la copia en cola localmente usando herramientas OpenDKIM; registra si pasa o no.
- Extrae parámetros DKIM:
d=,s=,c=y sil=está presente. - Compara cuerpos bytewise y encuentra la primera diferencia.
- Identifica el salto donde ocurrió la modificación correlacionando cabeceras Received y reglas de enrutamiento.
- Decide: eliminar el modificador, mover la firma después de él, o aceptar la falla e implementar ARC/mecanismo alternativo de confianza.
Checklist B: Hacer que tu pipeline de salida sea seguro para DKIM (la arquitectura aburrida)
- Define un único relé de salida “last-mile” cuya tarea sea firmar y enviar, no “mejorar” contenido.
- Coloca filtrado de contenido, reescritura, disclaimers y branding aguas arriba de ese relé.
- Asegura que el relé last-mile no ejecute filtros after-queue que puedan reescribir contenido.
- Estandariza la canonicalización:
relaxed/relaxedpara la mayoría de organizaciones. - Mantén la generación MIME consistente entre aplicaciones (la elección de librería importa).
- Despliega un punto de captura en el relé last-mile para diffs forenses (retención temporal).
- Monitorea fallos DKIM mediante bucles de retroalimentación de receptores y muestreos de verificaciones en copias salientes.
Checklist C: Preguntas para revisar cambios de proveedores e internos
- ¿Este componente reescribe URLs, HTML o añade banners?
- ¿Decodifica y recodifica MIME? ¿Preserva Content-Transfer-Encoding?
- ¿Reconstruye boundarys MIME o modifica la estructura multipart?
- ¿Dónde en el flujo de correo ocurre la firma DKIM respecto a este cambio?
- ¿Podemos probar estabilidad byte a byte después de la firma con un diff?
- ¿Existe enrutamiento por destinatario que pueda causar comportamiento inconsistente?
Preguntas frecuentes
¿Por qué DKIM falla cuando el contenido del correo “parece igual”?
Porque DKIM firma bytes, no el renderizado de tu cliente. Cambiar el ajuste de quoted-printable, boundarys MIME o la codificación de transferencia puede preservar la apariencia pero cambiar los bytes.
¿La canonicalización relaxed es suficiente para evitar desajustes del hash del cuerpo?
Previene fallos por diferencias triviales de espacios. No protege contra inyección de contenido, reescritura de URLs, reestructuración MIME o recodificación.
¿Debería usar c=simple/simple por mayor seguridad?
Solo si controlas todo el trayecto y puedes demostrar que nada modifica el mensaje. Si no, es auto-sabotaje: cambiarás pureza teórica por fallos en el mundo real.
¿Puedo “arreglar” esto rotando llaves DKIM o cambiando DNS?
No si el error es un desajuste del hash del cuerpo. Problemas de llaves y DNS suelen producir “no key”, “bad key” o errores de verificación de firma no relacionados con bh.
Tu problema es la mutación del mensaje.
¿Qué tal firmar con l= para ignorar pies añadidos?
Puede reducir roturas cuando se añaden pies, pero debilita la integridad: atacantes pueden añadir contenido fuera de la porción firmada.
Muchos receptores tratan l= con escepticismo. Arregla primero el pipeline.
¿Por qué reenviar rompe DKIM tan a menudo?
Los reenviadores con frecuencia modifican mensajes (recodificando, añadiendo cabeceras de lista, reenvolviendo), y DKIM no sobrevive cambios. ARC se introdujo para ayudar a preservar autenticación a través de saltos.
¿Por qué falla solo en mensajes con adjuntos?
Los adjuntos activan escaneo y manejo de contenido que pueden reconstruir MIME o cambiar el envoltorio base64. Esos son cambios de cuerpo y invalidan bh.
¿Cómo decido dónde firmar DKIM en un entorno complejo?
Firma en el último salto que controles antes de Internet público—después de todas las modificaciones de contenido. Haz que ese salto sea aburrido. Todo lo “inteligente” ocurre antes.
¿Los receptores pueden causar un desajuste del hash del cuerpo en su lado?
Sí. Algunos gateways entrantes modifican correo (banners, reescritura de URL, detonación). Si puedes probar que tus bytes salientes son estables, la falla puede estar en la cadena entrante del receptor.
¿DKIM falla más con UTF-8 o caracteres internacionales?
No inherentemente. Los problemas vienen de conversiones de codificación (8bit/QP/base64) y degradaciones de transporte provocadas por contenido no ASCII.
Conclusión: próximos pasos que realmente reducen incidentes
El desajuste del hash del cuerpo DKIM rara vez es “un problema de DKIM”. Es tu canal de correo indicando que algo reescribe contenido después de la firma.
Esa reescritura puede ser bienintencionada (banners de seguridad) o silenciosamente destructiva (reenvuelto QP, reconstrucción MIME). En cualquier caso, la solución es arquitectónica.
- Elige un relé last-mile y conviértelo en una caja sacralizada de firma y envío. Sin disclaimers. Sin reescritura de URLs. Sin normalización MIME.
- Mueve todas las modificaciones aguas arriba de la firma y trata cualquier componente post-firma como culpable hasta probarlo inocente con un diff bytewise.
- Instrumenta para pruebas: almacena una copia de corta retención del mensaje post-firma en el último salto para poder resolver disputas con evidencia y no con sentimientos.
- Estandariza la canonicalización (normalmente relaxed/relaxed) y evita
l=a menos que aceptes sus compensaciones de seguridad. - Cuando un proveedor diga “transparente”, pregunta “¿Cambia el cuerpo crudo?” Si no pueden responder, asume que sí.
No tienes que amar el correo para administrarlo bien. Solo tienes que dejar de permitir que cajas aleatorias reescriban tus bytes después de que prometiste al mundo que no lo harían.