Skip to main content

Cryptographic proof-of-deletion receipts — HMAC-SHA256 signed, hash-chained audit log

Project description

deleteceipt

PyPI Python License CI

Cryptographic proof that a file was deleted — not just a promise.

When users exercise their right to erasure (GDPR Article 17, CCPA, etc.), they receive a deletion confirmation email. That email is a promise, not proof: it is unilaterally generated by the same party that did the deleting and cannot be independently verified. deleteceipt replaces the promise with a cryptographically signed receipt — a JSON document whose signature can be verified by any third party holding only a public key, without trusting the server that issued it.


Why pre-deletion hashing?

The non-obvious insight at the heart of this library: the file hash must be committed before deletion, not after.

A hash computed after the file is gone is meaningless — the server could hash anything (an empty string, a zero byte) and produce a validly-signed receipt with no connection to what the user actually uploaded. deleteceipt separates the commitment (SHA-256 at upload time, stored in the database) from the deletion (discarding bytes later). By the time the receipt is issued, the hash is already on the ledger and cannot be changed retroactively without breaking the hash-chain audit log.

See docs/architecture.md for the full explanation and Mermaid diagrams.


Quick start

pip install deleteceipt           # HMAC-SHA256 core (zero dependencies)
pip install deleteceipt[ecdsa]    # + ECDSA/P-256 signing
pip install deleteceipt[mongo]    # + MongoDB/Motor backends
pip install deleteceipt[ecdsa,mongo]  # everything

10-line HMAC example

from datetime import datetime, timezone
from deleteceipt import compute_file_hash, issue_receipt, verify_receipt

# 1. At upload time — commit the hash before processing begins
file_bytes = open("document.pdf", "rb").read()
file_hash = compute_file_hash(file_bytes)

# 2. At deletion time — issue the signed receipt
now = datetime.now(timezone.utc)
receipt = issue_receipt(
    job_id="job-12345",
    file_hash=file_hash,
    uploaded_at=now,
    processing_completed_at=now,
    deleted_at=now,
    signing_key="your-secret-hmac-key",
    files_deleted=[{"path": "document.pdf", "size_bytes": len(file_bytes), "role": "input"}],
)

# 3. Verify — anyone with the key can check
assert verify_receipt(receipt, "your-secret-hmac-key")

ECDSA example (self-contained verification)

from deleteceipt import generate_keypair, issue_receipt_ecdsa, verify_receipt_ecdsa

private_pem, public_pem = generate_keypair()

receipt = issue_receipt_ecdsa(
    job_id="job-12345",
    file_hash=file_hash,
    uploaded_at=uploaded_at,
    processing_completed_at=processing_completed_at,
    deleted_at=deleted_at,
    private_key_pem=private_pem,
)

# The receipt embeds the public key — no external material needed
assert verify_receipt_ecdsa(receipt)

# Or verify with an explicit public key (e.g., downloaded from server)
assert verify_receipt_ecdsa(receipt, public_key_pem=public_pem)

HMAC vs ECDSA

Property HMAC-SHA256 ECDSA / P-256
Dependencies stdlib only cryptography
Key type Shared secret Private / public keypair
Who can verify Anyone with the secret key Anyone with the public key
Server must share Secret key (risky) Public key only (safe)
Self-contained receipt No Yes (signing_public_key_pem embedded)
Suitable for Internal systems Public / third-party verification
Signature size ~44 bytes (base64) ~96–104 bytes (DER base64)

Use HMAC when the verifier is the same party as the issuer (e.g., your own backend verifying its own receipts). Use ECDSA when you need users or regulators to verify receipts independently without possessing any secret key.


CLI

# Verify an HMAC receipt
deleteceipt verify receipt.json --key "your-hmac-key"

# Verify an ECDSA receipt (uses embedded public key)
deleteceipt verify-ecdsa receipt.json

# Verify with an explicit public key file
deleteceipt verify-ecdsa receipt.json --key server_pub.pem

# Generate a new P-256 keypair
deleteceipt keygen

# Inspect a receipt (no verification, human-readable table)
deleteceipt inspect receipt.json

Example deleteceipt inspect output:

+------------------------------+------------------------------------------------------------------+
| job_id                       | job-12345                                                        |
+------------------------------+------------------------------------------------------------------+
| file_hash_sha256             | b94d27b9934d3e08a52e52d7da7dabfac484efe04294e576...             |
+------------------------------+------------------------------------------------------------------+
| uploaded_at                  | 2026-03-01T10:00:00+00:00                                        |
+------------------------------+------------------------------------------------------------------+
| processing_completed_at      | 2026-03-01T10:00:05+00:00                                        |
+------------------------------+------------------------------------------------------------------+
| deleted_at                   | 2026-03-01T10:00:06+00:00                                        |
+------------------------------+------------------------------------------------------------------+

Audit log

from deleteceipt import AuditLog

log = AuditLog()  # InMemoryBackend by default
log.append_event("upload", job_id="job-1", metadata={"filename": "doc.pdf", "size_bytes": 4096})
log.append_event("processing_complete", job_id="job-1", metadata={})
log.append_event("deleted", job_id="job-1", metadata={"files_count": 2})

result = log.verify_chain()
# {"valid": True, "broken_at_seq": None, "total_entries": 3}

For async workloads (FastAPI, asyncio):

from deleteceipt import AsyncAuditLog

log = AsyncAuditLog()
await log.append_event("upload", job_id="job-1", metadata={"filename": "doc.pdf"})
result = await log.verify_chain()

For MongoDB production deployments:

from pymongo import MongoClient
from deleteceipt.audit import AuditLog, MongoBackend

col = MongoClient(url)["mydb"]["audit_log"]
log = AuditLog(backend=MongoBackend(col))

For async MongoDB (requires pip install deleteceipt[mongo]):

import motor.motor_asyncio
from deleteceipt.audit import AsyncAuditLog, AsyncMongoBackend

col = motor.motor_asyncio.AsyncIOMotorClient(url)["mydb"]["audit_log"]
log = AsyncAuditLog(backend=AsyncMongoBackend(col))

Examples

Documentation


License

Apache 2.0. See LICENSE.

Patent notice

See NOTICE.

deleteceipt is an open-source implementation of the cryptographic deletion receipt mechanism described in USPTO Provisional Application No. 64/019,899, "System and Method for Generating Cryptographically Signed Deletion Receipts in a Document Processing Service," filed March 28, 2026, by Yoonil Choi.

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

deleteceipt-0.1.0.tar.gz (50.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

deleteceipt-0.1.0-py3-none-any.whl (18.5 kB view details)

Uploaded Python 3

File details

Details for the file deleteceipt-0.1.0.tar.gz.

File metadata

  • Download URL: deleteceipt-0.1.0.tar.gz
  • Upload date:
  • Size: 50.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.5

File hashes

Hashes for deleteceipt-0.1.0.tar.gz
Algorithm Hash digest
SHA256 f0302b505e909adae44747612ebc253b61942e67bce6d81f480455f4cbb04456
MD5 d38cc5ef0d4a4a4cb2cd4041938ab5cc
BLAKE2b-256 a4e34f1cd4c3f626fa9636e10d3f775c954ae830b91fd0bd19f6b638a7032caf

See more details on using hashes here.

File details

Details for the file deleteceipt-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: deleteceipt-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 18.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.5

File hashes

Hashes for deleteceipt-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 d5e5b12951e4a3abb0aa1f8818347bb44075d522e4a4d8c82a7d8c8c4605d9f5
MD5 517ec68728777d390303fb178430e710
BLAKE2b-256 2331f222e3bc9bc1612fa6d47f814ba2efed1926b3145a8b060fb75a35cb07c2

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page