Django RSA private-key authentication helpers and middleware
Project description
ecp-lib
ecp-lib is a Django library for authentication using username + password + private.pem.
It generates RSA keys, stores the user’s public_key, and provides middleware that verifies the uploaded private key matches the key in the database.
What the library can do
- generate a
private_key/public_keypair - store the
public_keyin theECPKeymodel - read
private.pemfrom upload - validate the
username/password/private_keycombination - reject invalid login requests at the middleware level
- log whether the request reached the middleware at all and at which step it failed
What the library does not do
- does not provide ready-made
viewsorurls - does not log in the user by itself
- does not store
private.pemon the server - does not implement a challenge-response protocol
Installation
pip install ecp-lib
pip install "ecp-lib[django]"
Public API
from ecp_lib import (
ECPKey,
ECPMiddleware,
authenticate_with_private_key,
create_challenge,
create_user_keys,
generate_keys,
read_private_key,
sanitize,
sign,
validate_public_key,
verify,
)
The main Django-flow uses:
Main Django flow
Main flow
1. Registration
After creating a user, call create_user_keys(user).
The function:
- generates a new RSA key pair
- stores the
public_keyinECPKey - returns the
private_keyas a PEM string
Typical approach: provide this PEM to the user as a private.pem file.
from django.http import HttpResponse
from ecp_lib.auth import create_user_keys
def registration_success(request, user):
private_key = create_user_keys(user)
response = HttpResponse(private_key, content_type="application/x-pem-file")
response["Content-Disposition"] = 'attachment; filename="private.pem"'
return response
2. Login
The login form submits:
usernamepassword- a
private_keyorprivate_key_file
In the view, you can read the PEM and verify it using the helper:
from django.contrib.auth import login
from django.shortcuts import redirect, render
from ecp_lib.auth import authenticate_with_private_key, read_private_key
def login_view(request):
if request.method == "POST":
private_key = read_private_key(request.FILES["private_key_file"])
user, error = authenticate_with_private_key(
request=request,
username=request.POST["username"],
password=request.POST["password"],
private_key=private_key,
)
if error:
return render(request, "login.html", {"error": error}, status=400)
login(request, user)
return redirect("dashboard")
return render(request, "login.html")
authenticate_with_private_key(...):
- validates the input data
- checks
username/passwordusing Django’sauthenticate() - retrieves the user’s
public_keyfromECPKey - creates an internal payload
- signs it with the provided
private_key - verifies the signature using the stored
public_key
Returns:
(user, None)on success(None, "error text")on failure
Middleware
ecp_lib/middleware.py acts as an early guard for POST requests.
The middleware triggers when it detects:
usernamepasswordprivate_keyas a text field or aprivate_keyfile- or a
private_key_file
Supported content types:
application/x-www-form-urlencodedmultipart/form-dataapplication/json
What it does:
- checks that the request is a
POST - reads
username,password, and the private key - retrieves the user’s
public_keyfrom the database - generates a signature using the provided private key
- verifies the signature with the
public_key - returns
403on failure - allows the request to proceed to the view on success
Important:
- the middleware does not create a user session
- the middleware does not replace
django.contrib.auth.login - the middleware only blocks invalid requests before they reach the view
Django integration
In settings.py:
INSTALLED_APPS = [
"django.contrib.auth",
"django.contrib.contenttypes",
"ecp_lib",
]
MIDDLEWARE = [
"...",
"ecp_lib.middleware.ECPMiddleware",
]
After that, run the migrations:
python manage.py migrate
Middleware Logging
To see if the middleware is triggered, add a logger in settings.py:
LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"handlers": {
"console": {
"class": "logging.StreamHandler",
},
},
"loggers": {
"ecp_lib.middleware": {
"handlers": ["console"],
"level": "DEBUG",
"propagate": False,
},
},
}
The logs will show:
- that the request reached the middleware
- why the middleware allowed the request
- why the middleware rejected the request
- whether the verification succeeded
Cryptographic Helpers
generate_keys()
from ecp_lib.crypto import generate_keys
private_key, public_key = generate_keys()
- returns PEM strings
- generates only RSA keys
- minimum key length:
2048
sign() & verify()
from ecp_lib.crypto import sign, verify
signature = sign(private_key, "hello")
is_valid = verify(public_key, "hello", signature)
The library uses RSA-PSS with SHA-256.
create_challenge() & verify_challenge()
These are auxiliary helpers for testing or local verification of key pairs. They are not part of the main login flow via the HTML form.
Validation
ecp_lib/validators.py contains:
sanitize(value)validate_username(username)validate_public_key(public_key)
It validates:
- the type and non-emptiness of the value
- absence of dangerous control characters
- PEM format of the
public_key - RSA key type
- minimum key length of
2048
Model
ECPKey stores:
userasOneToOneField;public_keyasTextField;created_atasDateTimeField(auto_now_add=True).
Only the public_key is stored on the server.
Tests
Run:
pytest -q
Coverage:
- key generation
- signing and verification
- storing the
public_key - reading
private.pem - helpers from
auth.py - middleware for form POST
- middleware for JSON POST
Security
- do not store
private.pemin the database - provide the
private.pemto the user only once after registration - ensure only a valid
public_keyis stored in the database - use HTTPS, since the password and key file are sent to the server
- place the middleware as an early barrier, but not as a replacement for verification in the view
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ecp_lib-1.0.3.tar.gz.
File metadata
- Download URL: ecp_lib-1.0.3.tar.gz
- Upload date:
- Size: 15.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1b64b499d0b9ad2527c280437a264e71cb24785a71580aa250c238368d967380
|
|
| MD5 |
99ae96bdbb27c04f13aed07cd4c660ea
|
|
| BLAKE2b-256 |
781ef63c2a836ddc857f6aab69a632362c03d1764e033ac5ada589116c6e4694
|
Provenance
The following attestation bundles were made for ecp_lib-1.0.3.tar.gz:
Publisher:
publish-pypi.yml on T0ks1k24/django-pub-sub
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ecp_lib-1.0.3.tar.gz -
Subject digest:
1b64b499d0b9ad2527c280437a264e71cb24785a71580aa250c238368d967380 - Sigstore transparency entry: 1190710422
- Sigstore integration time:
-
Permalink:
T0ks1k24/django-pub-sub@64da0e04fc1e7f75d700daa4c01fe71c95a87ded -
Branch / Tag:
refs/tags/ecp-lib-v1.0.3 - Owner: https://github.com/T0ks1k24
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@64da0e04fc1e7f75d700daa4c01fe71c95a87ded -
Trigger Event:
release
-
Statement type:
File details
Details for the file ecp_lib-1.0.3-py3-none-any.whl.
File metadata
- Download URL: ecp_lib-1.0.3-py3-none-any.whl
- Upload date:
- Size: 12.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dd8ea89ff3e89ecf6f100f4a72c4b9ed518577c307c3b89f6708626d5f8540c7
|
|
| MD5 |
7b1952d9c427aafe98cffa3f0e2d4fc9
|
|
| BLAKE2b-256 |
28d384998b396f5f259a309f774bdf677c45707ea31b26eda3a531ac541db383
|
Provenance
The following attestation bundles were made for ecp_lib-1.0.3-py3-none-any.whl:
Publisher:
publish-pypi.yml on T0ks1k24/django-pub-sub
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ecp_lib-1.0.3-py3-none-any.whl -
Subject digest:
dd8ea89ff3e89ecf6f100f4a72c4b9ed518577c307c3b89f6708626d5f8540c7 - Sigstore transparency entry: 1190710426
- Sigstore integration time:
-
Permalink:
T0ks1k24/django-pub-sub@64da0e04fc1e7f75d700daa4c01fe71c95a87ded -
Branch / Tag:
refs/tags/ecp-lib-v1.0.3 - Owner: https://github.com/T0ks1k24
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@64da0e04fc1e7f75d700daa4c01fe71c95a87ded -
Trigger Event:
release
-
Statement type: