Cryptography: Using KernelTLS with Python

Cryptography: Using KernelTLS with Python

To learn more on cryptography check out my tutorial here
 
Python's ssl module does not directly use Kernel TLS (KTLS). However, as of OpenSSL 3.0, the ssl module can indirectly benefit from KTLS if the underlying OpenSSL library is configured to use KTLS.
When you create an SSL context using the ssl module, it uses the OpenSSL library to handle the TLS operations. If the OpenSSL library is configured to use KTLS, the ssl module will benefit from the performance improvements provided by KTLS.
 
Currently, Python does not support direct control or visibility over Kernel TLS (kTLS) because:
  1. The ssl module wraps OpenSSL, but does not expose:
    1. SSL_sendfile()
    2. SSL_get_ktls_send()
  1. kTLS is handled entirely in OpenSSL and the Linux kernel, after the TLS handshake, with no hooks in Python.

How OpenSSL (and thus theoretically Python) could use kTLS under the hood

 

1. Python and Kernel TLS: What's the Catch?

 
Python uses OpenSSL under the hood (ssl module). OpenSSL 3.0+ supports Kernel TLS.
But: Python doesn't yet expose an API to explicitly enable or detect kTLS. But: Python doesn't yet expose an API to explicitly enable or detect kTLS.
However, if:
  • You’re using Python 3.10+ (linked against OpenSSL 3.0+),
  • Your Linux kernel supports kTLS (≥ 5.2 recommended),
  • You’re using an eligible cipher (like AES-GCM),
  • You call sendfile() on a TLS socket,
    • …then kTLS might be automatically used by OpenSSL.
 

2. Conceptual Flow (What OpenSSL Does)

 
When OpenSSL decides to offload to kTLS:
  • It uses setsockopt() under the hood to configure the socket with encryption keys.
  • Then send() and sendfile() on the socket directly encrypt data in the kernel.
 

3. Python Example

Requirements:

  • Linux kernel ≥ 5.2
  • OpenSSL ≥ 3.0 compiled with enable-ktls
  • Python linked to that OpenSSL version (check via ssl.OPENSSL_VERSION)
  • Use TLS 1.2 or 1.3 with AES-GCM ciphers
  • You would also need a certificate and a private key. How they work together is:
    • Server sends cert.pem (public key + proof of identity).
    • Client verifies the certificate.
    • Client encrypts data using the public key from cert.pem.
    • Server uses key.pem (private key) to decrypt it.
    •  
To learn more on cryptography check out my tutorial here
 

Command To Generate Certificate and Private Key

openssl req -x509 -newkey rsa:2048 -nodes \ -keyout key.pem -out cert.pem -days 365 \ -subj "/CN=localhost"
 
What this does:
  • x509: generate a self-signed certificate
  • newkey rsa:2048: create a new 2048-bit RSA key
  • nodes: no passphrase on the key (for ease of use)
  • keyout: output file for private key
  • out: output file for certificate
  • days 365: valid for 1 year
  • subj "/CN=localhost": sets the Common Name (CN) in the certificate to localhost
 
After running this:
  • key.pem: server's private key
  • cert.pem: server's certificate (self-signed)
 
For use in browsers you'd like a version with SAN (Subject Alternative Name) support different from the above

Python TLS Server (using ssl)

 
# server.py import socket import ssl context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) context.load_cert_chain(certfile="cert.pem", keyfile="key.pem") bindsocket = socket.socket() bindsocket.bind(('0.0.0.0', 4433)) bindsocket.listen(5) print("Server listening on port 4433") while True: newsocket, fromaddr = bindsocket.accept() with context.wrap_socket(newsocket, server_side=True) as ssock: print(f"Connection from {fromaddr}") data = ssock.recv(1024) print(f"Received: {data.decode()}") ssock.sendall(b"Hello from server via TLS\n")

Python TLS Client

 
# client.py import socket import ssl context = ssl.create_default_context() with socket.create_connection(("127.0.0.1", 4433)) as sock: with context.wrap_socket(sock, server_hostname="localhost") as ssock: ssock.sendall(b"Hello from client via TLS\n") print(ssock.recv(1024).decode())
 
⚠️ The above client code will mostly fail with the following error:
 
[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self-signed certificate (_ssl.c:1028)
You will get this error because the server is using a self-signed certificate, and the Python client (by default) tries to verify it against trusted Certificate Authorities (CAs) — which it obviously can’t, since self-signed certs aren’t trusted by the system.
 
There are several solution options for this:
 
Option 1:
Use context = ssl._create_unverified_context() instead of context = ssl.create_default_context() to disable hostname and certificate verification.
WARNING: DO NOT use this in production — it disables all TLS security checks.
 
Option 2:
Add the server's cert.pem to a CA file on the client.
Here are the steps on mac:
  • Press Cmd + Space, search for Keychain Access, and open it.
  • Go to File > Import Items…
  • Select your cert.pem file. You can also drag and drop it into System keychain (not "login").
  • After importing, search for localhost in the list
  • Double-click the certificate
  • Expand the "Trust" section
  • Set "When using this certificate" to "Always Trust"
Then close the window — it will ask for your password to confirm.
 
Then update the client code to trust it:
context = ssl.create_default_context() context.load_verify_locations("cert.pem") # <-- use the server's cert here
 
 
Personally if this is for testing only use option 2 if you are on Mac. On MAC there is a common issue on macOS with Homebrew Python or OpenSSL built from source — it may not use the macOS system trust store.

4. How to Check if kTLS is Used?

 

Run this after connection is established:

sudo cat /proc/net/tls_stat
 
If kTLS is used, you’ll see counters like:
TLS current active sessions : 1 TLS TX records : 10 TLS RX records : 10
 

Check TCP socket status:

ss -tlpmi | grep tls
This may show a socket marked with tls under memory info.
 

Check kernel logs:

dmesg | grep ktls