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

    Close-up view of a computer screen displaying code in a software development environment.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    ftplib en Python: FTP y FTPS seguros

    Aprende ftplib en Python para listar, descargar y subir archivos por FTP o FTPS con TLS, timeouts, límites, reanudación y

    Ler mais

    Tempo de leitura: 5 minutos
    21/08/2026
    Rack de servidores que representa un endpoint creado con xmlrpc.server en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    xmlrpc.server: crea servidores XML-RPC

    Aprende xmlrpc.server en Python para crear servidores XML-RPC, registrar funciones, limitar métodos y rutas y evitar exposición insegura.

    Ler mais

    Tempo de leitura: 6 minutos
    21/08/2026
    Código de error sobre datos binarios que representa fallos manejados con urllib.error en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    urllib.error en Python: maneja errores HTTP

    Aprende urllib.error en Python para manejar URLError, HTTPError, descargas incompletas, retries selectivos y diagnósticos de red claros.

    Ler mais

    Tempo de leitura: 5 minutos
    21/08/2026
    Teclas con la palabra HTML que representan entidades HTML en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    html.entities: convierte entidades HTML

    Aprende html.entities en Python para consultar entidades HTML, convertir nombres y code points y no confundir decodificación con sanitización.

    Ler mais

    Tempo de leitura: 7 minutos
    21/08/2026
    Carpeta con archivos que representa tipos MIME identificados con mimetypes en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    mimetypes: detecta tipos MIME de archivos

    Aprende mimetypes en Python para identificar tipos de archivo, validar cargas y definir Content-Type con más seguridad.

    Ler mais

    Tempo de leitura: 6 minutos
    20/08/2026
    Código HTML en una pantalla que representa análisis con html.parser en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    html.parser en Python: analiza HTML

    Aprende html.parser en Python para extraer texto, enlaces y metadatos, procesar HTML por bloques y no confundir parsing con sanitización.

    Ler mais

    Tempo de leitura: 4 minutos
    20/08/2026