---
title: "¿Qué es la huella TLS y cómo funciona JA3?"
description: "La huella TLS es un identificador que sale de las listas de cifrados y extensiones del ClientHello. Cómo se calculan JA3 y JA4, y qué cambia un proxy."
url: https://proxynet.io/es/blog/tls-fingerprinting
date: 2026-09-19
author: "Acar Diveroli"
category: "Web scraping, Proxy 101"
lang: es
---

# ¿Qué es la huella TLS y cómo funciona JA3?

Tienes un script escrito en Python. Sale por un proxy, la dirección IP cambia en cada petición y en la cabecera `User-Agent` has puesto una línea de Chrome actual. Aun así, el sitio responde `403` en la primerísima petición. Si abres la misma dirección desde la misma IP en un navegador real, la página carga. La diferencia está en un paquete que viaja antes que cualquier cabecera HTTP: el mensaje `ClientHello` que abre la conexión cifrada. Por la forma en que está compuesto ese mensaje, un sitio sabe si habla con Chrome, con Python o con curl sin leer una sola cabecera.

En este artículo vemos el handshake TLS, los campos que hay dentro de `ClientHello`, cómo se calcula la cadena JA3 a partir de esos campos y qué hace JA4 de otra manera. Después pasamos a la pregunta que más nos llega: ¿un proxy cambia la huella TLS? Al final hay un ejemplo de Python probado que te muestra en local la cadena JA3 de tu propio cliente.

> **Nota: Respuesta breve**
>
> Una huella TLS es un resumen que se produce a partir de las listas de versión, suites de cifrado, extensiones y curvas del mensaje `ClientHello` que envía un cliente al abrir una conexión cifrada. JA3 une esas listas en orden y toma el hash MD5; JA4 ordena las listas y añade un prefijo legible. El valor identifica al **software**, no a la persona: todos los Chrome de la misma versión llevan la misma huella. Un túnel HTTP `CONNECT` y un proxy SOCKS5 no tocan ese mensaje, así que un proxy cambia la IP, no la huella TLS.

## ¿Qué es el handshake TLS?

TLS es el protocolo que cifra el tráfico entre el navegador y el sitio; el `https://` de la barra de direcciones indica que lo estás usando. Antes de que empiece el cifrado, las dos partes tienen que ponerse de acuerdo en qué versión, qué suite de cifrado y qué clave usar. Esa negociación corta se llama handshake. La versión actual, TLS 1.3, está definida en el [RFC 8446](https://www.rfc-editor.org/rfc/rfc8446#section-4.1.2), y el handshake transcurre más o menos así:

1. El cliente se conecta al servidor por TCP y envía un mensaje `ClientHello`. Contiene las suites de cifrado y las extensiones que admite, más su parte de clave.
2. El servidor elige de la lista y responde con `ServerHello`. Desde ese punto ambas partes pueden derivar la clave compartida.
3. El servidor envía su certificado y un mensaje `Finished` que prueba la integridad del handshake, los dos cifrados.
4. El cliente verifica el certificado y envía su propio mensaje `Finished`.
5. Solo entonces viaja la primera petición HTTP (`GET /`, cabeceras, `User-Agent`), dentro del canal cifrado.

Para la huella, lo que importa es el paso uno. `ClientHello` se envía antes de que exista clave compartida, así que va **sin cifrar**; el servidor, el CDN que tiene delante y cada equipo de red del camino pueden leerlo tal cual, antes de cualquier cabecera, cookie o línea de JavaScript.

## ¿Qué campos contiene el mensaje ClientHello?

`ClientHello` es una lista en la que el cliente dice «esto es lo que sé hablar». Estos son los campos que se usan para la huella:

| Campo | ¿Qué lleva? | ¿Por qué distingue? |
|---|---|---|
| `legacy_version` | El campo de versión antiguo. Por compatibilidad, los clientes de TLS 1.3 siguen escribiendo aquí el valor de TLS 1.2 (`771`) | Por sí solo dice poco; la versión real está en una extensión |
| `cipher_suites` | Suites de cifrado admitidas, **por orden de preferencia** | Tanto la lista como su orden varían de una biblioteca a otra |
| `extensions` | Los números de tipo de las extensiones: `server_name` (0), `supported_groups` (10), `signature_algorithms` (13), ALPN (16), `supported_versions` (43), `key_share` (51) y otras | Qué extensiones hay, y en qué orden, es propio de cada software |
| `supported_groups` | Curvas y grupos utilizables en el intercambio de claves (`29` = x25519, `23` = secp256r1) | Los grupos nuevos llegan antes a los navegadores |
| `ec_point_formats` | Formatos de punto de curva elíptica | Algunas bibliotecas envían tres valores, otras solo uno |
| `signature_algorithms` | Algoritmos de firma aceptados, en orden | JA3 no lo usa, JA4 sí |
| ALPN | El protocolo de aplicación que quiere hablar el cliente (`h2`, `http/1.1`) | Un navegador pide `h2`; los clientes sencillos a menudo no piden nada |

Estas listas no las rellena tu aplicación, sino la **biblioteca TLS** que tiene debajo. Chrome usa BoringSSL y Firefox usa NSS. El módulo `ssl` de Python y Node.js se apoyan en OpenSSL. El curl que viene con Windows usa Schannel, la pila TLS del propio sistema operativo. Cada biblioteca tiene una lista de cifrados por defecto, un conjunto de extensiones y un orden distintos; así es como la identidad del cliente se filtra en el primer paquete del handshake.

## ¿Qué es una huella TLS?

Una huella TLS son esas listas del `ClientHello` reducidas a una cadena corta y comparable. Tres propiedades la separan de otras señales:

- **Es pasiva.** El sitio no hace que el cliente ejecute nada; solo lee el primer paquete que llega. Desactivar JavaScript o borrar las cookies no cambia el resultado.
- **Es independiente de las cabeceras.** `User-Agent` es una línea de texto y se cambia con una línea de código en cualquier biblioteca HTTP. `ClientHello` viene del comportamiento compilado de la biblioteca.
- **Identifica al software, no a la persona.** Todo el que use la misma versión de Chrome en el mismo sistema operativo produce el mismo valor. La capa de JavaScript (canvas, fuentes, WebGL), que busca distinguir dispositivos uno a uno, la contamos en [¿Qué es la huella digital del navegador?](/es/blog/browser-fingerprinting).

El método no se inventó para detectar bots. Su primer uso fue la seguridad de red: el malware cifra su tráfico, pero su biblioteca TLS y sus ajustes suelen ser fijos. Un equipo de seguridad que no ve el contenido sí puede reconocer la misma familia de software por la disposición de su `ClientHello`.

## ¿Qué es JA3 y cómo se calcula?

JA3 es el primer método extendido que ató esta idea a un formato estándar. Lo desarrollaron en Salesforce en 2017 John Althouse, Jeff Atkinson y Josh Atkins, y se publicó como código abierto. Según se describe en el [repositorio JA3 de Salesforce](https://github.com/salesforce/ja3), el cálculo tiene cinco pasos:

1. Del mensaje `ClientHello` se toman cinco campos: versión de TLS, suites de cifrado, extensiones, curvas (`supported_groups`) y formatos de punto de curva elíptica.
2. Los valores de cada campo se convierten a **números decimales** y se unen con `-` en el orden en que aparecen en el mensaje.
3. Los cinco campos se unen con `,`. Si un campo está vacío, su hueco se deja vacío.
4. Los valores GREASE (los explicamos abajo) no entran en las listas.
5. Se toma el hash MD5 de la cadena resultante. Ese hash de 32 caracteres es la huella JA3.

El propio ejemplo del repositorio es este:

```text
769,47-53-5-10-49161-49162-49171-49172-50-56-19-4,0-10-11,23-24-25,0
→ ada70206e40642a3e4461f35503241d5
```

Leído de izquierda a derecha: `769` significa TLS 1.0; luego vienen doce suites de cifrado, tres extensiones, tres curvas y un único formato de punto. Aquí el MD5 no se usa por seguridad, sino para convertir una cadena larga en una clave buscable de longitud fija.

Dos apuntes: como los clientes de TLS 1.3 escriben el valor de TLS 1.2 en el campo de versión, casi todas las cadenas JA3 actuales empiezan por `771`. Salesforce archivó el repositorio el 1 de mayo de 2025; herramientas como Wireshark siguen calculando el valor, pero el método ya no recibe mantenimiento.

## ¿Qué es GREASE y por qué JA3 lo ignora?

GREASE es un mecanismo de robustez definido en el [RFC 8701](https://www.rfc-editor.org/rfc/rfc8701). El cliente añade a sus listas de suites de cifrado, extensiones y grupos unos pocos valores reservados elegidos al azar, del patrón `0x0A0A`, `0x1A1A` … `0xFAFA`. Esos valores no significan nada. El objetivo es comprobar de continuo si los servidores ignoran en silencio los valores que no reconocen: un servidor defectuoso que corta la conexión ante un valor desconocido se detecta hoy, y no el día en que llegue una función nueva de TLS.

Como los valores se eligen al azar, el mismo navegador produciría un hash distinto en cada conexión si JA3 los contara. Por eso la documentación del método pide saltarse los valores GREASE; en el código de abajo lo hace la función `is_grease`.

## ¿Por qué Chrome mezcla el orden de las extensiones?

GREASE protege el contenido de las listas, no su orden. Que el software de servidores y equipos intermedios se apoyara en el **orden fijo de extensiones** de Chrome suponía el mismo riesgo, así que el equipo de Chrome también hizo aleatorio el orden. Según [la ficha de Chrome Platform Status](https://chromestatus.com/feature/5124606246518784), la función «TLS ClientHello extension permutation» se activó por defecto en Chrome 110. El motivo que se indica no es huir de la huella, sino evitar que el ecosistema se vuelva frágil: un orden fijo empuja a quien desarrolla servidores a reconocer Chrome y dar por supuesto un comportamiento concreto, lo que dificulta los cambios futuros en TLS. El RFC 8446 ya dice que las extensiones pueden venir en cualquier orden; la única excepción es `pre_shared_key`, que debe ir al final cuando está presente.

Como JA3 une las extensiones en el orden del mensaje, este cambio hizo inestable el hash JA3 de los navegadores basados en Chromium. Lo medimos con el escucha de más abajo: 24 conexiones consecutivas de un navegador basado en Chromium dieron **24 hashes JA3 distintos**. Al ordenar los números de extensión, en todas había el mismo conjunto; lo único que cambiaba era el orden.

## ¿Qué es JA4 y en qué se diferencia de JA3?

JA4 es el formato más nuevo, publicado por FoxIO, que responde a este problema. Según [la especificación técnica de JA4](https://github.com/FoxIO-LLC/ja4/blob/main/technical_details/JA4.md), la huella tiene tres partes con la forma `a_b_c`. El ejemplo del documento, `t13d1516h2_8daaf6152771_e5627efa2ab1`, se lee así:

- **`t`**: TLS sobre TCP (`q` sería QUIC y `d`, DTLS).
- **`13`**: TLS 1.3. JA4 lee la versión en la extensión `supported_versions`, no en el campo antiguo.
- **`d`**: hay extensión SNI, es decir, el cliente se conecta a un nombre de dominio (`i` indica que no hay SNI).
- **`15`** y **`16`**: 15 suites de cifrado y 16 extensiones, sin contar GREASE.
- **`h2`**: el primer y el último carácter del primer valor de la lista ALPN, o sea HTTP/2.
- **`8daaf6152771`**: los códigos hexadecimales de las suites de cifrado se **ordenan**, se les aplica SHA-256 y se conservan los 12 primeros caracteres.
- **`e5627efa2ab1`**: los códigos de extensión se ordenan (sin SNI ni ALPN), se añaden los algoritmos de firma en su orden original y el resultado se resume igual.

La ordenación anula el efecto de la mezcla de extensiones. La primera parte, legible, permite una distinción gruesa sin mirar los hashes: dos primeras partes, una acabada en `h2` y otra en `00` porque no envía ALPN, no vienen del mismo software.

| | JA3 | JA4 |
|---|---|---|
| Quién lo publica | Salesforce (2017), repositorio archivado en 2025 | FoxIO, con desarrollo activo |
| Formato | un único hash MD5 de 32 caracteres | `a_b_c`: prefijo legible + dos hashes SHA-256 recortados |
| Orden de extensiones | el del mensaje | ordenado; no le afecta la mezcla |
| Versión de TLS | campo de versión antiguo (`771` incluso en TLS 1.3) | extensión `supported_versions` |
| ALPN, SNI | solo se ven como número de extensión | los dos aparecen escritos en la primera parte |
| Algoritmos de firma | no se usan | entran en la tercera parte |
| GREASE | se ignora | se ignora |

Según la nota de licencia del repositorio de FoxIO, JA4, la huella de cliente TLS, es abierta con licencia BSD 3-Clause; el resto de la familia (JA4S, JA4H, JA4X, JA4T y siguientes) depende de una licencia propia de FoxIO.

## ¿Un proxy cambia la huella TLS?

No. La razón está en cómo transporta un proxy el tráfico HTTPS.

Con un proxy HTTP, el cliente envía primero al proxy una petición `CONNECT destino.com:443`. El proxy abre una conexión TCP al destino, contesta `200 Connection Established` y a partir de ahí solo retransmite bytes. `ClientHello` viaja **dentro** de ese túnel: lo produce tu cliente, lo lee el sitio de destino y el proxy ni cambia su contenido ni lo reescribe. Con SOCKS5 pasa lo mismo; el protocolo retransmite la conexión TCP una capa más abajo y ni siquiera sabe que los datos que lleva son TLS. Recorrimos paso a paso los dos protocolos en [Diferencia entre SOCKS y HTTP](/es/blog/socks-vs-http-proxy).

El resultado es que el sitio ve a la vez la dirección IP del proxy y la huella TLS de **tu cliente**. Usar [Proxies HTTPS](https://proxynet.io/es/https-proxy) o [Proxies SOCKS5](https://proxynet.io/es/socks5-proxy) no lo cambia, y tampoco lo cambia que el proxy sea residencial, móvil o de centro de datos. Lo probamos con un proxy de prueba local: el mismo cliente curl se conectó al escucha de abajo de forma directa, por un túnel HTTP `CONNECT` y por SOCKS5, y el escucha registró la misma cadena JA3 las tres veces.

El único caso en que la huella cambia es cuando en medio hay un intermediario que **termina** el TLS:

| Qué hay en medio | ¿Quién establece el TLS con el destino? | ¿Qué huella ve el sitio? |
|---|---|---|
| Proxy HTTP (túnel `CONNECT`) | Tu cliente | La de tu cliente |
| Proxy SOCKS5 | Tu cliente | La de tu cliente |
| VPN | Tu cliente | La de tu cliente |
| Pasarela corporativa con inspección TLS | La pasarela | La de la pasarela |
| Antivirus con análisis HTTPS activado | El antivirus | La del antivirus |
| Servicio que descarga la página por ti | El cliente del servicio | La del cliente del servicio |

La quinta fila la vivimos en nuestra propia máquina mientras preparábamos este artículo: curl en Windows devolvía una huella al conectarse directamente al servicio de eco y otra distinta al pasar por el túnel del proxy local. La causa era el análisis HTTPS del antivirus, que rehacía la conexión directa con su propia pila TLS. Si el valor del servicio de eco no se parece a la biblioteca que esperabas, mira quién firmó el certificado.

## ¿Cómo puedes ver tu propia cadena JA3?

La vía más corta es un servicio de eco: abre [la página TLS de BrowserLeaks](https://browserleaks.com/tls) en el navegador y verás tus valores JA3 y JA4. Si quieres mirar a nivel de paquete, Wireshark calcula él mismo estos valores en los paquetes `ClientHello`; en la [referencia de filtros de visualización](https://www.wireshark.org/docs/dfref/t/tls.html) los campos figuran como `tls.handshake.ja3` y `tls.handshake.ja4`.

Para ver el cálculo en sí, puedes usar el script de abajo. Funciona solo con la biblioteca estándar de Python y no envía nada al exterior: abre un escucha TCP en bruto en el puerto `8443`, analiza el mensaje `ClientHello` de cada cliente que se conecta e imprime la cadena JA3 y su hash. Al arrancar conecta una vez el propio cliente `urllib` de Python. El escucha no completa el handshake, lee el primer paquete y cierra; un error de conexión en el lado del cliente es lo esperado.

```python
import hashlib
import socket
import threading
import urllib.request

HOST, PORT = "127.0.0.1", 8443

def is_grease(value):
    # RFC 8701: 0x0a0a, 0x1a1a, ... 0xfafa
    return (value & 0x0F0F) == 0x0A0A and (value >> 8) == (value & 0xFF)

def read_client_hello(conn):
    data = b""
    while len(data) < 5:
        data += conn.recv(4096)
    if data[0] != 22:  # 22 = registro de handshake
        raise ValueError("no es un handshake TLS")
    record_length = int.from_bytes(data[3:5], "big")
    while len(data) < 5 + record_length:
        chunk = conn.recv(4096)
        if not chunk:
            break
        data += chunk
    return data[5 : 5 + record_length]

def ja3_from_client_hello(hello):
    if hello[0] != 1:  # 1 = ClientHello
        raise ValueError("no es un ClientHello")
    pos = 4  # tipo de mensaje (1) + longitud (3)
    version = int.from_bytes(hello[pos : pos + 2], "big")
    pos += 2 + 32  # version + random
    pos += 1 + hello[pos]  # session_id
    size = int.from_bytes(hello[pos : pos + 2], "big")
    pos += 2
    ciphers = [int.from_bytes(hello[i : i + 2], "big") for i in range(pos, pos + size, 2)]
    pos += size
    pos += 1 + hello[pos]  # compression_methods
    end = pos + 2 + int.from_bytes(hello[pos : pos + 2], "big")
    pos += 2

    extensions, groups, point_formats = [], [], []
    while pos < end:
        ext_type = int.from_bytes(hello[pos : pos + 2], "big")
        ext_size = int.from_bytes(hello[pos + 2 : pos + 4], "big")
        body = hello[pos + 4 : pos + 4 + ext_size]
        pos += 4 + ext_size
        extensions.append(ext_type)
        if ext_type == 10:  # supported_groups
            groups = [int.from_bytes(body[i : i + 2], "big") for i in range(2, len(body), 2)]
        elif ext_type == 11:  # ec_point_formats
            point_formats = list(body[1:])

    def join(values):
        return "-".join(str(v) for v in values if not is_grease(v))

    fields = [str(version), join(ciphers), join(extensions), join(groups), join(point_formats)]
    ja3_text = ",".join(fields)
    return ja3_text, hashlib.md5(ja3_text.encode()).hexdigest()

def own_python_client():
    try:
        urllib.request.urlopen(f"https://localhost:{PORT}", timeout=5)
    except OSError:
        pass  # el escucha cierra sin responder, es lo esperado

def main():
    server = socket.create_server((HOST, PORT))
    print(f"escuchando en https://localhost:{PORT}, Ctrl+C para salir")
    threading.Thread(target=own_python_client, daemon=True).start()
    while True:
        conn, _ = server.accept()
        with conn:
            try:
                ja3_text, ja3_hash = ja3_from_client_hello(read_client_hello(conn))
            except (ValueError, IndexError, OSError) as exc:
                print("no se pudo leer:", exc)
                continue
        print(ja3_hash, ja3_text)

if __name__ == "__main__":
    main()
```

Mientras el script está en marcha, apunta distintos clientes a la misma dirección desde otra terminal. Escribe `localhost` en la dirección; un cliente que se conecta a una dirección IP no envía la extensión SNI y la huella cambia.

```bash
curl -k https://localhost:8443
node -e "require('https').get('https://localhost:8443', { rejectUnauthorized: false }).on('error', () => {})"
```

En nuestra máquina (Windows 11, Python 3.13 con OpenSSL 3.0, Node.js 24 y curl 8 compilado con Schannel) la salida fue esta. Acortamos el campo de suites de cifrado para que se lea mejor:

```text
331a436afb23d4e31134c11b301bdcb5 771,4866-4867-4865-…,0-11-10-35-16-22-23-49-13-43-45-51-21,29-23-30-25-24-256-257-258-259-260,0-1-2
2e6c64f66822fc35b6a7a128b557f1de 771,4866-4865-49196-…,0-43-13-35-10-11-16-51-49-23-65281-45,29-23-24,0
944d1e1858cd278718f8a46b65d3212f 771,4866-4867-4865-…,65281-0-11-10-35-22-23-13-43-45-51,4588-29-23-30-24-25-256-257,0-1-2
```

La misma máquina, la misma IP y tres identidades distintas: por orden, Python, curl y Node.js. Para Python, el servicio de eco devolvió exactamente el mismo hash, así que el analizador funciona bien. El `4588` de la línea de Node.js es el grupo híbrido poscuántico de intercambio de claves que figura en los registros de la IANA como `X25519MLKEM768`; esta versión de Python no lo envía. En ese mismo Node.js, `https.get` y el `fetch` integrado también dieron valores JA3 distintos, porque `fetch` añade la extensión ALPN: la huella depende de la biblioteca HTTP que uses, no del lenguaje. Las diferencias entre bibliotecas del lado de Python las comparamos en [HTTPX, Requests y AIOHTTP](/es/blog/httpx-vs-requests-vs-aiohttp).

Para medir el efecto de un proxy el escucha no sirve (un proxy remoto no llega a tu `localhost`), así que pregunta dos veces al servicio de eco:

```python
import json
import urllib.request

PROXY = "http://user:pass@pr.proxynet.io:8000"
URL = "https://tls.browserleaks.com/json"

def fingerprint(opener):
    with opener.open(URL, timeout=15) as response:
        result = json.load(response)
    return result["ja3_hash"], result["ja4"]

direct = urllib.request.build_opener(urllib.request.ProxyHandler({}))
proxied = urllib.request.build_opener(urllib.request.ProxyHandler({"https": PROXY}))

print("directo:", *fingerprint(direct))
print("proxy  :", *fingerprint(proxied))
```

Verás los mismos valores en las dos líneas; lo único que cambia es la dirección IP que ve el servicio.

## ¿Cómo usa esta información quien gestiona un sitio?

Los productos de CDN y cortafuegos exponen el valor JA3 o JA4 como un campo junto a cada petición. Quien gestiona el sitio usa ese campo al escribir reglas y al revisar los registros. Usos típicos:

- **Comprobación de coherencia.** Si `User-Agent` dice «Chrome» pero la huella no encaja con ninguna versión conocida de Chrome, la cabecera se ha modificado. Por sí sola no es una prueba concluyente, pero sí una señal fuerte que afecta a la puntuación. La cabecera en sí la contamos en [¿Qué es el User-Agent?](/es/blog/what-is-user-agent).
- **Límite de peticiones independiente de la IP.** Si un tráfico repartido entre cientos de IP lleva la misma huella, el contador puede atarse a la huella en lugar de a la IP. Una IP rotativa no reinicia ese contador.
- **Reconocer herramientas conocidas.** Existen listas de huellas recopiladas para familias de malware y herramientas de escaneo; los equipos de seguridad las cruzan con sus registros.

También tiene límites. Un sitio que bloquea el valor de un navegador actual bloquea a todos los visitantes que usan ese navegador. Las actualizaciones del navegador cambian el valor, y las pasarelas corporativas y los antivirus meten sus propias huellas por medio. Por eso una huella TLS no decide nada por sí sola; es una de las señales que alimentan la puntuación junto con la reputación de la IP y el comportamiento. Esa puntuación en conjunto la tratamos en [¿Cómo funciona la detección de bots?](/es/blog/how-bot-detection-works), y las capas de Cloudflare en [Cloudflare Precursor](/es/blog/cloudflare-precursor).

## ¿Qué significa esto si recoges datos?

Volvamos a la situación de la introducción. El cliente de Python dice en su línea `User-Agent` que es Chrome, mientras que su `ClientHello` dice que es OpenSSL. El sitio ve la contradicción en el primer paquete. La reacción correcta es eliminar la contradicción, no intentar ocultarla:

- **No digas que eres un navegador que no eres.** Que el `User-Agent` de tu script presente al script: un nombre, una versión y una dirección de contacto. A un cliente que llega con identidad honesta, respeta `robots.txt` y trabaja despacio, quien gestiona el sitio puede reconocerlo y ponerlo en una lista de permitidos. Las reglas las explicamos en [Qué es robots.txt y cómo leerlo](/es/blog/robots-txt).
- **Si hay una API oficial, úsala.** Una petición que llega con clave de API ya tiene identidad conocida; la discusión sobre la huella desaparece.
- **Si la página necesita de verdad un navegador, usa uno real.** Cuando abres con Playwright una página dibujada con JavaScript dentro de un Chromium real, tu cliente es realmente ese navegador; sus cabeceras, su capa TLS y su entorno JavaScript concuerdan entre sí. La instalación está en [Qué es Playwright y cómo usarlo con un proxy](/es/blog/playwright-proxy).
- **Baja el ritmo y pide permiso.** Para un trabajo regular y de gran volumen, escribir a quien gestiona el sitio suele ser la solución más duradera.

Los demás motivos de bloqueo, y las vías legítimas para evitarlos, los reunimos en [Cómo hacer web scraping sin que te bloqueen](/es/blog/web-scraping-without-getting-blocked).

## Casos de uso

- **Diagnosticar un `403` en un scraper:** si con la misma IP el navegador pasa y el script no, la diferencia está casi seguro en la identidad del cliente. Los códigos de estado los separamos en [Códigos de estado HTTP en web scraping](/es/blog/http-status-codes-web-scraping), y el montaje general está en nuestra página de [solución de extracción de datos](/es/data-scraping).
- **Separar el tráfico de bots en tu propio sitio:** agrupar los registros por huella muestra el tráfico distribuido que se le escapa a la agrupación por IP.
- **Presentar tu propio crawler:** una versión fija de la biblioteca produce una huella constante, y así se reconoce a tu crawler en los registros. La parte de infraestructura está en nuestra página de [solución de web crawler](/es/web-crawler).
- **Poner la expectativa correcta sobre un proxy:** [Proxies residenciales](https://proxynet.io/es/residential-proxy) cambia la reputación de IP y la ubicación; la identidad de tu cliente sigue siendo responsabilidad tuya.

## Errores frecuentes

- **Creer que el cliente cambia al cambiar el `User-Agent`.** La cabecera es texto; `ClientHello` es el comportamiento de la biblioteca y sale antes que la cabecera.
- **Esperar que el proxy cambie la huella TLS.** Un proxy que hace túnel cambia la IP y no toca el handshake.
- **Esperar que el hash JA3 se mantenga fijo en un navegador basado en Chromium.** El orden de extensiones cambia en cada conexión; para comparar, usa JA4 o una lista de extensiones ordenada.
- **Aceptar sin más el resultado del servicio de eco.** Un antivirus o una pasarela corporativa que analiza HTTPS le muestra al servicio su propia huella.
- **Usar `127.0.0.1` al medir.** No se envía SNI y el valor sale distinto del de las conexiones reales.

## Guía de decisión

| Situación | Recomendación |
|---|---|
| La IP cambia con el proxy y aun así recibes `403` en la primera petición | Mira la identidad del cliente: ¿dicen lo mismo el `User-Agent` y la biblioteca que usas? |
| Quieres conocer el valor JA3 o JA4 de tu propio cliente | Un servicio de eco o el escucha local de arriba |
| Quieres agrupar el tráfico de Chrome en tus registros | JA4, no JA3; no le afecta la mezcla de extensiones |
| La página necesita JavaScript y un navegador real | Automatización con navegador real (Playwright), un ritmo razonable y el alcance que permite el sitio |
| El servicio de eco muestra un valor que no esperabas | Mira quién firmó el certificado; puede haber software que termine el TLS en medio |
| Tu sitio recibe tráfico de bots distribuido | Ata el límite de peticiones al par IP + huella en vez de a la IP; no bloquees por un único hash |

## Preguntas frecuentes

### ¿Cuál es la diferencia entre JA3 y JA3S?

JA3 se produce a partir del mensaje `ClientHello` del cliente y JA3S a partir del mensaje `ServerHello` del servidor. Como un servidor responde de forma distinta a clientes distintos, JA3S no identifica al servidor por sí solo; en cambio, su respuesta al mismo cliente es siempre la misma. Por eso los equipos de seguridad usan los dos en pareja.

### ¿Usar una VPN cambia la huella TLS?

No. Una VPN envía el tráfico por un túnel cifrado, pero el handshake TLS con el sitio lo sigue haciendo tu navegador o tu script. El sitio ve la dirección IP del servidor VPN y la huella de tu cliente. La diferencia entre ambas herramientas la explicamos en [Proxy o VPN](/es/blog/proxy-vs-vpn).

### ¿Una huella TLS me identifica personalmente?

Por sí sola no. Todo el que use la misma versión de navegador en el mismo sistema operativo produce el mismo valor. Una identidad que se acerca a una persona surge al combinar ese valor con la dirección IP, las cookies y las señales de la capa de JavaScript.

### ¿Actualizar el navegador cambia la huella?

Puede cambiarla. Cuando una versión nueva quita una suite de cifrado o añade una extensión, la lista y el hash cambian. Por eso las listas de huellas se mantienen versión a versión.

### ¿Una ventana privada o borrar las cookies afecta a la huella TLS?

No. El mensaje `ClientHello` lo produce la biblioteca TLS del navegador; el historial, las cookies y el tipo de ventana no entran en esas listas.

### ¿Qué hago si me bloquean por mi huella?

Mide primero: fíjate en qué valor aparece en el servicio de eco y en si hay software que termine el TLS por medio. Si escribes un script, declara tu identidad con honestidad, baja el ritmo y usa la API oficial o el canal de permisos del sitio. Si te bloquean con un navegador corriente, el problema está casi seguro en la reputación de la IP y no en la huella.

## En resumen

La huella TLS sale de `ClientHello`, el primer paquete, sin cifrar, de una conexión cifrada. JA3 une en orden las cinco listas de ese mensaje y toma el hash MD5; cuando Chrome empezó a mezclar el orden de las extensiones, el hash se volvió inestable, y JA4 lo resolvió ordenando las listas. El valor muestra el software y no a la persona, sale antes que las cabeceras y ningún proxy que haga túnel lo toca: el proxy cambia la IP y la identidad del cliente se queda contigo. Para sumar el tipo de IP adecuado a una identidad de cliente constante y honesta, echa un vistazo a [nuestros servicios de proxy](/es/proxy).
