Skip to main content

django-amnesia-honeywords

PyPI version CI License: MIT

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:

  1. Find which candidate matches the submitted password
  2. If no match → reject (invalid password)
  3. If match but unmarked → breach detected (attacker using a stolen credential)
  4. 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.

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 k password 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 a honeyword_detected signal 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_BACKENDS to use HoneywordsBackend.
  • 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 if ModelBackend is enabled.
  • If you enable django.contrib.auth.backends.ModelBackend alongside HoneywordsBackend, then users without an AmnesiaSet can still authenticate using the default Django password backend. In production, prefer using only HoneywordsBackend, 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")

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 check to 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 k candidates (linear scan). Choose k and password hasher parameters accordingly.

Components

Django Models

  • AmnesiaSet — links a user to their set of k candidates with marking parameters
  • AmnesiaCredential — individual password hash with marked flag and index
  • HoneywordEvent — 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 user
  • amnesia_initialize_from_settings(user, password) — same, using values from HONEYWORDS settings
  • amnesia_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_detected signal on breach

policy.py

  • is_locked(user) — check if user is currently locked out
  • apply_reset(user) — mark user as requiring password reset
  • apply_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.py configuration as-is.
  • Use example_project/settings_prod.py as a production settings template (set DJANGO_SECRET_KEY and DJANGO_ALLOWED_HOSTS).
  • Avoid enabling django.contrib.auth.backends.ModelBackend in production unless you fully understand the bypass risk for users who were not initialized with an AmnesiaSet.
  • Do not use MD5PasswordHasher in production (it is used only in example_project/settings_test.py to 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/configuration.md for the full settings reference.
  • See docs/deployment.md for a production deployment checklist.
  • See docs/integration.md for guidance on initializing users during signup/password-change flows.

Project Structure

django-amnesia-honeywords/
├── src/django_honeywords/
│   ├── admin.py             # Django admin registrations
│   ├── apps.py              # Django app config
│   ├── amnesia_service.py   # Core amnesia service
│   ├── backend.py           # Authentication backend
│   ├── checks.py            # Deployment system checks
│   ├── 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
│   ├── migrations/          # Database migrations
│   └── management/commands/ # Management commands
├── docs/                    # Configuration, deployment, integration guides
├── tests/                   # Test suite
└── example_project/         # Example Django project for testing

Security Considerations

  1. Honeyword Quality: The included SimpleMutationGenerator creates basic variants. For stronger security, implement a custom generator that produces indistinguishable honeywords.

  2. Audit Logging: All authentication attempts are logged to HoneywordEvent. Regularly review these logs and set up alerts for honeyword detections.

  3. Parameter Tuning: The p_mark and p_remark parameters control the trade-off between false positive rate and detection sensitivity. See the Amnesia paper for guidance on choosing values.

  4. Password Reset Flow: When ON_HONEYWORD = "reset", users flagged with must_reset should be redirected to a password change page. Integrate this with your application's auth flow.

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

Who do I talk to?

Having problems? Email me: davidiliopoulos@gmail.com

Citing this work

If you use this repository for academic research, you are highly encouraged (though not required) to cite our paper:

@inproceedings{iliopoulos2026honeywords,
  title={Honeywords in the Wild: A First Empirical Study of Deployment Readiness and Adoption},
  author={Iliopoulos, David and Smaragdakis, Georgios and Dionysiou, Antreas},
  booktitle={Proceedings of the 11th International Workshop on Traffic Measurements for Cybersecurity (WTMC)},
  year={2026}
}

Release files for django-amnesia-honeywords 0.0.4

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for django-amnesia-honeywords 0.0.4
File Size Uploaded
django_amnesia_honeywords-0.0.4.tar.gz 24.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for django-amnesia-honeywords 0.0.4
File Interpreter ABI Platform
django_amnesia_honeywords-0.0.4-py3-none-any.whl Python 3 none any Details

Total release size: 46.3 kB

Release files / django_amnesia_honeywords-0.0.4.tar.gz

Download URL django_amnesia_honeywords-0.0.4.tar.gz
Size 24.8 kB
Tags Source
SHA-256 checksum
How to use checksums
d835a9867b1dcce1ca71ca1339724bc831310db7eea91bf10dad3e788ef71e02
BLAKE2b-256 checksum
How to use checksums
7f038aab57215d5e514b113a5c6f9858ad8543e9ccaf37e4f7a44d1f1a1fa4c2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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 Sep 15, 2026.

Transparency log

Release files / django_amnesia_honeywords-0.0.4-py3-none-any.whl

Download URL django_amnesia_honeywords-0.0.4-py3-none-any.whl
Size 21.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
7419fc47a746fb686f688c9597713e9d032d069f2e98fe0e16d363c5e089b833
BLAKE2b-256 checksum
How to use checksums
20d9d2f8a1e3f7a07f2ec4c97d61e12919802582b40565ba2ed9e5fb692523c3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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 Sep 15, 2026.

Transparency log

Release history Release notifications | RSS feed

0.0.5

2 release files

This release

0.0.4 This release

2 release files

0.0.3

2 release files

0.0.2

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page