Amnesia honeywords implementation for Django — breach detection without a separate honeychecker.
Project description
django-amnesia-honeywords
A Django authentication backend implementing the Amnesia honeywords scheme for breach detection — without requiring a separate honeychecker service.
When attackers crack a credential database and attempt online login with a stolen credential, the backend detects whether the submitted password is a marked credential or an unmarked decoy (honeyword).
Overview
For each user, instead of storing a single password hash, the system stores k hashed candidates (1 real + k-1 honeywords). Each candidate carries a marked boolean flag. The real password is always marked; other candidates are marked with probability p_mark.
On login:
- Find which candidate matches the submitted password
- If no match → reject (invalid password)
- If match but unmarked → breach detected (attacker using a stolen credential)
- If match and marked → success; with probability
p_remark, re-mark other candidates
The key advantage: no separate Honeychecker service is needed. The system never stores which index is the real password — security comes from the probabilistic marking scheme.
Help this research
This package is part of an MSc thesis at TU Delft investigating honeyword adoption in practice. If you tried (or chose not to) deploy this package, please take 2–3 minutes to fill in the anonymous survey — your experience directly shapes the research findings.
All responses are anonymous. No personal data is collected.
Features
- Drop-in Django Authentication Backend — seamlessly integrates with Django's auth system
- Honeyword Generation — simple mutation-based generator (extensible for custom generators)
- Event Logging — tracks all authentication attempts with IP and User-Agent
- Django Signals — hook into honeyword detection events
- Configurable Policies — choose between logging, password reset, or account lockout on detection
- No Honeychecker Required — Amnesia scheme eliminates the need for a separate service
Architecture
┌─────────────────────────────────┐
│ Django App │
│ │
│ ┌───────────────────────────┐ │
│ │ HoneywordsBackend │ │
│ │ (authentication) │ │
│ └───────────┬───────────────┘ │
│ │ │
│ ┌───────────▼───────────────┐ │
│ │ amnesia_check() │ │
│ │ - find matching cred │ │
│ │ - check marked flag │ │
│ │ - probabilistic remark │ │
│ └───────────┬───────────────┘ │
│ │ │
│ ┌───────────▼───────────────┐ │
│ │ AmnesiaSet │ │
│ │ └─ k AmnesiaCredentials │ │
│ │ (hash + marked flag) │ │
│ └───────────────────────────┘ │
└─────────────────────────────────┘
Installation
pip install django-amnesia-honeywords
What this package does (and does not do)
What it does
- Provides a Django authentication backend (
django_honeywords.backend.HoneywordsBackend) that checks passwords against an Amnesia honeyword set. - Stores
kpassword candidates per user (hashed) with probabilistic marking; on login it can detect use of an unmarked candidate as a breach signal. - Logs outcomes (
real/honey/invalid) and emits ahoneyword_detectedsignal for alerting/automation.
What it does not do automatically
- Installing the package does not change your project's authentication by itself.
You must explicitly set
AUTHENTICATION_BACKENDSto useHoneywordsBackend. - It cannot initialize existing users from an already-hashed password. You need the user's plaintext password (signup / password change / controlled migration).
Important integration notes
- When you initialize honeywords for a user, this package sets the user's Django password to unusable (
set_unusable_password()) to reduce bypass risk ifModelBackendis enabled. - If you enable
django.contrib.auth.backends.ModelBackendalongsideHoneywordsBackend, then users without an AmnesiaSet can still authenticate using the default Django password backend. In production, prefer using onlyHoneywordsBackend, or ensure all users are initialized.
Or install from source:
git clone https://github.com/iliopdavid/django-amnesia-honeywords.git
cd django-amnesia-honeywords
pip install -e .
Quick Start
1. Add to Django Settings
INSTALLED_APPS = [
# ...
"django_honeywords.apps.DjangoHoneywordsConfig",
]
AUTHENTICATION_BACKENDS = [
"django_honeywords.backend.HoneywordsBackend",
]
# Honeywords configuration (all optional — sensible defaults provided)
HONEYWORDS = {
"AMNESIA_K": 20, # Number of candidates per user
"AMNESIA_P_MARK": 0.1, # Probability of marking a honeyword
"AMNESIA_P_REMARK": 0.01, # Probability of re-marking on success
"ON_HONEYWORD": "log", # "log" | "reset" | "lock"
"LOG_REAL_SUCCESS": False, # Log successful *marked* credential logins (real or marked honeyword)
"LOCK_BASE_SECONDS": 60, # Base lockout duration
"LOCK_MAX_SECONDS": 3600, # Maximum lockout duration
}
2. Run Migrations
python manage.py migrate django_honeywords
3. Initialize Users (Important)
This package cannot derive honeywords from an existing hash: you must initialize users while you have their plaintext password (signup, password change, migration script).
- Management command (for migration / admin scripts):
python manage.py amnesia_init_user <username> --password <password>
- Programmatic initialization:
from django_honeywords.amnesia_service import amnesia_initialize_from_settings
amnesia_initialize_from_settings(user, "real_password")
4. (Recommended) Remove ModelBackend in production
For most deployments, configure only the honeywords backend:
AUTHENTICATION_BACKENDS = [
"django_honeywords.backend.HoneywordsBackend",
]
If you keep ModelBackend enabled, make sure you understand the bypass implications for users who are not initialized.
Configuration Options
| Setting | Default | Description |
|---|---|---|
AMNESIA_K |
20 |
Number of candidate passwords per user (1 real + k-1 honeywords) |
AMNESIA_P_MARK |
0.1 |
Probability of marking each honeyword during initialization |
AMNESIA_P_REMARK |
0.01 |
Probability of re-marking other candidates on successful login |
ON_HONEYWORD |
"log" |
Action on honeyword detection: "log", "reset", or "lock" |
LOG_REAL_SUCCESS |
False |
Whether to log successful authentications with marked credentials |
LOCK_BASE_SECONDS |
60 |
Base duration for account lockout |
LOCK_MAX_SECONDS |
3600 |
Maximum lockout duration (exponential backoff capped here) |
Operational Notes
- System checks: run
python manage.py checkto surface configuration warnings (e.g., wildcard hosts, test hashers, backend fallbacks). - Event semantics: logging a “real” outcome corresponds to a marked credential login (real password or marked honeyword). Amnesia intentionally cannot distinguish those.
- Performance: authentication checks up to
kcandidates (linear scan). Choosekand password hasher parameters accordingly.
Components
Django Models
AmnesiaSet— links a user to their set ofkcandidates with marking parametersAmnesiaCredential— individual password hash withmarkedflag and indexHoneywordEvent— audit log of authentication attempts (outcome: real/honey/invalid)HoneywordUserState— tracks lockout and password-reset state per user
Services
amnesia_service.py
amnesia_initialize(user, password, k, p_mark, p_remark)— generate and store candidates for a useramnesia_initialize_from_settings(user, password)— same, using values fromHONEYWORDSsettingsamnesia_check(user, password)— returns"success","breach", or"invalid"
generator.py
SimpleMutationGenerator— basic character mutation generator for honeywords- Creates variants by randomly mutating single characters
- Extensible: implement your own generator with a
honeywords(real, k)method
backend.py
HoneywordsBackend— Django authentication backend- Authenticates users via
amnesia_check() - Enforces policy (log/reset/lock) on honeyword detection
- Fires
honeyword_detectedsignal on breach
- Authenticates users via
policy.py
is_locked(user)— check if user is currently locked outapply_reset(user)— mark user as requiring password resetapply_lock(user)— apply exponential backoff lockout
events.py
log_event(user, username, outcome, request)— record authentication attempt with metadata
Signals
Connect to the honeyword_detected signal to implement custom alerting:
from django_honeywords.signals import honeyword_detected
def on_honeyword(sender, user, username, request, event, **kwargs):
send_security_alert(
message=f"Honeyword detected for user {username}",
ip=event.ip_address,
user_agent=event.user_agent,
)
honeyword_detected.connect(on_honeyword)
Management Commands
amnesia_init_user
Initialize honeywords for an existing user:
python manage.py amnesia_init_user alice --password "SecurePass123"
Arguments:
username— username of the user--password— the user's real password (required)
Parameters k, p_mark, and p_remark are read from the HONEYWORDS settings.
Development
Running Tests
# Install dev dependencies
pip install -e ".[dev]"
# Run all tests
pytest
# Run specific test groups
pytest tests/test_amnesia_a1_models.py # Model creation
pytest tests/test_amnesia_a2_core.py # Core amnesia logic
pytest tests/test_amnesia_a3_backend.py # Authentication backend
pytest tests/test_amnesia_a4_command.py # Management command
Deployment Notes
- Do not deploy the
example_project/settings.pyconfiguration as-is. - Use
example_project/settings_prod.pyas a production settings template (setDJANGO_SECRET_KEYandDJANGO_ALLOWED_HOSTS). - Avoid enabling
django.contrib.auth.backends.ModelBackendin production unless you fully understand the bypass risk for users who were not initialized with anAmnesiaSet. - Do not use
MD5PasswordHasherin production (it is used only inexample_project/settings_test.pyto keep tests fast).
Recommended production policy:
- Prefer
ON_HONEYWORD = "lock"(or"log") unless you have a complete password-reset UX wired up. - If you set
ON_HONEYWORD = "reset", your application must provide password reset/change views and messaging so users can recover.
Documentation
- See
docs/deployment.mdfor a production deployment checklist. - See
docs/integration.mdfor guidance on initializing users during signup/password-change flows. - See
docs/releasing.mdfor GitHub + PyPI publishing steps.
Project Structure
django-amnesia-honeywords/
├── src/django_honeywords/
│ ├── apps.py # Django app config
│ ├── amnesia_service.py # Core amnesia service
│ ├── backend.py # Authentication backend
│ ├── conf.py # Settings with defaults
│ ├── events.py # Event logging
│ ├── generator.py # Honeyword generation
│ ├── models.py # Database models
│ ├── policy.py # Reset/lock policies
│ ├── signals.py # Django signals
│ └── management/commands/ # Management commands
├── tests/ # Test suite
└── example_project/ # Example Django project for testing
Security Considerations
-
Honeyword Quality: The included
SimpleMutationGeneratorcreates basic variants. For stronger security, implement a custom generator that produces indistinguishable honeywords. -
Audit Logging: All authentication attempts are logged to
HoneywordEvent. Regularly review these logs and set up alerts for honeyword detections. -
Parameter Tuning: The
p_markandp_remarkparameters control the trade-off between false positive rate and detection sensitivity. See the Amnesia paper for guidance on choosing values. -
Password Reset Flow: When
ON_HONEYWORD = "reset", users flagged withmust_resetshould be redirected to a password change page. Integrate this with your application's auth flow.
For Researchers
This package is a research artifact developed as part of an MSc thesis at Delft University of Technology:
Honeywords in Practice: Investigating Adoption and Barriers to Deployment David Iliopoulos, TU Delft, 2026
Research goals
The package serves two purposes:
- Demonstrate the deployment feasibility of the Amnesia honeyword scheme in a real-world Django context.
- Empirically study adoption friction — what makes developers adopt or avoid honeyword systems in practice.
Participate
If you installed, evaluated, or chose not to deploy this package, your experience is valuable data. Please fill in the anonymous survey:
→ Anonymous adoption survey (2–3 min)
Citation
If you use this package or reference it in academic work:
@software{iliopoulos2026honeywords,
author = {Iliopoulos, David},
title = {django-amnesia-honeywords},
year = {2026},
url = {https://github.com/iliopdavid/django-amnesia-honeywords}
}
References
- Juels, A., & Rivest, R. L. (2013). Honeywords: Making password-cracking detectable. ACM CCS 2013.
- Wang, K. C., & Reiter, M. K. (2021). Using amnesia to detect credential database breaches. In 30th USENIX Security Symposium (USENIX Security 21) (pp. 839-855).
License
MIT License
Project details
Release history Release notifications | RSS feed
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 django_amnesia_honeywords-0.0.3.tar.gz.
File metadata
- Download URL: django_amnesia_honeywords-0.0.3.tar.gz
- Upload date:
- Size: 24.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b0390ce8d74919e75d81c16990a7da0252f6888755d59520a2de79c157600b5a
|
|
| MD5 |
2680de0739d93d3ea393ffd7901f051a
|
|
| BLAKE2b-256 |
3536e219d7a9b66655dfcf08e32bead340cebb5812a72f030354b57cb4b3ba61
|
Provenance
The following attestation bundles were made for django_amnesia_honeywords-0.0.3.tar.gz:
Publisher:
publish.yml on iliopdavid/django-amnesia-honeywords
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
django_amnesia_honeywords-0.0.3.tar.gz -
Subject digest:
b0390ce8d74919e75d81c16990a7da0252f6888755d59520a2de79c157600b5a - Sigstore transparency entry: 1175497844
- Sigstore integration time:
-
Permalink:
iliopdavid/django-amnesia-honeywords@ab602cde486eb975970d8edb4f42796c9b2029af -
Branch / Tag:
refs/tags/v0.0.3 - Owner: https://github.com/iliopdavid
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@ab602cde486eb975970d8edb4f42796c9b2029af -
Trigger Event:
push
-
Statement type:
File details
Details for the file django_amnesia_honeywords-0.0.3-py3-none-any.whl.
File metadata
- Download URL: django_amnesia_honeywords-0.0.3-py3-none-any.whl
- Upload date:
- Size: 21.6 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 |
3848b7f030cd60cdb00d63e56dbde6a20428fce1d0a5840a0482c5cb46386e41
|
|
| MD5 |
89a0354a650b404416058f68de8c8f96
|
|
| BLAKE2b-256 |
7c390caa655a2e556e32cffcaadfd9ae229c90e6d94366a0455a3e22e57af5ba
|
Provenance
The following attestation bundles were made for django_amnesia_honeywords-0.0.3-py3-none-any.whl:
Publisher:
publish.yml on iliopdavid/django-amnesia-honeywords
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
django_amnesia_honeywords-0.0.3-py3-none-any.whl -
Subject digest:
3848b7f030cd60cdb00d63e56dbde6a20428fce1d0a5840a0482c5cb46386e41 - Sigstore transparency entry: 1175497858
- Sigstore integration time:
-
Permalink:
iliopdavid/django-amnesia-honeywords@ab602cde486eb975970d8edb4f42796c9b2029af -
Branch / Tag:
refs/tags/v0.0.3 - Owner: https://github.com/iliopdavid
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@ab602cde486eb975970d8edb4f42796c9b2029af -
Trigger Event:
push
-
Statement type: