Have you ever needed to share a bank statement with someone? Maybe you’ve needed a user of your project to share one with you for troubleshooting.
I hit this exact problem. I’ve been building an extensible tool to convert UK bank statements into a relational database, but I only have access to statements from my own bank. How can I get access to other bank statements without asking people to send me sensitive, personally identifiable information?
I needed anonymised bank statements that retained their layout and font encoding so the statements could be processed in exactly the same way as the original. I also wanted to scramble everything by default, but enable the protection of dates and transaction types. Finally, it would be helpful if scrambling multiple statements from the same account could be configured to always scramble to the same fake value!
So I built a Python tool that does it properly — scrambles the sensitive data while keeping the PDF looking exactly the same.
The problem with PDF redaction
Traditional redaction tools typically do one of two things:
- Black boxes — overlay opaque rectangles. The text is still underneath (visible as soon as my parser processes it). Not actually secure.
- Text replacement — swap the text, but break the font encoding, layout, or both. The PDF not only looks wrong, but even the slightest change in the makeup of the statement could invalidate my config files and render them useless.
Neither is ideal. What I needed was to actually replace the bytes in the PDF’s content stream — the raw instructions that tell the PDF viewer what to draw — while preserving the font encoding and positioning.
Why pikepdf?
I looked at three Python PDF libraries:
Library Level Why it didn’t work PyPDF2 High-level Merging, splitting, extracting — no content stream access pdfplumber High-level I already use this for text extraction, but it doesn’t modify content streams pikepdf Low-level Direct access to content stream operators and font encodingspikepdf gives you access to the raw PDF operators — Tj (show text), TJ (show text with positioning), Tm (set text matrix). This is where the actual text lives, and where I needed to make changes.
How it works
The core idea is simple:
- Parse the PDF’s content stream into operators
- Scan for sensitive patterns (sort codes, account numbers, IBANs, card numbers)
- Replace the matching bytes with scrambled alternatives
- Write the modified content stream back
Here’s the tricky part: PDF font encodings. Different banks use different encoding strategies:
- Latin-1 (single-byte, straightforward)
- ToUnicode CMap (maps character codes to Unicode — common in modern PDFs)
- Identity-H (CID fonts, multi-byte — used by some banking software)
The tool handles all three, so the scrambled text renders correctly regardless of the bank’s PDF generator.
Usage
One function, one import:
from bank_statement_anonymiser import anonymise_pdf
anonymise_pdf("statement.pdf", "anonymised.pdf")
Enter fullscreen mode Exit fullscreen mode
That’s it. The output PDF looks identical to the input, but with sensitive data scrambled.
What gets anonymised
The tool targets these patterns:
- Sort codes (UK-specific: XX-XX-XX format)
- Account numbers (8-digit UK accounts)
- IBANs (international bank account numbers)
- Card numbers (Visa, Mastercard patterns)
- Merchant names (configurable via config files)
What doesn’t get anonymised
- Transaction Values (as my dependent projects validate the value of transaction lines against the total statement movement)
- Dates (various formats to cover transaction and statement dates)
- Transaction Types (specified in the never_anonymise_system.toml containing known transaction types for supported banks)
Configuration
You can further customise what gets anonymised by passing your own TOML config files:
# always_anonymise.toml — force-replace these patterns
"12-12-12" = "99-99-99"
"Jason Farrar" = "John Doe"
Enter fullscreen mode Exit fullscreen mode
# never_anonymise.toml — protect these phrases
exclude = [
"Balance Brought Forward",
"Account Summary",
]
Enter fullscreen mode Exit fullscreen mode
The system uses an exclusion-based approach: everything is scrambled by default, then you specify what to protect.
Limitations
This is UK-specific. The regex patterns for sort codes, account numbers, and IBAN formats are included for UK banks. Currently tested successfully on:
- HSBC
- Natwest
- TSB
- Halifax
It may also work for other banks, so please give it a go and let me know!
Other banks may require adding new regex patterns, and will almost certainly use different transaction types. There may also be other font encodings I haven’t come across yet.
All contributions are very much appreciated!
Installation
pip install uk-bank-statement-anonymiser
Enter fullscreen mode Exit fullscreen mode
Further instructions on installation and usage are available in the README
Open source
MIT licensed. All processing is local — no network requests, no accounts, no data collection.
GitHub: github.com/boscorat/uk-bank-statement-anonymiser
Have you encountered similar PDF anonymisation challenges? I’d be interested to hear about other banks’ PDF formats — the more patterns I can add, the more useful this becomes.
Any questions or comments are very welcome. I’d love to hear if you think there’s a better way to approach this problem, or changes I can make to the structure to widen the useful scope of this project.
답글 남기기