When users exercise their right to erasure, they deserve more than a confirmation email. deleteceipt issues a cryptographically signed receipt — a JSON document any third party can verify without trusting the server that issued it.
Every privacy policy says "we delete your data when you ask." The problem is not dishonesty — it is that these claims are structurally unverifiable. deleteceipt replaces the promise with evidence.
A support email saying "your file has been deleted" is generated by the same party that held the data, on systems the user cannot audit. It is assertion, not evidence.
A cryptographic signature on the deletion receipt can be verified by anyone holding the public key — users, regulators, auditors — without contacting the server that issued it.
The file hash must be committed before deletion, not after. A hash computed post-deletion is meaningless — the server could hash anything. The commitment is what makes the receipt trustworthy.
GDPR Article 17, CCPA, and a widening array of national laws impose deletion obligations subject to audit. Organizations that cannot demonstrate what they deleted, when, and how face growing regulatory exposure.
The non-obvious step is step 1 — committing the hash at upload time, before processing or deletion begins.
SHA-256 is computed over the raw file bytes immediately at upload. This hash is stored in the database before any processing begins.
The file is processed normally. When the TTL expires or the user requests deletion, all files — input, output, intermediates — are removed.
A JSON receipt is signed using HMAC-SHA256 or ECDSA/P-256, covering the pre-committed hash, timestamps, and a manifest of deleted files.
Any party with the public key can verify the ECDSA receipt without contacting the server. The receipt is a portable, durable artifact.
The HMAC core has zero dependencies. ECDSA signing requires only the
cryptography package.
# pip install deleteceipt from datetime import datetime, timezone from deleteceipt import compute_file_hash, issue_receipt, verify_receipt # 1. At upload time — commit the hash before processing 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")
# pip install "deleteceipt[ecdsa]" from deleteceipt import generate_keypair, issue_receipt_ecdsa, verify_receipt_ecdsa # Generate a P-256 keypair (run once; store private key securely) private_pem, public_pem = generate_keypair() # Issue — signed with the private key 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, ) # Verify — public key is embedded in the receipt; no secret needed assert verify_receipt_ecdsa(receipt) # Or verify with an explicit public key (e.g., fetched from server) assert verify_receipt_ecdsa(receipt, public_key_pem=public_pem)
from deleteceipt import AuditLog log = AuditLog() # InMemoryBackend by default; swap for MongoBackend in prod log.append_event("upload", job_id="job-1", metadata={"filename": "doc.pdf"}) log.append_event("processing_complete", job_id="job-1", metadata={}) log.append_event("deleted", job_id="job-1", metadata={"files_count": 3}) result = log.verify_chain() # {"valid": True, "broken_at_seq": None, "total_entries": 3} # Async variant for FastAPI / asyncio from deleteceipt import AsyncAuditLog log = AsyncAuditLog() await log.append_event("upload", job_id="job-1", metadata={})
# Verify an ECDSA receipt (uses embedded public key) deleteceipt verify-ecdsa receipt.json # Verify an HMAC receipt deleteceipt verify receipt.json --key "your-hmac-key" # Inspect a receipt — human-readable table, no verification deleteceipt inspect receipt.json # Generate a new P-256 keypair deleteceipt keygen # Verify with an explicit public key file deleteceipt verify-ecdsa receipt.json --key server_pub.pem
The receipt is a self-contained JSON document. No database query needed to understand what it asserts.
Assigned at upload time. Statistically unguessable. Links the receipt to the upload event without leaking information about other jobs.
SHA-256 of the raw file bytes, computed at upload time — not at deletion time. Users can verify this hash against their own copy of the original file.
Upload, processing completion, and deletion. Three timestamps let auditors verify that retention duration is consistent with stated TTL policies.
Input, output, and intermediate files listed by path, size, and role. A receipt that only mentions the primary input does not fully serve the right to be forgotten.
ECDSA/P-256 signature over the canonical JSON of all preceding fields. The public key is embedded — no key exchange needed to verify.
The choice depends on who needs to verify the receipt and whether they can be trusted with a shared secret.
| Property | HMAC-SHA256 | ECDSA / P-256 |
|---|---|---|
| Dependencies | stdlib only | cryptography≥42 |
| Key type | Shared secret | Private / public keypair |
| Who can verify | Anyone with the secret | Anyone with the public key |
| Share with verifier | Secret key (risky) | Public key only (safe) |
| Self-contained receipt | No | Yes |
| Third-party verification | No | Yes |
| Best for | Internal / enterprise self-hosted | Users, regulators, auditors |
Each audit log entry cryptographically covers the hash of the previous entry. Suppressing a deletion record requires rewriting all subsequent entries — and every external anchor published since.
Note: Hash chains prove tamper-evidence, not completeness. They ensure recorded events cannot be suppressed — but only a sound deletion workflow ensures all events are recorded in the first place. See cryptographic-assurance.md.
KDP SELECT
"The delete button lies. When a user submits a deletion request, the clock starts. Thirty days to comply. The policy is clear, the legal exposure is real — and then someone looks at the database schema."
The ideas powering deleteceipt — pre-deletion hashing, cryptographic receipts, hash-chained audit logs, deletion-first architecture — are explored in full in this book. Covers GDPR Article 17, the Google Spain case, filesystem deletion myths, crypto-shredding, and how to build systems that can honestly prove they forget.
Open source, Apache 2.0. Zero mandatory dependencies. Integrates with FastAPI, Django, Flask, or any Python backend.