Cryptographic proof-of-deletion receipts — HMAC-SHA256 signed, hash-chained audit log
Project description
deleteceipt
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
examples/fastapi_integration.py— full upload/delete/receipt flow with FastAPIexamples/web_demo.html— browser UI demo (open after starting the FastAPI server withuvicorn fastapi_integration:app --reload)
Documentation
docs/architecture.md— Mermaid lifecycle diagram and deep explanation of why pre-deletion hashing mattersdocs/legal-foundation.md— The Google Spain case, GDPR Article 17, global erasure laws, and the gap between compliance and cryptographic proofdocs/why-deletion-is-hard.md— Filesystems, soft deletes, WAL logs, backups, caches, and derived data: why "deleted" is rarely what it seemsdocs/deletion-first-architecture.md— tmpfs, TTL-by-default, data minimization, and the one-job-one-directory patterndocs/cryptographic-assurance.md— SHA-256 pre-deletion commitment, HMAC vs ECDSA, hash chains, and the honest limits of what signatures prove
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
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 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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f0302b505e909adae44747612ebc253b61942e67bce6d81f480455f4cbb04456
|
|
| MD5 |
d38cc5ef0d4a4a4cb2cd4041938ab5cc
|
|
| BLAKE2b-256 |
a4e34f1cd4c3f626fa9636e10d3f775c954ae830b91fd0bd19f6b638a7032caf
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d5e5b12951e4a3abb0aa1f8818347bb44075d522e4a4d8c82a7d8c8c4605d9f5
|
|
| MD5 |
517ec68728777d390303fb178430e710
|
|
| BLAKE2b-256 |
2331f222e3bc9bc1612fa6d47f814ba2efed1926b3145a8b060fb75a35cb07c2
|