StegoToolkit

Document Steganography Hider

Hide text or a file inside a DOCX, XLSX, PPTX or ODT document with six methods (custom XML part, ZIP comment, XML attributes, hidden text, tracked changes, ZIP extra fields). Optional AES-256-GCM encryption. Processed in your browser, not uploaded.

DOCX/XLSX/PPTX/ODT
6 methods
AES-256-GCM
In-browser

Drop a document here or click to browse

DOCX · XLSX · PPTX · ODT · Max 200 MB

Processed in your browser

Your document is parsed and rewritten in this browser tab using JSZip and is not sent to a server. Usage statistics may record which options were used, never your document.

How to Hide a Message in a Word, Excel, or PowerPoint File (4 steps)

  1. Upload a DOCX, XLSX, PPTX or ODT document (the carrier); up to 200 MB, 50 MB on a phone
  2. Type a message or attach a file to hide
  3. Choose how to hide it: Quick mode uses Custom XML Parts, Standard adds ZIP Comment and XML Attribute, Forensic adds Hidden Text, Revision Tracking and ZIP Extra Field
  4. Optionally add a password (AES-256-GCM), encode, and download the new document

6 Document Embedding Methods Compared

MethodTechniqueDurabilityBest For
Custom XML PartsAdds customXml/ep_payload.xml, a content-type entry and a relationshipKept by python-docx and python-pptx re-saves, dropped by openpyxl (Word and LibreOffice not tested)Default: large payloads in DOCX, XLSX or PPTX
ZIP CommentWrites EP_STEGO_V1: and base64 into the ZIP commentDropped by every re-save tested; 48,750 bytes at mostSmall messages in any ZIP-based type, including ODT
XML AttributeAdds ep:d attributes (200 base64 characters each) to paragraphs, sheets or slide idsKept by python-docx and python-pptx re-saves, dropped by openpyxl; 150 bytes per element (Word, PowerPoint and LibreOffice not tested)Documents with many paragraphs or slides
Hidden TextOne run with the hidden-font property at the start of the first paragraphKept by a python-docx re-save; visible wherever hidden text is shownDOCX with a large payload
Revision TrackingA tracked insertion by EP_STEGO holding a hidden run; turns Track Changes onKept by a python-docx re-save; shows in the revision listDOCX, when you accept the Track Changes side effect
ZIP Extra FieldWrites the payload into central-directory extra fields (tag 0x4550)Dropped by every re-save tested; capacity calculated by the tool as ZIP entries × 65,531 bytesLarger payloads in a file that will not be re-saved

Document Steganography: What This Tool Does

A DOCX, XLSX or PPTX file is a ZIP archive of XML files, and an ODT file is a ZIP too. That gives several places to put extra data without changing what the document shows: a new part, the ZIP comment, the ZIP extra fields, attributes on existing elements, or Word features such as hidden text and tracked changes. This tool offers six such methods, and the Extractor on this site reads each of them back. Everything runs in your browser.

None of the methods is encryption, and none hides from someone who unzips the file that something extra is there. Add a password to encrypt the message with AES-256-GCM before it is hidden.

Encoding a document that already holds a message from this tool keeps the earlier message in the file for every method except ZIP Extra Field, whose payload is lost because the ZIP is rebuilt; using the same method again makes the newest message the one the Extractor returns. Open the Extractor to read a file back, or the PDF, Image and Text Hiders for other kinds of carrier.

What Changes in the File

Custom XML Parts adds one file, customXml/ep_payload.xml, one Override line in [Content_Types].xml and one relationship (Id rIdEpStego) in _rels/.rels. ZIP Comment changes only the archive comment. XML Attribute adds ep:d="EP_STEGO_V1:0:…" attributes and declares the ep namespace as ignorable on the root element. Hidden Text adds one hidden run; Revision Tracking adds one tracked insertion and turns Track Changes on in word/settings.xml. ZIP Extra Field changes only bytes in the central directory.

Every other part of the document is left as it was: the tests compare each untouched part byte for byte with the original, check every ZIP CRC and XML file, and open the result with python-docx, openpyxl, python-pptx and odfpy. The one exception worth knowing: Revision Tracking edits word/settings.xml, and the new <w:trackRevisions/> element is appended at the end of that file rather than at its position in the schema sequence. We could not test any of this in Word.

Choosing a Method

Quick mode offers Custom XML Parts only. Standard mode adds ZIP Comment and XML Attribute. Forensic mode adds Hidden Text, Revision Tracking and ZIP Extra Field, Reed-Solomon error correction, per-recipient copies and a two-layer decoy mode. The Personal, Professional and CTF buttons are presets on top of that: Professional switches to per-recipient copies with Custom XML Parts and RS-20, and CTF switches the default method to ZIP Comment with no error correction.

If the document will be opened and saved again, use Custom XML Parts, XML Attribute, Hidden Text or Revision Tracking: those were kept by a python-docx re-save (and Custom XML Parts and XML Attribute by a python-pptx re-save). If it will be sent as it is, ZIP Comment and ZIP Extra Field are fine. An XLSX re-saved with openpyxl lost every method, so for spreadsheets send the file as it is.

Copies for Several Recipients

In Forensic mode (or with the Professional preset) you can enter up to 200 recipients, one per line or as a CSV, and the tool makes one copy per recipient, each hiding a different watermark ID in the method you chose, plus a watermark_keys.json file that maps each ID to its recipient. If a leaked copy still holds its ID, the Extractor can use the key file to name the recipient; the key file alone cannot identify a copy whose ID was removed.

Copies are named after the recipient with a number added when two names would clash, so no copy overwrites another. A recipient whose copy could not be made is listed in the key file and left out of the ZIP, and a run that would need more than 800 MB of memory is refused.

Capacity by Method (Computed by the Tool)

Largest typed message in bytes for a generated DOCX with 100 paragraphs and 6 ZIP entries, computed by the tool's own capacity function. The file-name frame (1 byte for text), Reed-Solomon parity (RS-10) and the 48 bytes a password adds are all counted, and a message of exactly this size is accepted while one byte more is refused. ZIP Extra Field scales with the number of ZIP entries and XML Attribute with the number of paragraphs, sheets or slide ids; the three methods without a fixed limit are capped at 16 MiB by the tool.

MethodStored limitNo passwordPasswordRS-10Password + RS-10
Custom XML Parts16,777,216 B16,777,215 B16,777,167 B16,057,139 B16,057,101 B
ZIP Comment48,750 B48,749 B48,701 B46,633 B46,595 B
XML Attribute15,000 B14,999 B14,951 B14,333 B14,285 B
Hidden Text16,777,216 B16,777,215 B16,777,167 B16,057,139 B16,057,101 B
Revision Tracking16,777,216 B16,777,215 B16,777,167 B16,057,139 B16,057,101 B
ZIP Extra Field393,186 B393,185 B393,137 B376,289 B376,241 B

What Survives a Re-Save (Measured with Libraries)

Each method was used to hide a short message in a DOCX made by python-docx, an XLSX made by openpyxl and a PPTX made by python-pptx. Each file was then opened and saved again with the same library and read with the Extractor's code. These are library re-saves: Microsoft Word, Excel, PowerPoint, LibreOffice and Google Docs were not tested, and ODT was not re-saved. One file per format and one library version each (python-docx 1.2.0, openpyxl 3.1.5, python-pptx 1.0.2): treat it as an indication.

MethodDOCX (python-docx)XLSX (openpyxl)PPTX (python-pptx)
Custom XML PartsKeptLostKept
ZIP CommentLostLostLost
XML AttributeKeptLostKept
Hidden TextKeptNot availableNot available
Revision TrackingKeptNot availableNot available
ZIP Extra FieldLostLostLost

Where Each Method Stores Data

MethodWhereWhat changesVisible to someone who unzips it?
Custom XML PartscustomXml/ep_payload.xmlOne new part, one Override line, one relationshipYes
ZIP CommentZIP end-of-central-directory commentOnly the archive commentYes
XML Attributeep:d attributes on paragraphs, sheets or slide idsAdded attributes plus a namespace and mc:Ignorable on the rootYes
Hidden TextA hidden run in word/document.xmlOne run at the start of the first paragraphYes
Revision TrackingA tracked insertion in word/document.xmlOne insertion, plus Track Changes switched on in word/settings.xmlYes
ZIP Extra FieldCentral-directory extra fields, tag 0x4550Only central-directory bytesYes, with a ZIP tool that lists extra fields

Examples

Input: A 100-paragraph DOCX of 1,632 bytes, the message "Meet at the old bridge at 9pm.", Custom XML Parts, no password

Result: The file grows to 2,126 bytes (+494). It gains customXml/ep_payload.xml with <ep:data>AE1lZXQgYXQgdGhlIG9sZCBicmlkZ2UgYXQgOXBtLg==</ep:data>, the line <Override PartName="/customXml/ep_payload.xml" ContentType="application/vnd.stegotoolkit.stego+xml"/> in [Content_Types].xml and <Relationship Id="rIdEpStego" … Target="customXml/ep_payload.xml"/> in _rels/.rels.

Meaning: The 30-byte message is stored as 31 bytes: a leading 0 byte (no file name) and the text, which is why the base64 starts with AE. The same message with ZIP Comment adds only 56 bytes: the comment EP_STEGO_V1:AE1lZXQg… and nothing else.

Input: The same message with a password

Result: The stored body grows from 31 to 79 bytes (31 plus 48 for the AES-GCM wrapper), which is 108 base64 characters instead of 44. The Extractor shows 79 bytes of encrypted-looking data until you enter the password.

Meaning: Encryption changes how much is stored, not whether the data can be found: the part, comment or attribute is still there. With a wrong or missing password the Extractor shows the still-encrypted bytes instead of an error.

Input: A 48,750-byte message, then a 48,751-byte message, with ZIP Comment and no password

Result: The capacity bar counts the 1-byte file-name frame, so the longest typed text is 48,749 bytes; one byte more is refused with a message that suggests Custom XML Parts. Feeding the encoder the framed 48,750 bytes directly works, and 48,751 is refused.

Meaning: The ZIP comment holds 65,000 base64 characters, which is 48,750 bytes of stored data. What you can type is smaller by the frame, a password's 48 bytes and any Reed-Solomon parity.

Input: A PowerPoint file with 6 slides, XML Attribute, a 900-byte message

Result: A real PPTX lists its slides as <p:sldId> elements, which are the 6 places the tool writes to, so it holds 6 × 150 = 900 bytes stored (899 typed). Earlier versions looked for a <p:sld> element that this part does not contain and refused every real PowerPoint file.

Meaning: Capacity depends on the file: a 3-sheet workbook holds 450 bytes with this method, a 100-paragraph document 15,000. The capacity bar shows the figure for your file.

Input: Revision Tracking on a DOCX

Result: word/document.xml gains <w:ins w:id="0" w:author="EP_STEGO" w:date="2024-01-01T00:00:00Z"><w:r><w:rPr><w:vanish/></w:rPr><w:t>EP_STEGO_V1:…</w:t></w:r></w:ins>, and word/settings.xml gains <w:trackRevisions/>.

Meaning: Anything the recipient types afterwards is tracked, which can draw attention. The author name and revision id are fixed, so the insertion is easy to recognise in the revision list.

Input: 10 recipients whose names clean up to the same text (for example a@b.com and a_b.com), Custom XML Parts

Result: The ZIP holds 10 copies with different names (the second a_b.com becomes a_b.com-2), watermark_keys.json lists 10 different IDs, and each copy decodes to its own watermark.

Meaning: Earlier versions named copies by replacing unusual characters with underscores and cutting at 30 characters, so clashing names overwrote each other and the ZIP held fewer copies than recipients.

How the Document Hider Works

Read the document as a ZIP

JSZip opens the file in your browser. The format comes from the file extension, and a file that is not a ZIP package is refused with a message.

Prepare the message

Your text or file gets a small frame (a flag byte, plus the file name for a file), then optional Reed-Solomon parity, then optional AES-256-GCM encryption (48 bytes added); in that order, so with a password Reed-Solomon cannot repair damaged bytes: they fail the encryption check first, and Reed-Solomon helps only payloads hidden without a password.

Check capacity

The stored size is compared with the method's limit for this document. If it does not fit, nothing is written and you get the numbers.

Write the method's data

The payload is written as a part, a comment, attributes, a hidden run, a tracked insertion or central-directory extra fields, depending on the method. Each method makes only the changes it needs to embed its payload; Revision Tracking also edits word/settings.xml to switch Track Changes on.

Rebuild the package

JSZip generates the new ZIP. For ZIP Extra Field the central directory is then rewritten with the payload; for Revision Tracking Track Changes is switched on in the settings.

Check and download

In Standard and Forensic mode the result is checked: it must open as a ZIP, keep its content-type and relationship parts, and every XML part must be well-formed. An integrity line says whether anything looked wrong. You download the new document and, optionally, a JSON report.

What the results mean

ResultWhat it meansWhat to do
Summary: method, format, payload size, size deltaThe method and format used, the size of what was hidden and how many bytes the file grew by.Compare the delta with the capacity bar; a bigger delta means a bigger payload.
Integrity line: verifiedThe output opens as a ZIP, has [Content_Types].xml and _rels/.rels (OOXML files), and every .xml and .rels part passed a well-formedness check: balanced tags and quoted, non-repeated attributes. It is not a schema validation and not a test in Word. Quick mode does not run it.Open the file in your own viewer too; Word and LibreOffice were not tested.
Integrity line: issues foundOne of those checks failed; the first five problems are listed underneath with the part name and the line.Choose another method, or re-save the original and try again.
Reed-Solomon noteRepairs up to 5, 10 or 15 changed bytes per 223-byte block for RS-10, RS-20 or RS-30.It does not rebuild data that a program removed when it saved the file.
Survival noteWhat the python-docx, python-pptx and openpyxl re-save tests showed for this method and format.Word, LibreOffice and Google Docs were not tested; send the file as it is if you are unsure.
Batch export resultHow many copies are in the ZIP; recipients whose copy could not be made are listed and also in watermark_keys.json.Fix the cause or remove those recipients; the ZIP holds only the copies that were made.

Limitations

  • Reed-Solomon parity is added before encryption, so it cannot repair a password-protected payload: damaged bytes fail the AES-GCM check first. It helps only payloads hidden without a password.
  • Only DOCX, XLSX, PPTX and ODT are supported, and the format is taken from the file extension. ODT supports only ZIP Comment and ZIP Extra Field; Hidden Text and Revision Tracking are DOCX only.
  • A document that Office encrypted with a password is not a ZIP and is refused. Files above 200 MB (50 MB on a phone) are refused.
  • Re-save results were measured with python-docx, openpyxl and python-pptx only. Microsoft Word, Excel, PowerPoint, LibreOffice and Google Docs were not tested, and the output files were not opened in them; the XML Attribute method relies on the Markup Compatibility declaration being honoured.
  • Word's Document Inspector has checks for hidden text and for custom XML data, so Hidden Text, Revision Tracking and Custom XML Parts payloads may be reported or removed; this was not tested.
  • Revision Tracking switches Track Changes on and appends the setting at the end of word/settings.xml, which was not tested in Word.
  • Reed-Solomon repairs a few changed bytes; it cannot restore a part, comment or attribute that a program removed.
  • Without a password the payload has no integrity check beyond the Extractor's length and marker checks: a changed byte in an unencrypted message is not detected. With a password, AES-GCM detects it.
  • Key-derivation time on phones was not measured.

Privacy

The document is read and written in your browser tab (a Web Worker, JSZip and the Web Crypto API) and is not uploaded. Usage statistics may record which options were used, never the document, the message or the password. The hosting provider keeps ordinary request logs for page loads.

Which Tool Next?

Frequently Asked Questions

Will the document still open normally?

Every output was checked with Python's zipfile and XML parser and opened with the matching library for each type (python-docx for DOCX, openpyxl for XLSX, python-pptx for PPTX, odfpy for ODT; ODT re-save survival was not tested), which read the same visible content as the original. We could not open files in Word, LibreOffice or Google Docs here, so open the result in your own viewer. Revision Tracking turns on Track Changes in the document settings.

Which method should I use?

Custom XML Parts is the default: it holds the most and was kept by python-docx and python-pptx re-saves. ZIP Comment and ZIP Extra Field are lost by any re-save, so use them only for a file that is sent as it is. XML Attribute fits documents with many paragraphs or slides. Hidden Text and Revision Tracking work on DOCX only.

Is my document uploaded?

No. JSZip and the Web Crypto API process the document in this browser tab; it is not sent to a server. Usage statistics may record which options were used, never your document.

How much can I hide?

ZIP Comment holds 48,750 bytes. XML Attribute holds 150 bytes per paragraph, sheet or slide id. ZIP Extra Field holds, by the tool's own calculation, the number of ZIP entries × 65,531 bytes (65,535 minus the 4-byte tag header; a small DOCX with 6 entries holds 393,186). Our tests accept exactly that figure and refuse one byte more on the test documents. Custom XML Parts, Hidden Text and Revision Tracking have no fixed limit in the code; the tool caps them at 16 MiB. A password adds 48 bytes, the file-name frame 1 byte (more for a file), and Reed-Solomon its parity. The capacity bar counts all of it.

Can I hide a file, not just text?

Yes. Pick "Hide a File" on the payload step and choose any file. Its name is stored in front of the bytes and restored by the Extractor, which also detects the type from the file signature.

Does a re-save keep the hidden data?

We measured this with three libraries. A python-docx or python-pptx re-save kept Custom XML Parts and XML Attribute data (python-docx also kept Hidden Text and Revision Tracking), an openpyxl re-save of a workbook kept none, and every one dropped the ZIP comment and extra fields. Word, LibreOffice and Google Docs were not tested; run the saved copy through the Extractor before relying on it.

Is hiding text in Word the same as steganography?

Word's hidden-text property only stops the text being displayed. The Hidden Text method uses that property for a base64 string, so anyone who shows hidden text sees it. The other methods keep the data out of the page content: a separate part, the ZIP comment, an attribute, a tracked insertion or the ZIP extra fields. None of them hides that the data exists from someone who unzips the file.

What does the password do?

With a password the payload is encrypted in your browser with AES-256-GCM, using a key derived with PBKDF2 (100,000 iterations, random salt and IV; 48 bytes added). A wrong password does not raise an error in the Extractor: it shows the still-encrypted bytes. The password protects the content, not the fact that something is hidden, and a forgotten password cannot be recovered.

Does it work with password-protected Word files?

No. A document that Office encrypted is not a ZIP archive and cannot be read in the browser, so it is refused. Remove the document password in Office first, then encode.

Why is the payload listed in [Content_Types].xml?

Every part of an Office package must be registered there. The Custom XML Parts method adds one Override entry and one relationship (Id rIdEpStego) so the package stays valid; the Extractor uses them to find the part.

Related Tools