PDF Steganography Hider
Hide text or a file in a PDF with five methods (metadata, invisible text, whitespace, object stream, font entry). AES-256-GCM encryption with a password (optional). Processed in your browser, not uploaded.
Try the demo instantly
Loads a small sample PDF and a sample message, no upload needed
Drop PDF here or click to browse
Accepted: PDF · Maximum file size: 200 MB on desktop, 50 MB on mobile
Processed in your browser
Your PDF is read and rewritten as a new copy in this browser tab and is not sent to our servers. Usage statistics may record which options you used (such as the algorithm) and coarse file facts such as size range, never the PDF or the payload.
How to Hide Data in a PDF (5 steps)
- Upload your PDF file as the carrier
- Type your secret message or upload a file to hide
- Choose an embedding algorithm (Metadata is the simplest)
- Add a password to encrypt the payload (optional)
- Click Encode — download your stego PDF
5 PDF Embedding Algorithms
| Algorithm | Technique | Discoverability | Best For |
|---|---|---|---|
| Metadata Embedding | Base64 text in the Info dictionary's Keywords field (Subject set to a marker) | Keywords field is visible in document properties | Quick demos and CTF-style challenges |
| Invisible Text Layer | Base64 in a Catalog entry, plus white 0.001-pt text drawn off-page on page 1 | Catalog entry; text-extraction tools may show the off-page copy | An additional copy that text-extraction tools may expose |
| Whitespace Manipulation | Base64 in a page Resources entry, plus a few Tw word-spacing lines prepended to page 1 | Page 1 Resources entry; a small Tw stream first on the page | Experiments with content-stream operators |
| Object Stream Padding | A standalone uncompressed stream object, referenced from the Catalog | A stream object linked from the Catalog | Binary payloads |
| Font Encoding | Base64 in a custom key of the first font dictionary | A key in a font dictionary | Keeping the data away from the Catalog and metadata |
What This Tool Does to a PDF
A PDF is a collection of numbered objects — a Catalog, page dictionaries, content streams, font dictionaries, metadata — tied together by references. Readers ignore dictionary entries they do not recognise, and that is the space this tool uses: it adds an entry with a custom name that holds your payload as Base64 text, then saves the file again. The page content is not redrawn, so the pages look the same.
That makes it a way to attach data to a PDF without changing how it looks, not a way to make the data hard to find. Anyone who dumps the file's objects with a PDF tool can see the custom entries and the Base64 text. In files this tool wrote, a plain strings pass finds the Base64 only for Object Stream, because pdf-lib writes the other four entries inside Flate-compressed object streams (measured, see the limitations); any tool that inflates streams finds all five. Encrypt the payload with a password if the content must stay private; encryption hides what the data says, not that it is there.
What a PDF Is Made Of
A PDF is a set of numbered objects: a Catalog (the root), a Pages tree, one dictionary per page, content streams that draw the text and images, font dictionaries, and an Info dictionary with the title, author, keywords and dates. A cross-reference table (or a cross-reference stream) lists where each object starts, and newer files pack many objects into compressed object streams. Viewers draw only what the pages reference, and ignore dictionary entries whose names they do not know. That tolerance is the space this tool uses.
Before You Use It on a Real Document
Keep the original: the tool never changes the file you chose, it offers a new one. Run the Extractor on the downloaded copy before you send it. If the file is digitally signed, hide first and sign afterwards, because saving the PDF again changes the bytes the signature covered. A password-protected PDF is refused; remove its protection first. There is no way to recover a lost password.
The hidden data is plain Base64 unless you set a password, so do not rely on it for confidentiality without one. Whoever receives the file can also see the original document metadata (unless you chose Strip) and the new custom entries if they dump the objects.
The Five Algorithms, and Where Each One Stores Your Data
Metadata Embedding writes the payload into the Info dictionary's Keywords field, as the text STEGO_META_V1: followed by Base64 cut into 500-character pieces joined with ||. It also sets the Subject field to STEGO_V1. It is the easiest to see: Acrobat's Document Properties or any metadata viewer shows it.
Invisible Text Layer stores the Base64 payload in a Catalog entry named StegoITData, which is what the extractor reads. It also draws the same data as white text, 0.001 points tall, at coordinates (−9999, −9999) on page 1, so the file contains genuine off-page text. Whitespace Manipulation stores the payload in a page Resources entry named StegoWSData and prepends a small content stream to page 1: a comment line and up to 256 Tw (word-spacing) lines of 0.0001 or 0 that spell out the first 32 bytes. Those lines are not attached to any words, so they do not change the layout; the Resources entry is the copy the extractor reads.
Object Stream adds a new uncompressed stream object containing STEGO_OBJ_V1: and the Base64 payload, and references it from the Catalog under StegoObjStream. Font Encoding adds a StegoFontData entry to the first font dictionary it finds on any page (or to page 1's Resources if the PDF has no fonts), holding STEGO_FONT_V1: and the Base64 payload.
Choosing a Method
All five methods store the same thing — Base64 text under a custom key — in different places, and none uses the visible content of the page. They are equally capable of carrying your data; they differ in where an inspector would look. Metadata is the most conspicuous, because every PDF viewer exposes it. The other four sit in structural dictionaries that viewers do not show but that any object dump lists, and that a tool which rebuilds the file from its rendered pages would not keep.
So choose by purpose. Use Metadata when you want the simplest, most inspectable result. Use Object Stream for larger or binary payloads. Use the others when you want the data kept out of the metadata fields. If the content is sensitive, set a password, whichever method you pick.
Capacity and File Size
All five methods share one limit: 10,485,760 bytes (10 MiB). It is this tool's own enforced limit on the prepared payload, counted after the 1-byte filename frame (plus a length byte and the name for a file), the 48 bytes a password adds, and any Reed-Solomon parity. It is a memory and time policy, not a structural limit of PDF and not a promise that other software will open the result. The capacity bar shows it for every method; a payload at the displayed figure is accepted and one byte more is refused (tested at the boundary). A 10 MiB (10,485,760 bytes) payload takes about 0.2 s (Object Stream) to 7 s (Metadata) in Node on a laptop, including the read-back check, and the work runs in a Web Worker.
Four methods (Metadata, Invisible Text, Whitespace Manipulation, Font Encoding) store the payload in a PDF string; Object Stream stores it in a stream and is the better choice for large payloads. ISO 32000-1 Annex C lists 32,767 bytes as an implementation limit for a PDF string. As approximate compatibility guidance only: Base64 text of 32,767 characters holds about 24 KB of payload (about 12 KB for Metadata, whose Keywords field pdf-lib writes as UTF-16 with a 2-byte marker, two bytes per character: checked on real output), so a larger payload may open in this tool's Extractor but not in other software. Those figures are about the Base64-encoded size, not the original file size, and we did not test which readers enforce the limit.
The file grows by different amounts. Measured on incompressible data in a 3-page PDF, the output grew by about 1.0 times the payload size for Whitespace and Font Encoding, 1.3 times for Object Stream, 1.5 times for Metadata and 2.3 times for Invisible Text (which stores the payload twice, in the Catalog and as page text). Text payloads compress better, so those figures are an upper bound.
Encryption and the Reed-Solomon Option
Without a password the payload is stored as plain Base64 and anyone who finds it can read it. With one, it is encrypted before it is embedded — see "How Strong Is the Encryption?" below for the exact scheme and its limits.
The RS-10, RS-20 and RS-30 options add Reed-Solomon parity: 10, 20 or 30 parity bytes per 223-byte block plus a 16-byte header, repairing up to 5, 10 or 15 changed bytes per block (the code's theoretical correction capacity, assuming the damaged block can be located; the 16-byte RS header is not covered by the parity). For a PDF it adds little, because the payload sits in the file as one intact string rather than being spread over pixels or samples that a re-encode might damage. We suggest leaving it off.
What Can Remove Your Payload
Anything that rewrites the PDF from its rendered pages — print to PDF, flattening, many optimisers, scanning — will not carry custom dictionary entries across. A re-save with the same library (pdf-lib load and save) kept all five methods in our tests; we have not tested Acrobat, Preview, Ghostscript, PDF/A converters, e-signature services or Google Drive, so treat them as untested rather than safe. Editing metadata can wipe the Keywords field.
Modifying a digitally signed PDF invalidates its signature, because this tool saves the whole file again. Embed before you sign. Password-protected (encrypted) PDFs are not supported: remove the protection first.
How Strong Is the Encryption?
If you set a password, the payload is encrypted before it is hidden, in your browser, with the Web Crypto API. The cipher is AES-256 in GCM mode. The 256-bit key is derived from your password with PBKDF2 using HMAC-SHA-256 and 100,000 iterations. Every encryption generates a fresh random 16-byte salt and a fresh random 12-byte IV with the browser's cryptographically secure random generator (crypto.getRandomValues), so encrypting the same message twice gives different bytes. The stored layout is a 4-byte length, the salt, the IV, then the ciphertext followed by a 16-byte authentication tag — 48 bytes added in total. The tag means a wrong password, or any change to the ciphertext, makes decryption fail instead of returning garbage.
These are standard, widely used primitives, but this implementation has not had an independent audit, and using them does not make the system unbreakable. Their limits matter. Anyone who finds the hidden data can copy the ciphertext and try passwords against it offline, so your password is the real defence: use a long random passphrase, not a word or a date. The key derivation uses 100,000 PBKDF2-HMAC-SHA-256 iterations. This is below OWASP's commonly cited 600,000-iteration recommendation for password storage. That recommendation is not a direct requirement for this file-encryption format, but the lower count makes a strong, unique password especially important. The iteration count is fixed in the code and is not written into the file, so raising it requires a new, versioned format so that files already made can still be opened.
Encryption hides what the data says, not that it exists, and it does not hide the length: AES-GCM ciphertext is the same length as the plaintext, and the encrypted layout adds a fixed 48 bytes of overhead: its own 4-byte length field, the salt, the IV and the authentication tag, separate from any length or framing the tool puts around it. No padding is added. There is no key file and no way to recover a forgotten password. We have not measured key-derivation time on phones or older devices, so very large batches or slow devices may take noticeably longer per file. The tool is intended for educational and personal use; do not rely on it as the only protection for sensitive information, or against a well-resourced adversary.
Where Each Method Writes Its Data
Names and markers are taken from utils/pdfSteganography.ts. The Extractor reads exactly these places and no others.
| Method | Where in the PDF | Name and marker | Stored as |
|---|---|---|---|
| Metadata Embedding | Info dictionary: Keywords and Subject | Keywords = STEGO_META_V1: + Base64 in 500-character pieces joined with ||; Subject = STEGO_V1 | Text string (two bytes per character), inside a compressed object stream |
| Invisible Text Layer | Catalog entry, plus text on page 1 | Catalog key StegoITData (Base64); page text STEGO_IT_V1: + Base64, white, 0.001 pt, at (−9999, −9999) | A PDF string; the page text sits in a compressed content stream |
| Whitespace Manipulation | Page 1 Resources entry, plus a new first content stream | Resources key StegoWSData (Base64); stream starts % STEGO_WS_V1, then up to 256 0.0001 Tw or 0 Tw lines and a closing 0 Tw | A PDF string; the small Tw stream is not compressed |
| Object Stream | A new stream object referenced from the Catalog | Catalog key StegoObjStream; stream text STEGO_OBJ_V1: + Base64 | An uncompressed stream |
| Font Encoding | The first font dictionary found on any page (page 1's Resources if none) | Key StegoFontData = STEGO_FONT_V1: + Base64 | A PDF string |
What Changes in the File
| Part of the PDF | What happens | How this was checked |
|---|---|---|
| Page content | Not redrawn. The original content streams are unchanged; Invisible Text adds a text stream, Whitespace Manipulation adds a first stream. | Content streams compared before and after, per method |
| Info dictionary | Preserve keeps every field and both dates (including Producer). Strip removes Title, Author, Subject, Keywords, Creator, Producer, both dates and the XMP stream. Randomise gives both dates random values from the last two years. Metadata Embedding always replaces Keywords and Subject, also after Strip: the strip runs first and the method then writes its own Keywords and Subject. | Each option read back from the downloaded file |
| File structure | pdf-lib rewrites the whole file: it writes new cross-reference data and carries over the content it supports, using object streams. It is a newly saved file and some features of the original may differ. | By design; not compared feature by feature |
| Digital signatures | Invalidated, because the bytes they cover change. | By design; not tested with a signing tool |
| File size | Grows by about 1.0 times the payload (Whitespace Manipulation, Font Encoding), 1.3 times (Object Stream), 1.5 times (Metadata) or 2.3 times (Invisible Text, which stores the payload twice). Measured on incompressible data; text compresses better. | Measured with a 1 MB random payload in a 3-page PDF |
| A plain strings search | Finds the Base64 only for Object Stream; the other four sit in compressed object streams. A tool that inflates streams finds all five. | Raw bytes of finished files searched for the Base64 |
How Many Bytes Are Stored
The stored size is what counts against the 10 MiB limit: the 1-byte filename frame (plus a length byte and the name for a file), 48 bytes for a password, then Reed-Solomon parity (16-byte header plus 10, 20 or 30 bytes per 223-byte block).
| Example payload | Stored bytes | Base64 characters |
|---|---|---|
| meet at noon (12 bytes), no options | 13 | 20 |
| the same with a password | 61 | 84 |
| the same with RS-10 | 39 | 52 |
Examples
Input: A 3-page PDF (1,203 bytes) + the text meet at noon, Metadata method, no password
Result: The stored bytes are one 00 byte (no file name) plus the 12 characters, 13 bytes, which is the Base64 text AG1lZXQgYXQgbm9vbg==. Keywords becomes STEGO_META_V1:AG1lZXQgYXQgbm9vbg== and Subject becomes STEGO_V1. The file grew by 78 bytes (1,281 bytes).
Meaning: Open File → Properties in a PDF reader and the Keywords field is there. The Extractor's Metadata method reads it back. Your own Keywords and Subject were replaced.
Input: The same PDF and message with a password
Result: The stored data is 61 bytes instead of 13 (the encrypted payload adds 48 bytes before Base64 encoding), 84 Base64 characters instead of 20, and decodes to random-looking bytes.
Meaning: Without the password the stored data is unreadable ciphertext, but the Keywords entry still shows that something is stored there.
Input: Invisible Text Layer
Result: The Catalog gains a StegoITData string, and page 1 gains white text at (−9999, −9999) starting STEGO_IT_V1:.
Meaning: A text-extraction tool may print the off-page text, and an object dump shows the Catalog entry. The page looks identical.
Input: Whitespace Manipulation on a text-heavy PDF
Result: Page 1 gets a leading content stream starting % STEGO_WS_V1 and up to 256 lines of 0.0001 Tw or 0 Tw; the full payload is in the page's StegoWSData resource.
Meaning: The Tw lines are decoration for the first 32 bytes; the extractor reads the Resources entry. The layout does not change.
Input: Object Stream with a 20 KB file as payload
Result: A new stream object containing STEGO_OBJ_V1: and the Base64 data (about 27 KB), referenced from the Catalog as StegoObjStream.
Meaning: The file grows by roughly the size of the Base64 text. Larger payloads belong here, not in a string field.
Input: A PDF with no text and no fonts (for example a scan), Whitespace or Font Encoding
Result: The capacity bar shows the same 10 MiB (10,485,760 bytes) as for every method. The data is written to the page's Resources dictionary, which is created if the page has none.
Meaning: Neither method needs text on the page. Both were round-tripped on a blank page and on a page with no Resources entry at all.
How the Hider Works, Step by Step
1. Load
Your PDF is parsed in the browser with pdf-lib. Metadata options are applied first if you chose one: Preserve keeps every Info field and both dates, Strip removes the Info fields, both dates and the XMP stream, Randomise replaces both dates with random ones from the last two years.
2. Prepare the payload
Your text or file gets a small filename wrapper, is optionally encrypted with AES-256-GCM, and optionally wrapped in the redundancy format.
3. Embed
The payload is turned into Base64 and written to the one place your algorithm uses: the Keywords field, a Catalog entry, a Resources entry, a new stream object, or a font dictionary.
4. Save
pdf-lib saves the whole PDF again. pdf-lib rewrites the PDF and carries over the content it supports. The file is a newly saved copy, not a patched version of the old one, and some features of the original may differ.
5. Read back
The finished PDF is parsed again and the payload is extracted with the same code the Extractor uses. If it does not match, no file is offered.
6. Download
The new PDF is created in your browser and downloaded from it. Nothing is sent to a StegoToolkit upload server.
What the results mean
| Result | What it means | What to do |
|---|---|---|
| Encoding complete | The PDF was saved with your payload added | Download it, then test the Extractor on the downloaded copy |
| Capacity warning | The stored payload (with the filename frame, the 48 bytes of a password and any Reed-Solomon parity) is over 10 MiB (10,485,760 bytes) | Shorten the payload or turn off Reed-Solomon; the limit is the same for every method |
| Algorithm: Metadata | Payload is in Keywords, Subject is a marker | Visible in document properties — pick another algorithm to keep it out of the metadata |
| Encryption: AES-256 ✓ | The stored data is ciphertext | Keep the password; there is no way to recover it |
| RS Protection: RS-10/20/30 | The payload carries Reed-Solomon parity | Leave off unless you expect a few isolated byte changes; it adds 10/20/30 bytes per 223-byte block |
Limitations
- This is not a way to make data hard to find. Each method stores Base64 text under a custom key that any PDF object dump lists. Use a password if the content matters.
- The extractor reads only the exact custom key names this tool writes. It cannot read payloads hidden by other tools or by other techniques — use the Polyglot Analyzer, the Strings Analyzer or the Hex Viewer for those.
- Anything that rebuilds a PDF from its rendered pages (print to PDF, flattening, scanning) removes the payload. Individual tools such as Acrobat, Preview and Ghostscript have not been tested.
- Modifying a digitally signed PDF invalidates the signature. Password-protected PDFs are not supported.
- The 10 MiB (10,485,760 bytes) limit is a policy, the same for every method. ISO 32000-1 Annex C lists 32,767 bytes as an implementation limit for a PDF string, and some other readers may enforce it; Object Stream avoids it. We did not test which readers do.
- The RS-10/20/30 option is Reed-Solomon parity: it repairs a few changed bytes per block (5, 10 or 15 is the theoretical capacity when the block can be located; the 16-byte RS header is not protected), not removed objects or a rewritten file.
- The upload box accepts PDFs up to 200 MB (50 MB on a phone) and refuses anything larger without reading it. Measured in Chromium on a laptop with a 190 MB PDF that is one large binary stream: about 0.5 s to read it, about 1 to 1.5 s to hide a short message (Object Stream or Metadata), and the page stayed responsive (longest frame gap 143 ms). A PDF made of very many small objects takes longer. The two side-by-side previews on the result page are skipped above 20 MB.
- For metadata-focused PDF work, with more methods and a CTF mode, see the PDF Metadata Steganography Hider.
Privacy
Your PDF and payload are processed in your browser and are not sent to a StegoToolkit upload server; the stego PDF is created on your device and downloaded from it. Usage analytics (page views and tool events) record which options you choose (such as the algorithm), coarse facts about the file such as its type and size range, and how long processing took — never the PDF, the payload, a filename or a password. Your display mode preference is kept in this browser's localStorage.
Which Tool Next?
- PDF Steganography Extractor — Read back what you hid
- PDF Metadata Steganography Hider — A different tool with its own PDFMETA header and a CTF mode: six metadata-oriented methods (XMP-style Info fields, a custom Info key, an object stream, content-stream whitespace, an incremental update, an embedded file attachment). The five methods here are structural dictionary entries under fixed key names
- Polyglot File Analyzer — Check a PDF for extra formats appended to it
- Strings Analyzer — See what a plain strings pass finds in your finished PDF (the Base64 shows only for Object Stream)
- Hex Viewer — Look at the raw bytes around the new entries
- Capacity Calculator — Check whether a payload fits before you start
Frequently Asked Questions
Will the PDF look different after encoding?
The page content is not redrawn: all five methods add data to dictionaries or an extra stream, and Invisible Text also draws white text off the page, so the pages should look the same (we compared the content streams, not rendered pixels). The file does change: it is saved again, gets larger (by about 1.0 to 2.3 times the payload size, depending on the method), and a PDF object dump will show the new entries. With Metadata Embedding the Keywords and Subject fields are replaced.
Is my PDF uploaded to a server?
No. Processing happens in your browser and the stego PDF is created on your device. The file is not sent to a StegoToolkit upload server.
Does this work with scanned PDFs?
Yes, as far as the structure goes. The methods write dictionary entries rather than editing page text, and all five were round-tripped on PDFs with no text, no fonts and no Resources entry (we did not run a real scanner's output or a PDF full of large images). Run the Extractor on the result to confirm.
Will encoding break digital signatures?
Yes. The tool saves the whole PDF again, which invalidates any digital signature. Embed your data first and sign afterwards.
How much data can I hide?
Up to 10 MiB (10,485,760 bytes) of stored data for every method (the capacity bar counts the filename frame, a password's 48 bytes and Reed-Solomon parity). Base64 adds a third. Other readers may reject strings above 32,767 bytes (ISO 32000-1 Annex C), so for payloads above about 24 KB (12 KB for Metadata) prefer Object Stream.
What does the Reed-Solomon option do?
It adds Reed-Solomon parity (10, 20 or 30 bytes per 223-byte block plus a 16-byte header for RS-10, RS-20, RS-30), which repairs up to 5, 10 or 15 changed bytes per block in theory (when the damaged block can be located; the 16-byte header is not covered). A PDF payload is stored as one intact string, so it adds little here; we suggest leaving it off.
How hard is the hidden data to find?
Not hard. Each method stores Base64 text under a custom key name, and any PDF tool that lists objects shows it. A plain strings pass on the finished file finds the Base64 only for Object Stream, because the other four sit in compressed object streams, but a tool that inflates streams finds all of them. Metadata is the easiest to see. If the content is sensitive, set a password so the stored data is ciphertext.
Can I decode it with another tool?
Yes, by hand. The data is Base64 after a marker such as STEGO_META_V1:, STEGO_OBJ_V1: or STEGO_FONT_V1:, so a PDF object viewer and a Base64 decoder are enough, plus your password if you set one. The PDF Steganography Extractor reads it automatically.
Related Tools
PDF Steganography Extractor
Extract text or files hidden by the PDF Steganography Hider's five methods. Tries each method in turn and shows a PDF Anomaly Score. Processed in your browser, not uploaded.
Steganography Capacity Calculator
Calculate exact hidden-payload capacity for PNG, BMP, WebP, WAV, FLAC, AIFF and more — plus PSNR and detection risk, all client-side. Links to the right specialist hider/extractor for your file type.