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.







