openssl-psk
TLS-PSK (pre-shared key) socket wrapper for Python 3.10+.
No compiled extensions — pure Python using the standard ssl module and ctypes.
pip install openssl-psk
Why this exists
The standard ssl module has no PSK support below Python 3.13.
openssl-psk works around this limitation by:
- Calling
libssl'sSSL_CTX_set_psk_*_callbackdirectly viactypeson Python 3.10–3.12 - Using the native
SSLContext.set_psk_*_callback()API transparently on Python 3.13+
No C extension to compile. Just pip install.
Quick start
Client
import socket
from openssl_psk import wrap_socket_client
tcp = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tcp.connect(("192.168.1.1", 4116))
# simplest form — just the key
ssl_sock = wrap_socket_client(tcp, psk=b"84155C93FDB08FB384B75C44A7D3B625")
ssl_sock.sendall(b"GET /resource HTTP/1.1\r\nHost: 192.168.1.1\r\n\r\n")
print(ssl_sock.recv(4096))
ssl_sock.close()
Server
import socket
from openssl_psk import wrap_socket_server
PSK_DB = {
b"device-001": b"84155C93FDB08FB384B75C44A7D3B625",
b"device-002": b"0AE9DF505E3D3B9A5305409550D42C96",
}
tcp = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tcp.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
tcp.bind(("0.0.0.0", 4116))
tcp.listen(5)
while True:
conn, addr = tcp.accept()
ssl_sock = wrap_socket_server(
conn,
psk=lambda identity: PSK_DB[identity],
hint="my-server", # optional identity hint sent to clients
)
data = ssl_sock.recv(4096)
ssl_sock.sendall(b"HTTP/1.1 200 OK\r\n\r\n")
ssl_sock.close()
API
wrap_socket_client(sock, psk, ciphers=..., server_hostname=None)
| Parameter | Type | Description |
|---|---|---|
sock |
socket.socket |
Connected TCP socket to wrap |
psk |
bytes | tuple | callable |
Pre-shared key — see accepted shapes below |
ciphers |
str |
OpenSSL cipher string (default: PSK-AES128-CBC-SHA256) |
server_hostname |
str | None |
SNI hostname passed to wrap_socket |
Accepted psk shapes:
# 1. Just the key — identity sent to server is b''
wrap_socket_client(sock, psk=b"mypsk")
# 2. Key + client identity
wrap_socket_client(sock, psk=(b"mypsk", b"myidentity"))
# 3. Callable: server hint → key
PSK_FOR = {b"server1": b"abcdef", b"server2": b"123456"}
wrap_socket_client(sock, psk=lambda hint: PSK_FOR[hint])
Returns an ssl.SSLSocket after completing the handshake.
wrap_socket_server(sock, psk, hint=None, ciphers=...)
| Parameter | Type | Description |
|---|---|---|
sock |
socket.socket |
Accepted TCP socket to wrap (from socket.accept()) |
psk |
bytes | callable |
Pre-shared key — see accepted shapes below |
hint |
str | None |
PSK identity hint sent to clients (optional) |
ciphers |
str |
OpenSSL cipher string (default: PSK-AES128-CBC-SHA256) |
Accepted psk shapes:
# 1. Just the key — same key accepted for every client identity
wrap_socket_server(sock, psk=b"mypsk")
# 2. Callable: client identity → key (raise to reject)
PSK_FOR = {b"clientA": b"abcdef", b"clientB": b"123456"}
wrap_socket_server(sock, psk=lambda identity: PSK_FOR[identity])
Returns an ssl.SSLSocket after completing the handshake.
When using a callable, raise any exception to reject the client.
Compatibility
| Python | Mechanism | CPython only? |
|---|---|---|
| 3.10 | ctypes → libssl |
Yes |
| 3.11 | ctypes → libssl |
Yes |
| 3.12 | ctypes → libssl |
Yes |
| 3.13+ | ssl.SSLContext.set_psk_*_callback() |
No |
The ctypes path reads the SSL_CTX* pointer out of CPython's PySSLContext
struct at a fixed offset that has been stable across CPython 3.x. It will
raise a RuntimeError with a clear message if the pointer is NULL, so you
will know immediately if anything changes in a future CPython release.
Requires OpenSSL ≥ 1.0.1 (for PSK cipher support).
Running the tests
pip install pytest
pytest
License
MIT — see LICENSE.
Metadata
Release files for openssl-psk 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| openssl_psk-0.1.1.tar.gz | 8.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| openssl_psk-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 16.2 kB
Release files / openssl_psk-0.1.1.tar.gz
| Download URL | openssl_psk-0.1.1.tar.gz |
|---|---|
| Size | 8.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
7d1e4ec573ce806f77df12123cb718f84953f0023c38a7bbd92b67ec84fcae4e
|
|
BLAKE2b-256 checksum How to use checksums |
a1e1eb2a3f3591c16920510da017bab87b52718ac378048248df707725ebdf80
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on May 25, 2026.
Transparency logRelease files / openssl_psk-0.1.1-py3-none-any.whl
| Download URL | openssl_psk-0.1.1-py3-none-any.whl |
|---|---|
| Size | 7.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
0a4396ef6285620e145e2667aeeb78c56499e875ec5e5fbaf50c8b57f918c2fd
|
|
BLAKE2b-256 checksum How to use checksums |
3c2fb0c46c5551c6d5cd7e9d02fdb1df180651df9009dd397d2d189d8ca5800a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on May 25, 2026.
Transparency log