BASALT · JOURNAL
Redacting images, photographs, and scans inside a PDF
Text redaction gets the attention because text redaction failures are easy to demonstrate: select the black box, paste, read the secret. Image redaction fails in the same way and is harder to notice, because nobody thinks to try copying a photograph. A face, a signature, a license plate, or an entire scanned page can sit intact under a rectangle in a file everyone believes is clean.
The mechanism is worth understanding precisely, because the fix is different from the text case and most tools that handle one do not handle the other.
How images are stored in a PDF
An image in a PDF is an XObject: a self contained stream object with a dictionary describing its width, height, color space, bits per component, and filter, plus a byte payload. The page's content stream positions it with a transformation matrix and paints it with a Do operator.
The filter tells you how the payload is encoded. DCTDecode is baseline JPEG. JPXDecode is JPEG 2000. FlateDecode is zlib compressed raw samples, common for screenshots and synthetic graphics. CCITTFaxDecode and JBIG2Decode are bilevel encodings used for black and white scans, which is what a fax or a document scanner in text mode produces.
The important structural point is that the image object is entirely independent of what is painted on top of it. Drawing a filled rectangle after the Do operator appends a few operators to the content stream. The XObject is untouched, byte for byte, and any tool that walks the object graph can pull it out and write it to a file. That is a three line script with any PDF library, and it does not require defeating anything.
Why a black rectangle is worse for images than for text
With text there is at least a chance the tool you used rasterized the page, which destroys the words as a side effect of destroying everything. With images the failure is cleaner. The photograph is a single object, it is trivially extractable, and the extracted copy is the full original resolution rather than what was displayed.
That last part matters more than it sounds. A scan placed on a page at 8.5 by 11 inches may be a 300 or 600 DPI image internally. What you saw on screen when you drew the box was a downsampled rendering. The extracted object is the full resolution capture, where a signature you thought was too small to matter is perfectly legible.
The same applies to a redaction drawn at low zoom. Your rectangle covers the visible artifact. The underlying pixels have far more detail than the rendering you were looking at when you decided the box was big enough.
What real image redaction requires
To remove content from an image inside a PDF you have to modify the image data itself, then write a new stream. The sequence is:
- Locate every image XObject drawn on the page, including images nested inside form XObjects and inside patterns, and including images painted multiple times with different matrices.
- Compute the inverse of the transformation matrix that placed each image, so a rectangle drawn in page coordinates maps to a rectangle in image pixel coordinates. This has to account for rotation, scaling, and non uniform scaling, and for the page's own
/Rotateentry. - Decode the image to raw samples.
- Overwrite the covered pixels with a constant value. Not blur, not pixelate, not darken. Replace.
- Re-encode and write a new stream, replacing the original object rather than adding a new one.
Step four is where a lot of well intentioned work goes wrong. Blurring and pixelation are reversible to varying degrees, because they are lossy transformations that preserve statistical structure. For a small alphabet like digits on a license plate or a fixed format identifier, an attacker can render every candidate string, apply the same transform, and match. A mosaic filter with a known block size on known font metrics is a search problem, not a cryptographic one. Solid replacement is not a search problem, because the information is not present in any form.
Step five matters because of how PDF is structured. If you write the modified image as a new object and leave the old one in the file, unreferenced, it is still in the byte stream and still recoverable by scanning for object headers. And if the file is saved as an incremental update, the entire previous revision including the original image is appended below the new one. A redacted file has to be written as a single generation.
Re-encoding and the artifacts it leaves
Re-encoding a JPEG is lossy on every pass, so a redacted scan is very slightly different everywhere, not only under the mark. This is unavoidable and it is the correct trade. Some tools try to avoid it by converting DCTDecode to FlateDecode, which is lossless but often much larger. Either is defensible. What is not defensible is skipping the re-encode and leaving the original stream.
One practical consequence: file size is a useful signal. If you redact a page containing a large scan and the output file is a few hundred bytes bigger than the input, the image was not touched. A real image redaction changes the size of the image object, usually noticeably.
Scans, OCR layers, and the invisible copy
A scanned document that has been through OCR carries two representations of the same content. The visible one is the image. The invisible one is a text layer, drawn with text rendering mode 3, which makes glyphs invisible while keeping them selectable and searchable.
Destroying pixels in the image does nothing to that text layer. A tool that handles images but not OCR output produces a document where the photograph of the name is gone and the machine readable name is still selectable behind it. This is one of the most common ways a carefully redacted scan leaks, and it is covered in more detail in redacting a scanned PDF with an OCR layer.
Both representations have to be handled in the same operation: pixels destroyed and re-encoded, glyphs excised from the content stream with the surviving glyphs re-emitted in place.
The copies you did not know existed
Even with the image and the text layer both handled, a PDF can carry additional renderings of the same page.
Page thumbnails are small preview images stored in the page dictionary under /Thumb. They are generated by whatever produced the file and they are not regenerated when you edit the page. A stale thumbnail is a picture of the unredacted page, at low resolution but frequently readable enough to matter.
Alternate images under /Alternates provide a different rendering for a different output device, such as a high resolution version for print alongside a screen version. Both are in the file. Soft masks under /SMask carry an alpha channel as a separate image object, and for some source material the mask itself is informative.
Embedded file attachments can contain the source scan as a TIFF or the original photograph as a JPEG. Optional content groups, the layers feature, can hold a second image that is switched off by default and rendered by a viewer when the layer is enabled. The full inventory is in what a PDF still carries after you think you cleaned it.
Verifying an image redaction
Do not check by looking at the page. Check by extracting the objects.
Run a PDF object extractor over the saved file and dump every image to disk, then open them. pdfimages from Poppler does this in one command and writes each XObject as a separate file. Look at every extracted image, including the small ones, which is where thumbnails show up. If the material you removed appears in any of them, the redaction did not happen.
Then check for the text layer separately, because image extraction will not show it. Extract text with an independent parser and search for the terms. The full procedure is in how to check whether a PDF was really redacted.
Frequently asked questions
Can you recover an image that was blacked out in a PDF?
Yes, if the black box was drawn on top rather than applied to the image data. The image is stored as a separate XObject that is completely independent of anything painted over it, so any PDF library can extract the original in a few lines of code. Recovery only fails when the pixels themselves were overwritten and the image was re-encoded.
How do I redact an image in a PDF properly?
The pixels under the mark have to be replaced with a constant value in the decoded image data, and the image re-encoded as a new stream that replaces the original object. Covering the image with a rectangle changes only the painting order, not the stored bytes. The file also has to be written as a single generation so the pre-redaction revision is not appended below the new one.
Is blurring or pixelating a face in a PDF good enough?
No. Blur and pixelation are lossy transformations that preserve statistical structure, so for constrained content like digits, plates, or fixed format identifiers an attacker can render every candidate, apply the same filter, and match the result. Solid replacement removes the information entirely, which turns recovery from a search problem into an impossible one.
Does redacting a scanned page also remove the searchable text?
Not unless the tool handles both. A scan that has been through OCR carries an invisible text layer drawn with text rendering mode 3, and destroying pixels in the image leaves that layer completely intact. The name you blacked out in the picture can still be selected and copied from behind it.
Why is the extracted image higher resolution than what I saw on screen?
Because the viewer renders a downsampled version to fit the page, while the stored XObject keeps its native capture resolution, often 300 or 600 DPI for a scan. A signature or a serial number that looked illegible at the zoom level where you drew the box can be perfectly readable in the extracted original.
How can I check whether an image redaction actually worked?
Extract every image object from the saved file and look at them, rather than inspecting the rendered page. The pdfimages tool from Poppler dumps each XObject to a separate file in one command, including thumbnails and soft masks. If the removed material appears in any extracted image, the redaction did not happen.
What are page thumbnails and why do they leak redacted content?
Thumbnails are small preview images stored in the page dictionary under /Thumb, generated when the file was created and not regenerated when the page is edited. A stale thumbnail is a low resolution picture of the unredacted page, and it is frequently readable enough to reveal what was removed. They have to be stripped explicitly.
Does the file size tell me whether an image was really redacted?
It is a useful signal. Real image redaction requires decoding, overwriting pixels, and re-encoding, which changes the size of the image object noticeably. If you redact a page containing a large scan and the output is only a few hundred bytes larger than the input, all you added was a rectangle.
Doing it in Basalt
Basalt treats images as a first class redaction target. Pixels under a mark are destroyed and the image is re-encoded so there is no original underneath, OCR text layers are excised alongside the pixels, and stale thumbnails, attachments, hidden layers, and XMP metadata are stripped in the same pass.
The file is written as one generation, so earlier revisions cannot be recovered from the update chain. A built in verifier re-opens the written bytes with an independent parser and proves the redaction before anything is saved, and if it cannot prove it, no file is written and it tells you why.
The engine runs as a separate process with no network entitlement, enforced by macOS at the code signature level, so nothing is transmitted. It is $29 once for up to three Macs, free for 24 hours, and a 17 MB download.
Redaction that proves itself
Basalt destroys the content you mark, then re-opens the file it wrote and proves the content is gone before it saves anything. Your documents never leave your Mac.