pyOpenSSL review
pyOpenSSL 26.4.0 exposes selected OpenSSL TLS machinery through SSL.Context and SSL.Connection. It covers socket and memory-BIO transports, verification callbacks, SNI, ALPN, OCSP hooks, sessions, and nonblocking handshake states. Version 26.4.0 raises the maximum supported cryptography release to the 50.x line. The larger 26.x change is subtraction: certificate request helpers have been removed, X509 mutation and private-key methods are deprecated, and the README tells anyone doing work beyond TLS connections to use cryptography directly.
pyOpenSSL 26.4.0 installed as five packages totaling 17 MB in 0.3 seconds and imported in 0.30 seconds with zero audit findings in our sandbox. Keep it for OpenSSL-specific TLS callbacks and memory BIOs; build new certificate and key code on cryptography.
We installed it
| Install | ✓ · 0.3s | 5 packages on disk · 17 MB |
| Import | ✓ | import OpenSSL in 0.30s · pure Python · py.typed · requires Python >=3.9 |
| Known vulns | 0 | (pip-audit) |
Answers from our run
Does pyOpenSSL install cleanly?
Yes. In a fresh container with an empty cache, pip install pyOpenSSL finished in 0.3s, leaving 5 packages and 17 MB on disk. pip-audit reported no known vulnerabilities.
What does pyOpenSSL need to run?
Python >=3.9, and nothing compiled: it is pure Python. In our run import OpenSSL succeeded in 0.30s, and the package ships py.typed for type checkers.
pyOpenSSL or cryptography: which should you use?
cryptography: Use it for certificate, key, signing, serialization, and verification work that pyOpenSSL is retiring. pyOpenSSL 26.4.0 installed as five packages totaling 17 MB in 0.3 seconds and imported in 0.30 seconds with zero audit findings in our sandbox.
When should you not use pyOpenSSL?
The task parses, creates, or signs certificates and keys; those OpenSSL.crypto paths are moving to cryptography
Discussed on
- hnPyOpenSSL on PyPy3 points
Use it if
- A TLS endpoint needs OpenSSL callbacks or controls that Python's ssl module does not expose
- An event-driven transport must feed and drain encrypted bytes through memory BIOs
- SNI, ALPN, OCSP, or client-certificate policy belongs directly in handshake code
- An established framework already passes SSL.Context and SSL.Connection through its extension points
- The task parses, creates, or signs certificates and keys; those OpenSSL.crypto paths are moving to cryptography
- A standard verified HTTPS socket is sufficient and can use Python ssl without this dependency chain
- The implementation assumes chain verification also validates the requested hostname; pyOpenSSL leaves hostname identity as separate work
- Every cryptography release must be adopted immediately, while pyOpenSSL deliberately publishes supported minimum and maximum bounds
- Deprecation warnings are ignored in CI; 26.x has already removed request and extension wrappers after their warning periods
Setup reality
Our install of pyOpenSSL 26.4.0 took 0.3 seconds on Python 3.12. Five packages used 17 MB afterward, and import OpenSSL succeeded in 0.30 seconds. pip-audit reported zero known vulnerabilities. The package declares 7 direct dependency entries, requires Python >=3.9, is pure Python, includes py.typed, and reports Apache License 2.0.
cryptography provides the native OpenSSL binding beneath this pure-Python layer. Common targets get wheels; uncommon platforms may need cryptography's compiler, Rust, and system-library prerequisites. Dependency resolution can fail when another package pins cryptography outside pyOpenSSL's supported range. Release 26.4.0 supports cryptography through 50.x. pyOpenSSL itself needs no service credential or global configuration file.
Finish configuring SSL.Context before it creates any Connection. Current 26.x code rejects unsafe mutation after that point. A Session may only be reused with a compatible context. TLS clients must set SNI before the handshake and run hostname identity checks in addition to chain verification. Returning true from every verify callback turns a failed verification into acceptance.
Memory-BIO and nonblocking loops must expect WantReadError during writes and WantWriteError during reads, then drain or feed ciphertext before retrying. ZeroReturnError represents an orderly TLS close. Convert peer certificates to cryptography objects at the boundary. New certificate, CSR, and key code belongs on cryptography because the matching OpenSSL.crypto APIs are deprecated or already gone.
Patterns
Start a TLS client with chain checks tls-client-connection
import socket
from OpenSSL import SSL
ctx = SSL.Context(SSL.TLS_CLIENT_METHOD)
ctx.set_verify(SSL.VERIFY_PEER | SSL.VERIFY_FAIL_IF_NO_PEER_CERT)
ctx.set_default_verify_paths()
ctx.set_min_proto_version(SSL.TLS1_2_VERSION)
sock = socket.create_connection(("example.com", 443))
conn = SSL.Connection(ctx, sock)
conn.set_tlsext_host_name(b"example.com") # SNI
conn.set_connect_state()
conn.do_handshake()SNI must be set before do_handshake(). Chain trust does not establish that the certificate names example.com.
Check the requested DNS identity verify-hostname
from cryptography.x509 import DNSName, ExtensionOID
cert = conn.get_peer_certificate(as_cryptography=True)
san = cert.extensions.get_extension_for_oid(
ExtensionOID.SUBJECT_ALTERNATIVE_NAME).value
names = san.get_values_for_type(DNSName)
if not any(n == "example.com" or
(n.startswith("*.") and "example.com".endswith(n[1:]))
for n in names):
conn.close()
raise SSL.Error("hostname mismatch")This sketch omits full wildcard, IDNA, and IP-address rules. Use a maintained identity checker for arbitrary hostnames.
Load a server key and chain tls-server-context
from OpenSSL import SSL
ctx = SSL.Context(SSL.TLS_SERVER_METHOD)
ctx.use_certificate_chain_file("fullchain.pem")
ctx.use_privatekey_file("privkey.pem")
ctx.check_privatekey() # raises if key does not match cert
ctx.set_min_proto_version(SSL.TLS1_2_VERSION)
ctx.set_options(SSL.OP_NO_COMPRESSION | SSL.OP_CIPHER_SERVER_PREFERENCE)check_privatekey raises on a mismatch. Complete all context mutation before using it to create a Connection.
Switch contexts from the SNI name sni-callback
contexts = {b"a.example.com": ctx_a, b"b.example.com": ctx_b}
def pick_cert(connection):
name = connection.get_servername()
chosen = contexts.get(name)
if chosen is not None:
connection.set_context(chosen)
main_ctx.set_tlsext_servername_callback(pick_cert)get_servername returns bytes or None. The main context remains the fallback when a client sends no matching SNI.
Choose HTTP protocol through ALPN alpn-negotiation
# client
client_ctx.set_alpn_protos([b"h2", b"http/1.1"])
# ... after handshake
print(conn.get_alpn_proto_negotiated()) # b'h2'
# server
def choose(connection, protos):
return b"h2" if b"h2" in protos else b"http/1.1"
server_ctx.set_alpn_select_callback(choose)ALPN values are bytes. A server callback must select from the offered list or abort negotiation.
Run a handshake without a socket memory-bio
from OpenSSL import SSL
conn = SSL.Connection(ctx, None) # no socket
conn.set_connect_state()
while True:
try:
conn.do_handshake()
break
except SSL.WantReadError:
transport.send(conn.bio_read(65536)) # flush outbound bytes
conn.bio_write(transport.recv(65536)) # feed inbound bytesA WantReadError can still leave bytes waiting in bio_read(). Send them, feed new ciphertext with bio_write(), and retry.
Retry nonblocking reads on either direction nonblocking-io
import select
from OpenSSL import SSL
sock.setblocking(False)
while True:
try:
data = conn.recv(4096)
break
except SSL.WantReadError:
select.select([sock], [], [], 5)
except SSL.WantWriteError:
select.select([], [sock], [], 5)
except SSL.ZeroReturnError:
data = b"" # clean TLS shutdown from the peer
breakTLS reads can require socket writability and writes can require readability. ZeroReturnError means the peer completed a clean TLS shutdown.
Convert the verified chain to cryptography inspect-peer-certificate
cert = conn.get_peer_certificate(as_cryptography=True)
print(cert.subject.rfc4514_string())
print(cert.not_valid_after_utc)
chain = conn.get_verified_chain(as_cryptography=True)
for c in chain or []:
print(c.issuer.rfc4514_string())as_cryptography=True keeps downstream certificate logic off the shrinking OpenSSL.crypto accessor surface.
Use the compatibility store verifier verify-chain-offline
from cryptography import x509
from OpenSSL.crypto import X509, X509Store, X509StoreContext, X509StoreContextError
store = X509Store()
store.add_cert(X509.from_cryptography(x509.load_pem_x509_certificate(root_pem)))
leaf = X509.from_cryptography(x509.load_pem_x509_certificate(leaf_pem))
try:
X509StoreContext(store, leaf, chain=[intermediate]).verify_certificate()
except X509StoreContextError as exc:
print("invalid:", exc)X509StoreContext is a legacy compatibility path. Prefer cryptography's verification APIs for new offline checks.
Write TLS secrets for packet debugging keylog-callback
def keylog(connection, line):
with open("/tmp/sslkeys.log", "ab") as fh:
fh.write(line + b"\n")
ctx.set_keylog_callback(keylog)Anyone holding this file can decrypt the captured sessions. Restrict access and delete it when the diagnostic run ends.
Enforce mutual TLS chain verification client-certificates
def verify_cb(conn, cert, errnum, depth, ok):
if not ok:
print("chain error", errnum, "at depth", depth)
return bool(ok)
server_ctx.set_verify(
SSL.VERIFY_PEER | SSL.VERIFY_FAIL_IF_NO_PEER_CERT | SSL.VERIFY_CLIENT_ONCE,
verify_cb,
)
server_ctx.load_verify_locations("client-ca.pem")The callback's return value controls acceptance. Returning True when ok is false overrides OpenSSL's chain failure.
Replace OpenSSL.crypto certificate parsing migrate-off-crypto
# old
from OpenSSL import crypto
cert = crypto.load_certificate(crypto.FILETYPE_PEM, pem_bytes)
subject = cert.get_subject().CN
# current
from cryptography import x509
from cryptography.x509.oid import NameOID
cert = x509.load_pem_x509_certificate(pem_bytes)
subject = cert.subject.get_attributes_for_oid(NameOID.COMMON_NAME)[0].valueRecent 26.x releases removed request and extension wrappers. Turn pyOpenSSL deprecations into test failures while migration is in progress.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| cryptography | PyPI | Use it for certificate, key, signing, serialization, and verification work that pyOpenSSL is retiring. |
| truststore | PyPI | Use it when an ssl-based client should consult the operating system trust store. |
| service-identity | PyPI | Use it when hostname or service identity checks are the missing layer in a TLS integration. |
More security guides
cryptography · pyjwt · jose · dompurify · requests-oauthlib · jsonwebtoken · the whole shelf →
How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.

