ssl en Python: conexiones TLS seguras

Publicado el: 19/08/2026
Tempo de leitura: 5 minutos
Sala de servidores iluminada que representa conexiones TLS seguras con ssl en Python

El módulo ssl añade TLS a los sockets de Python, proporcionando cifrado en tránsito y autenticación mediante certificados. Es la capa utilizada por clientes HTTPS y puede proteger protocolos personalizados sobre TCP. Sin embargo, activar cifrado no basta: el cliente debe validar la cadena del certificado, comprobar el hostname y mantener una política moderna de versiones.

Esta guía presenta SSLContext, clientes y servidores TLS, autoridades certificadoras públicas o privadas, versiones mínimas, SNI, timeouts, inspección de conexiones y autenticación mutua. Los ejemplos usan la biblioteca estándar, aunque para HTTP normalmente conviene una biblioteca superior que integre estas decisiones.

TLS, SSL y Python

El nombre histórico SSL permanece en la API, pero los protocolos SSL antiguos son inseguros y obsoletos. El código moderno debe negociar TLS con PROTOCOL_TLS_CLIENT o PROTOCOL_TLS_SERVER. Python utiliza la biblioteca OpenSSL instalada, por lo que algunas funciones y mensajes pueden variar entre plataformas.

Antes de añadir TLS, revisa la guía del cliente TCP con sockets. TCP crea el flujo de bytes; después el handshake TLS negocia versión, cifrado y certificados.

Cliente TLS con valores seguros

El punto de partida recomendado es ssl.create_default_context(). Carga las autoridades del sistema, exige una cadena válida y verifica el hostname.

import socket
import ssl

hostname = "www.python.org"
context = ssl.create_default_context()

with socket.create_connection((hostname, 443), timeout=10) as tcp_socket:
    with context.wrap_socket(
        tcp_socket,
        server_hostname=hostname,
    ) as tls_socket:
        print(tls_socket.version())
        print(tls_socket.cipher())

server_hostname es esencial. Activa SNI para que el servidor seleccione el certificado correcto y proporciona el nombre que debe coincidir con el certificado.

Por qué CERT_NONE es inseguro

Desactivar la verificación mantiene el tráfico cifrado, pero acepta cualquier certificado. Un atacante en la red puede presentar su propio certificado, completar el handshake y leer o modificar la conexión.

# No uses esto en producción.
context.check_hostname = False
context.verify_mode = ssl.CERT_NONE

Los errores deben corregirse: cadena incompleta, hostname incorrecto, certificado expirado, reloj incorrecto o CA privada ausente. Suprimir la validación solo oculta el defecto.

Contexto explícito

context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
context.minimum_version = ssl.TLSVersion.TLSv1_2
context.load_default_certs()

assert context.verify_mode == ssl.CERT_REQUIRED
assert context.check_hostname is True

PROTOCOL_TLS_CLIENT habilita verificación de cadena y hostname. TLS 1.0 y 1.1 están obsoletos. Define TLS 1.2 como mínimo cuando necesites una política explícita; TLS 1.3 se negociará si ambos extremos lo soportan.

CA privada

Los servicios internos pueden usar una autoridad privada. Carga la CA correcta en lugar de desactivar la verificación:

context = ssl.create_default_context(
    cafile="empresa-root-ca.pem",
)

Distribuye la CA por un canal autenticado y planifica su rotación. Fijar solo el certificado final sin estrategia de renovación puede causar interrupciones.

Enviar datos

Después del handshake utiliza sendall() y recv() como con un socket normal. TLS sigue siendo un flujo de bytes y no conserva los límites de los mensajes.

request = (
    "GET / HTTP/1.1\r\n"
    f"Host: {hostname}\r\n"
    "Connection: close\r\n\r\n"
).encode("ascii")

tls_socket.sendall(request)
chunks = []
while chunk := tls_socket.recv(16_384):
    chunks.append(chunk)
response = b"".join(chunks)

Para HTTP real usa una biblioteca que maneje redirects, proxies, cookies y límites. Consulta la guía para integrar APIs con Python.

Servidor TLS

Un servidor utiliza PROTOCOL_TLS_SERVER y carga su certificado y clave privada.

import socket
import ssl

context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
context.minimum_version = ssl.TLSVersion.TLSv1_2
context.load_cert_chain(
    certfile="certchain.pem",
    keyfile="private.key",
)

with socket.socket() as listener:
    listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    listener.bind(("127.0.0.1", 8443))
    listener.listen()

    with context.wrap_socket(listener, server_side=True) as secure_listener:
        connection, address = secure_listener.accept()
        with connection:
            print(address, connection.version())

Es un ejemplo didáctico. Producción necesita concurrencia, límites, logs, apagado ordenado y renovación. La guía del servidor HTTP simple explica por qué estos servidores no sustituyen infraestructura profesional.

Proteger la clave privada

La clave privada debe ser legible únicamente por el proceso requerido. No la incluyas en repositorios, imágenes públicas ni logs. Muchos sistemas terminan TLS en un proxy o servicio gestionado que automatiza protección y renovación.

Autenticación mutua

Con mTLS, el servidor también exige un certificado del cliente.

server_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
server_context.load_cert_chain("server.pem", "server.key")
server_context.load_verify_locations(cafile="client-ca.pem")
server_context.verify_mode = ssl.CERT_REQUIRED

El certificado demuestra posesión de una clave emitida por una CA aceptada. La aplicación todavía debe convertir esa identidad en permisos y mantener emisión, revocación y rotación.

Certificado del cliente

client_context = ssl.create_default_context(cafile="server-ca.pem")
client_context.load_cert_chain("client.pem", "client.key")

No reutilices una única clave privada en todos los clientes si necesitas revocar dispositivos individualmente.

Inspeccionar el peer

certificate = tls_socket.getpeercert()
print(certificate.get("subject"))
print(certificate.get("issuer"))
print(certificate.get("notAfter"))
print(tls_socket.version())
print(tls_socket.cipher())

Desde Python 3.13, get_verified_chain() devuelve la cadena validada y get_unverified_chain() la cadena bruta enviada. Nunca uses la cadena no verificada como prueba de confianza.

Timeouts y excepciones

Configura timeout para conexión y operaciones. Captura SSLCertVerificationError por separado si necesitas el código y mensaje de validación.

try:
    pass
except ssl.SSLCertVerificationError as error:
    print(error.verify_code, error.verify_message)
except (ssl.SSLError, OSError) as error:
    print(f"Fallo TLS o de red: {error}")

No registres claves, tokens o contenido confidencial. El hostname, versión negociada, razón de OpenSSL e identificador de operación suelen ser suficientes.

Sockets no bloqueantes

En modo no bloqueante, una lectura TLS puede requerir escritura y una escritura puede requerir datos de entrada. Maneja SSLWantReadError y SSLWantWriteError con un selector. Para sistemas complejos, asyncio o un framework gestionan estos detalles.

Ciphers y TLS 1.3

Los contextos modernos ya seleccionan cifrados fuertes. Evita copiar listas antiguas de tutoriales. La configuración de TLS 1.3 es diferente dentro de OpenSSL. Cambia la política solo por un requisito revisado de cumplimiento o interoperabilidad.

Certificados autofirmados

Un certificado autofirmado puede funcionar en pruebas o redes privadas si se distribuye explícitamente como raíz confiable. Confiar en un certificado concreto es diferente de aceptar todos. Nunca resuelvas un error cambiando a CERT_NONE.

Errores comunes

Los fallos más peligrosos son desactivar la verificación, quitar el hostname, omitir server_hostname, permitir TLS obsoleto, usar certificados expirados, compartir claves privadas, omitir timeouts y asumir que cifrado sin autenticación es seguro.

Buenas prácticas

Comienza con create_default_context(). Exige cadena y hostname válidos. Usa TLS 1.2 o superior, actualiza Python y OpenSSL, configura timeouts, protege claves, automatiza renovación y prueba alertas de expiración. Para mTLS, separa CAs e identidades por ambiente.

Conclusión

El módulo ssl permite construir conexiones TLS directamente, pero la seguridad depende de certificados, hostname, versiones y claves. Los valores modernos de Python ofrecen una base sólida. No los desactives para evitar errores; corrige la cadena de confianza y utiliza bibliotecas superiores cuando sea posible.

Consulta la documentación oficial de ssl y el RFC 8446 sobre TLS 1.3.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

    Desarrollador configurando un servidor HTTPS y certificado TLS con Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    HTTPSServer: crea un servidor HTTPS local en Python

    Aprende HTTPSServer en Python para crear servicios HTTPS locales, configurar certificados, usar hilos y comprender sus límites.

    Ler mais

    Tempo de leitura: 4 minutos
    08/10/2026
    Archivos protegidos que representan extracción segura de TAR con Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    tarfile extraction_filter: extrae TAR con seguridad

    Aprende tarfile extraction_filter en Python para extraer archivos TAR con validación, seguridad y control de rutas.

    Ler mais

    Tempo de leitura: 6 minutos
    08/10/2026
    Código Python mostrando avisos controlados con catch_warnings
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    catch_warnings: captura warnings en pruebas Python

    Aprende catch_warnings en Python para capturar, probar y controlar avisos con filtros específicos y alcance seguro.

    Ler mais

    Tempo de leitura: 5 minutos
    07/10/2026
    Código Python que representa referencias persistentes de pickle
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    pickle persistent_id: serializa referencias externas

    Aprende pickle persistent_id en Python para referencias externas estables, validación, seguridad, rendimiento y compatibilidad.

    Ler mais

    Tempo de leitura: 5 minutos
    07/10/2026
    Código binario que representa buffers y vistas de memoria en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    memoryview.count: cuenta valores sin copiar buffers

    Aprende memoryview.count en Python para contar bytes y valores en buffers sin copias, con formatos, límites y buenas prácticas.

    Ler mais

    Tempo de leitura: 6 minutos
    06/10/2026
    Código Python y anotaciones de tipos
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    annotationlib: evita imports circulares en anotaciones

    Aprende annotationlib en Python para inspeccionar anotaciones diferidas, evitar imports circulares y crear herramientas seguras.

    Ler mais

    Tempo de leitura: 6 minutos
    06/10/2026