Open source · Apache 2.0

Cryptographic proof that
a file was deleted

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.

View on GitHub Live demo
pip install deleteceipt Copied!
Zero dependencies for HMAC core
ECDSA / P-256 for third-party verification
Hash-chained append-only audit log
GDPR Art. 17 · CCPA · right to erasure
The problem

A promise is not proof

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.

📧

Confirmation emails prove nothing

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.

🔐

Signatures are independently verifiable

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.

🔗

Pre-deletion commitment is the key insight

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.

⚖️

Compliance is becoming enforceable

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.

How it works

Four steps to verifiable deletion

The non-obvious step is step 1 — committing the hash at upload time, before processing or deletion begins.

Step 1

Commit the hash

SHA-256 is computed over the raw file bytes immediately at upload. This hash is stored in the database before any processing begins.

Step 2

Process, then delete

The file is processed normally. When the TTL expires or the user requests deletion, all files — input, output, intermediates — are removed.

Step 3

Issue the receipt

A JSON receipt is signed using HMAC-SHA256 or ECDSA/P-256, covering the pre-committed hash, timestamps, and a manifest of deleted files.

Step 4

Verify independently

Any party with the public key can verify the ECDSA receipt without contacting the server. The receipt is a portable, durable artifact.

Quick start

Minimal by design

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

Every field has a purpose

The receipt is a self-contained JSON document. No database query needed to understand what it asserts.

{
  "job_id": "f47ac10b-58cc-4372-a567...",
  "file_hash_sha256": "9f86d081884c7d659a2feaa0...",
  "uploaded_at": "2025-03-14T09:00:00Z",
  "processing_completed_at": "2025-03-14T09:00:47Z",
  "deleted_at": "2025-03-14T10:01:23Z",
  "files_deleted": [
    { "path": ".../input.pdf", "size_bytes": 2048576, "role": "input" },
    { "path": ".../output.pdf", "size_bytes": 1897432, "role": "output" },
    { "path": ".../text_layer.txt", "size_bytes": 48291, "role": "intermediate" }
  ],
  "signing_public_key_pem": "-----BEGIN PUBLIC KEY-----\n...",
  "server_signature_ecdsa": "MEUCIQD3Lk9..."
}

job_id — UUID v4

Assigned at upload time. Statistically unguessable. Links the receipt to the upload event without leaking information about other jobs.

file_hash_sha256 — pre-deletion commitment

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.

Three timestamps — full lifecycle

Upload, processing completion, and deletion. Three timestamps let auditors verify that retention duration is consistent with stated TTL policies.

files_deleted — completeness manifest

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.

server_signature_ecdsa — cryptographic seal

ECDSA/P-256 signature over the canonical JSON of all preceding fields. The public key is embedded — no key exchange needed to verify.

Signing schemes

HMAC or ECDSA — choose the right tool

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
Dependenciesstdlib onlycryptography≥42
Key typeShared secretPrivate / public keypair
Who can verifyAnyone with the secretAnyone with the public key
Share with verifierSecret key (risky)Public key only (safe)
Self-contained receiptNoYes
Third-party verificationNoYes
Best forInternal / enterprise self-hostedUsers, regulators, auditors
Audit log

Tamper-evident by design

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.

#0 upload prev: 000…000  |  hash: a3f2c8e1…  |  job: f47ac10b
↓ each entry's hash covers the previous
#1 processing_complete prev: a3f2c8e1…  |  hash: 7b904d3f…  |  job: f47ac10b
#2 deleted prev: 7b904d3f…  |  hash: c21e8a02…  |  job: f47ac10b

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.

The Right to Be Forgotten book cover KDP SELECT
The book behind the library

The Right to Be Forgotten:
Privacy, Deletion, and the Architecture of Trust

by Yoonil Choi  ·  Published April 7, 2026
"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.

Also available free with Kindle Unlimited  ·  293 pages  ·  ASIN: B0GP35C79Q

Start issuing verifiable deletion receipts

Open source, Apache 2.0. Zero mandatory dependencies. Integrates with FastAPI, Django, Flask, or any Python backend.

View on GitHub Try the live demo Architecture docs