mrkeyoor.com_
Sun 20 Sept 02:40 UTC
PyPISecurityupdated 19 Sept 2026

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.

Verdict

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

Lab card: what happened when we installed pyOpenSSLScreenshot of pyOpenSSL documentation
Install✓ · 0.3s5 packages on disk · 17 MB
Importimport OpenSSL in 0.30s · pure Python · py.typed · requires Python >=3.9
Known vulns0(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

API stability3/5SSL.Context and SSL.Connection keep their central roles, but 26.x now enforces context immutability after connection creation and rejects sessions from incompatible contexts. OpenSSL.crypto is much less stable: certificate request and extension wrappers are gone, while X509 mutation and key APIs are deprecated. TLS users can upgrade deliberately; certificate-management users need an active migration plan.
Docs4/5The official site documents SSL objects, callbacks, exceptions, constants, crypto compatibility, and every release. Its README plainly directs non-TLS work to cryptography, and the changelog names removals and replacements. The reference is stronger than the tutorials: a complete client still requires readers to combine SNI, chain verification, hostname identity, and nonblocking state handling correctly.
Maintenance5/5PyPI lists 26.4.0, and GitHub shows a 2026-08-21 push with 86 open issues and pull requests. The Python Cryptographic Authority coordinates the package with cryptography, publishes explicit version bounds, and removed obsolete wrappers after deprecation. The 26.x line also shipped fixes for callback and buffer-safety vulnerabilities, evidence that native-boundary behavior receives active attention.
Ecosystem4/5The supplied snapshot is roughly 59.5 million downloads per week, and GitHub showed 944 stars. Networking frameworks and older integrations still expose pyOpenSSL types, while cryptography objects can cross the boundary through dedicated methods. New Python code often uses ssl for ordinary TLS and cryptography for X.509 work, so the package remains widely installed without being the default greenfield choice.

Discussed on

  1. 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
Skip it if

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 bytes

A 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
        break

TLS 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].value

Recent 26.x releases removed request and extension wrappers. Turn pyOpenSSL deprecations into test failures while migration is in progress.

Alternatives

PackageRegistryPick it when
cryptographyPyPIUse it for certificate, key, signing, serialization, and verification work that pyOpenSSL is retiring.
truststorePyPIUse it when an ssl-based client should consult the operating system trust store.
service-identityPyPIUse 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.