# pyOpenSSL: the Python wrapper for the OpenSSL cases cryptography leaves out

> pyOpenSSL is a high-level Python wrapper around a subset of the OpenSSL library, offering connection objects, Python written callbacks and OpenSSL's own error codes. The Python Cryptographic Authority itself recommends using pyca/cryptography wherever possible, and says to drop this dependency for anything that is not a TLS connection.

**pyca/pyopenssl** — A Python wrapper around the OpenSSL library

- Repository: https://github.com/pyca/pyopenssl
- Website: https://pyopenssl.org/
- Stars: 945 · Forks: 443
- Language: Python
- License: Apache-2.0
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/pyca-pyopenssl

## A subset binding, deliberately smaller than OpenSSL

pyOpenSSL is described as a high-level wrapper around a subset of the OpenSSL library, and the word subset is the most important word in that sentence. It is not a complete mapping of the C API. It is the part of OpenSSL that a Python program can use without dropping down to ctypes or writing C.

The project belongs to the Python Cryptographic Authority, the same organisation behind cryptography and certifi, and the documentation lives at pyopenssl.org with a stable branch of the docs. The repository is a conventional Python source layout with a src directory, a tests directory, a doc directory, a noxfile.py for driving test sessions, both setup.py and setup.cfg alongside pyproject.toml, and an INSTALL.rst at the root for installation notes.

The maintenance picture is straightforward. The last push to the repository was on 2026-10-14, the repository is not archived, and no GitHub releases are published, so distribution happens through the package index rather than through release assets. The licence is Apache 2.0.

For most readers, the first thing to know is stated in the README itself, in a note at the top. The Python Cryptographic Authority strongly suggests using pyca/cryptography where possible, and says that if you are using pyOpenSSL for anything other than making a TLS connection, you should move to cryptography and drop the pyOpenSSL dependency. That guidance is the most useful sentence the project offers, and any evaluation should start from it.

## SSL.Connection, Python callbacks, and OpenSSL error codes

The README lists what the wrapper includes, and three items stand out.

First, SSL.Connection objects, wrapping the methods of Python's portable sockets. The point of the wrapper is that you get an object that behaves like a socket but talks to OpenSSL underneath, so existing code that treats a socket as a file-like object can be adapted without restructuring around a foreign API.

Second, callbacks written in Python. This is the feature that most often justifies the dependency. Hooks such as verification callbacks, verification error callbacks, data callbacks and the various passphrase and info callbacks are implemented in Python and invoked from native code, which is something a hand written ctypes binding rarely gets right and something cryptography does not expose in the same shape. When you need to decide in Python whether to accept a certificate, or to serve a different key per SNI name, this is the reason people reach for pyOpenSSL.

Third, an extensive error-handling mechanism mirroring OpenSSL's error codes. The library carries the OpenSSL error queue into Python rather than flattening it, so a failure carries the native error information with it. That is valuable when you are diagnosing a handshake failure, and it is also the reason pyOpenSSL's exceptions do not look like the rest of your application's exception handling.

Together these three explain the library's remaining audience: a narrow band of code that needs OpenSSL specifically, not merely cryptography.

## The maintainers tell you when to leave

The README note deserves closer reading, because it is unusual for a library to advertise its own replacement. The note says the Python Cryptographic Authority strongly suggests the use of pyca/cryptography where possible, and that if you are using pyOpenSSL for anything other than making a TLS connection, you should move to cryptography and drop your pyOpenSSL dependency.

That is a narrow carve-out, and it is a useful one. It says the maintained use case is TLS connections, and it says the rest is legacy. cryptography is a higher level library with a Rust core, it offers certificate and key handling, symmetric primitives, and a modern TLS stack built on its own backend, so the overlap with pyOpenSSL is small outside of connection setup. Where pyOpenSSL still wins is in the callbacks and in the raw error information, and in a handful of OpenSSL specific behaviours that cryptography does not expose.

The practical consequence for an adopter is that pyOpenSSL should be a deliberate, narrow dependency. A new project that needs a TLS client should start with cryptography. A project that already depends on pyOpenSSL for one callback can keep it, but should not add new certificate handling on top of it, because the two libraries will then hold two different models of the same objects and the mismatch shows up as confusing errors at the boundary.

The project also keeps the discussion channels open, with an issue tracker, a cryptography-dev mailing list for both user and development discussions, and the #pyca channel on irc.libera.chat.

## The version number is parsed out of a source file at build time

The packaging is unusual enough to be worth reading, because it tells you how the project ties itself to the OpenSSL build on a given machine.

setup.py does not hardcode a version. It computes a path to the metadata file inside the package, os.path.join("src", "OpenSSL", "version.py"), reads it, and extracts the dunder style attributes from it with a regular expression that searches for a name and value pair. The values it pulls out are the ones the setup call needs, including the title used as the package name, the version, the summary used as the description, the author, the email, the url, and the license.

```python
META_PATH = os.path.join("src", "OpenSSL", "version.py")
```

The setup call then reads its arguments from that file.

```python
setup(
    name=find_meta("title"),
    version=find_meta("version"),
    description=find_meta("summary"),
```

The long description is assembled in the same file, concatenating the README with a section of the changelog pulled out by a regular expression and a link to the full changelog on the documentation site.

The reason to care is that the version reported by pyOpenSSL is not only a package version. It is the record of which OpenSSL the wrapper was built against, and it is the first thing to compare when a symbol is missing or a behaviour differs from the system OpenSSL. The source tree at src/OpenSSL/version.py is where that record lives.

## Strict typing, ruff, pytest and nox drive the test suite

The project configuration shows the standards the code is held to, which matters for a library that other people depend on for security sensitive work.

The typing configuration is strict mypy. warn_unused_configs is on, strict is on, and strict_bytes is on, the last of which is significant for a library whose API moves bytes and str around constantly, since it forces the byte and string distinction to be explicit. There are per module overrides: the OpenSSL package itself has warn_return_any disabled, and the cryptography, cffi and pretend modules are set to ignore missing imports, which is the normal way to type a library that sits on top of another one.

Lint and format are handled by ruff, with a rule selection of E, F, I, W, UP and RUF, a line length of 79, and isort configured to treat OpenSSL and tests as first party code. The target version is still py37, with a comment in the configuration explaining that it should be removed when the python requires metadata moves from setup.py into pyproject.toml.

Pytest is configured with addopts set to report skipped tests and to enforce strict markers, and with the test paths set to the tests directory. The coverage configuration is likewise thorough, with branch coverage enabled, relative file paths, and the source set to the OpenSSL package and the tests, plus path mappings so that coverage collected inside a nox environment is attributed back to the source tree.

```toml
[tool.mypy]
warn_unused_configs = true
strict = true
strict_bytes = true
```

The noxfile.py at the root is what ties those pieces together, running the same sessions locally and in CI, across interpreters and against the cryptography package in multiple configurations. For a user of the library this matters in a narrow way: it tells you the project runs its own test matrix against several interpreters, so an upgrade of cryptography or of the underlying OpenSSL is exercised rather than assumed.

## No GitHub releases, so the upgrade path is a dependency bump

The repository publishes no GitHub releases, which is normal for a library distributed through a package index and not a sign of abandonment. The maintenance signal is the branch activity: the last push was on 2026-10-14, and the repository is not archived. The changelog is kept in CHANGELOG.rst at the root, and the setup script extracts the most recent entry from it to build the release description on the package index, so the changelog and the published metadata cannot drift apart.

For a consumer, that means the upgrade path is the ordinary one. You track the package index version, and the interesting question is not when a release happened but what it changed relative to the OpenSSL build you ship, since a binding tracks the library it wraps.

The licence is Apache 2.0, with the licence file at the root of the repository. That is a permissive grant with an explicit patent clause, and it does not impose copyleft obligations on code that merely imports the library. The practical licence caveat is the one that applies to any wrapper: the OpenSSL library itself is distributed under its own terms, so the licence of the native code you ship alongside the Python package is a separate question from the licence of the Python code, and the repository includes a LICENSE file for the wrapper and a separate NOTICE.md for attribution purposes.

There is also a genuine operational cost to the dependency. Because pyOpenSSL is a binding to a native library, the versions of the two have to be compatible, and a wheel that bundles a particular OpenSSL build will not necessarily match the system one. Teams that standardise on system OpenSSL, a particular distribution, and a container base image need to decide which of those is the source of truth, because a mismatch surfaces as a missing symbol or an unexpected error code rather than a clean import failure.

## Where pyOpenSSL is the wrong tool

There are two wrong answers to this question, and they are different.

The first wrong answer is using pyOpenSSL for anything other than a TLS connection. The project tells you to move to cryptography in that case, and the reasons are practical rather than ideological. cryptography is actively developed for general use, it has a broader API for keys, certificates and symmetric primitives, and it does not bind you to a particular native library version. Reaching for pyOpenSSL because you know its API, or because a tutorial used it, is how a legacy dependency enters a new project and stays for a decade.

The second wrong answer is reaching for cryptography when you actually need OpenSSL specific behaviour. There are cases where a Python written callback, or the exact contents of the OpenSSL error queue, is the requirement rather than a convenience, and cryptography does not model those. In those cases pyOpenSSL is the right tool and the cost of the native dependency is the price of admission.

There is a third, quieter failure mode: mixing the two libraries in the same object graph. Certificates and keys created by one library are not the objects the other expects, and code that converts between them ends up with serialisation steps, lossy field mappings and error handling that is much worse than either library used alone.

Finally, the error model itself is a cost. The extensive error handling that mirrors OpenSSL's error codes is a feature when you are debugging a handshake and a nuisance when you are writing application code, since the exceptions carry native error detail that most code will never inspect and cannot sensibly map to user facing messages.

## Conclusion

Adopt pyOpenSSL when you need OpenSSL behaviour that cryptography does not expose, in particular a TLS connection with a Python written callback, and you accept a dependency on a subset binding rather than the full library. Do not adopt it for certificates, key management, signing or anything else that pyca/cryptography covers, because the project itself tells you to move. Verify first which OpenSSL version your platform provides, since the wrapper only exposes the parts of the library that exist in it, and check the version metadata in src/OpenSSL/version.py against the OpenSSL build you ship, since a mismatch there shows up as a missing symbol rather than a clear error.

## FAQ

### What is pyOpenSSL and what is it used for?

pyOpenSSL is a high-level Python wrapper around a subset of the OpenSSL library, offering SSL.Connection objects that wrap the methods of Python's portable sockets, callbacks written in Python, and an error-handling mechanism mirroring OpenSSL's error codes. The licence is Apache 2.0.

### Should I use pyOpenSSL or pyca/cryptography?

The README says the Python Cryptographic Authority strongly suggests using pyca/cryptography where possible, and that if you are using pyOpenSSL for anything other than making a TLS connection you should move to cryptography and drop the pyOpenSSL dependency.

### How do I install pyOpenSSL?

The repository keeps installation notes in the INSTALL.rst file at its root, and the project documentation is published at pyopenssl.org with a stable branch of the docs. The package is distributed through the usual Python package index, and the repository itself publishes no GitHub releases.

### How does pyOpenSSL report the OpenSSL version it was built against?

The version is kept in src/OpenSSL/version.py, and setup.py parses that file at build time with a regular expression to obtain the package title, version, summary, author, email, url and licence. The setup script also extracts the newest changelog entry from CHANGELOG.rst to build the long description.

### What does pyOpenSSL use for testing and typing?

The project runs strict mypy with strict_bytes enabled, lints and formats with ruff using a line length of 79, and tests with pytest configured for strict markers. A noxfile.py at the root drives the test sessions locally and in CI, and coverage is configured with branch coverage and path mappings back to the source tree.

### Is pyOpenSSL still maintained?

The repository is not archived and the last push was on 2026-10-14, although no GitHub releases are published, so updates arrive as ordinary package version bumps rather than release assets.

## Sources

- [Issues](https://github.com/pyca/pyopenssl/issues)
- [License: Apache-2.0](https://github.com/pyca/pyopenssl/blob/main/LICENSE)
- [Project website](https://pyopenssl.org/)
- [pyca/pyopenssl on GitHub](https://github.com/pyca/pyopenssl)
- [README](https://github.com/pyca/pyopenssl/blob/main/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/pyca-pyopenssl
