HTML Steganography Hider
Hide secret messages inside HTML, SVG and XML files using zero-width characters, comments, whitespace, attribute order, case variation, and entity substitution. Optional AES-256-GCM encryption. The methods aim to keep normal browser rendering, but some can change text content or be removed by HTML processing tools.
Drop HTML file or click to browse
.html, .htm, .svg, .xml — max 50 MB
How to Hide a Message in an HTML File (5 steps)
- Upload an HTML file or paste your HTML source code
- Enter the secret message or file you want to hide
- Choose a steganography method (Zero-Width Characters recommended)
- Optionally add a password for AES-256 encryption
- Click Encode and download your HTML file; compare the rendered previews on the result page
HTML Steganography Methods — Six Ways to Hide Data
| Method | Capacity | Detection Risk | Best For |
|---|---|---|---|
| Zero-Width Characters | High | Visible in a character-level view | Any HTML with text |
| HTML Comments | Medium | Visible in View Source | Markup-rich HTML |
| Whitespace / Newline | Medium | Visible with show-whitespace | Formatted HTML |
| Attribute Order | Low | Visible by comparing with the original | Attribute-rich HTML |
| Case Variation | Low | Visible by comparing with the original | Any HTML |
| Entity Substitution | Low | Visible by comparing with the original | HTML with entities |
Detection Risk describes how visible the change is under the inspection methods named in the row. It is a qualitative guide, not a measured detection rate, and no detection tool was tested against any method.
What This Tool Hides, and Where
The carrier is a text file of HTML source: .html, .htm, .svg or .xml, up to 50 MB (10 MB on a phone). The hidden payload is typed text or one or more files, which are bundled into a ZIP first when you attach several. Nothing is converted: the output is the same markup with small edits, saved with the same extension.
The six methods write in different places in HTML source. Zero-Width Characters go after visible characters in text. HTML Comments are added after closing tags. Whitespace adds one space or tab at the end of a line. Attribute Order sorts the attributes of a tag in one of two orders. Case Variation writes a tag name in lower or upper case. Entity Substitution spells &, <, >, " and in the named or the numeric form. The first reference table below lists what each method touches and what it skips. For XML, SVG and XHTML files some methods have extra restrictions: Case Variation is not available, Entity Substitution skips , and Zero-Width and Whitespace follow XML-specific rules (see HTML, SVG and XML Files below).
Every method stores the same structure: a 4-byte length field followed by the stored data, written as a string of bits, one bit per slot. The stored data begins with a one-byte flag that says whether a file name follows (for a file, the name comes next, so a hidden file keeps its name), then your message or file. The length counts all of it: the flag, any file name, the 48 bytes added by a password and the Reed-Solomon parity, if you use them. For "Hi" with no password the length is 3 (00 00 00 03): one flag byte plus two message bytes. A slot is a place where the method can write one bit. The number of slots decides the capacity, so it depends on your file, not only on its size: two HTML files of the same size can have very different capacities because each method depends on different eligible places in the source. The tool counts the slots exactly before you encode.
Choosing a Method
On the figures page, Zero-Width Characters had the largest capacity by far, and it was the only method still readable after both tested transformations (an aggressive html-minifier-terser 7.2.0 run and a parse5 7.1.2 parse and re-serialise). It usually has the most room on pages with running text, while a page with no text, such as a tag-only template, has none. Results may differ with other files and other processing pipelines. Use it unless you need the file to look unchanged in a text diff, in which case nothing here is subtle: every method is visible to a tool that compares the file with the original.
Attribute Order, Case Variation and Entity Substitution change no visible character and add nothing, but they carry very little and are lost when the HTML is parsed and written out again. They suit small messages in files you control. Comments and Whitespace add visible text to the source.
To hide a message in plain text instead of an HTML file, use the Invisible Text Hider or the Unicode Steganography Hider. To hide data in a DOCX, XLSX or PPTX file, see Document Steganography.
Password, Error Correction and Size
A password encrypts the data with AES-256-GCM before it is written, with a key derived by PBKDF2 (100,000 iterations). Encryption adds 48 bytes. It hides the content, not the fact that something is hidden: the characters, comments or tag changes that carry the bits are still there.
Reed-Solomon is in Forensic mode (the third option of the Quick / Standard / Forensic switch at the top of the Configure step): the section is titled Error Correction (Reed-Solomon) and offers None, rs-10, rs-20 and rs-30. It adds a 16-byte header plus 10, 20 or 30 parity bytes for every 223 data bytes (levels rs-10, rs-20, rs-30) and can repair up to 5, 10 or 15 changed bytes per block. It repairs scattered changes inside the stored data. It cannot repair a method that was erased, for example comments removed by a minifier, and the 4-byte length at the start is not protected by it.
There is no compression step in this tool. The second reference table shows the stored size of a 100-byte message with and without a password and Reed-Solomon.
HTML, SVG and XML Files
The tool opens .html, .htm, .svg, .xml and .xhtml. A file is treated as XML when its name ends in .svg, .xml or .xhtml, or when it starts with an XML declaration or an <svg> root element; everything else is treated as HTML. XML is stricter than HTML: tag names are case-sensitive, only five entities are defined (& < > " '), and nothing except whitespace and comments may stand outside the root element. Three methods therefore behave differently on XML. Case Variation is not available (it refuses with a message). Entity Substitution leaves alone. Zero-Width never writes outside the root element. Whitespace only marks lines that end in a tag and is not used when the file contains xml:space="preserve".
These rules were checked on 20 real SVG and XML files (icons, a Maven POM, an RSS feed, Apple property lists) with the xmllint program: every output was still well-formed XML and had the same canonical XML as the cover once the embedded bits were ignored. Without them, Case Variation, Zero-Width and Entity Substitution each produced files that xmllint rejected. Other XML parsers, schema validators and editors were not tested.
Watermark Batch Export
Watermark Batch Export is on the result page. It makes one copy of your ORIGINAL file per recipient (the message you just hid is not in these copies), using the method you chose. Each copy hides a small JSON note: a watermark ID, the recipient's ID and a time stamp. It has no password and uses Reed-Solomon rs-10. The watermark ID is 128 bits derived with HKDF-SHA-256 from a random secret created for that run, the SHA-256 hash of the cover file and the recipient's ID, so every recipient gets a different ID. The secret is not saved anywhere and is not needed afterwards.
You get two downloads: a ZIP with one file per recipient, and watermark_keys.json, which lists every ID with the recipient's name, e-mail, the method and the time. Keep that key file to yourself and do not put it in the ZIP. To find out which copy leaked, open the leaked file in the HTML Extractor, then open Watermark Attribution and load watermark_keys.json: it compares the ID read from the file with the key file and allows a few wrong bits.
Up to 200 recipients are accepted per run; blank and repeated lines are ignored, and two recipients whose names would give the same file name get different file names. If a copy cannot be made (for example the cover has too few places for the method) it is left out of the ZIP, shown on screen and listed under "failed" in the key file. If no copy can be made, nothing is downloaded and the reason is shown. The IDs identify a copy only while the hidden note survives: a method that is removed by a minifier or an editor takes the ID with it.
Using It on Real Pages
If the cover already contains the characters or comments a method uses to carry bits, the tool removes them first and tells you how many it removed: U+200B and U+200C in text, comments or CDATA (Zero-Width; the Hider writes only into text, but the Extractor reads all three places, so none may be left), or comments made only of one or two spaces (Comments). Without that, the Extractor would read them as part of your message.
Nothing is ever written inside <script> or <style>, and attribute values are changed only by Entity Substitution, which respells entities there (both spellings mean the same thing to a browser). Whitespace is the exception that can change a page: a space or tab at the end of a line inside <pre> or <textarea> is part of that content. Compare the previews on the result page; the tool does not compare rendering for you.
Capacity by Method (figures page)
The figures page is a doctype, <html>, a head with <title>Test</title>, a body, and 100 lines of the form <p class="note" id="n0">Item 0: Cats & dogs play</p> (index 0 to 99), 6,458 characters in all. Slots are counted by the tool itself; the largest message is the slots, minus the 32-bit length and the 1-byte file-name flag, divided by 8 and rounded down. A password takes another 48 bytes.
| Method | Slots (bits) | Largest message | Largest with a password |
|---|---|---|---|
| Zero-Width Characters | 2,599 | 319 B | 271 B |
| HTML Comments | 104 | 8 B | none (needs 48 B) |
| Whitespace / Newline | 106 | 8 B | none (needs 48 B) |
| Attribute Order | 100 | 7 B | none (needs 48 B) |
| Case Variation | 208 | 21 B | none (needs 48 B) |
| Entity Substitution | 200 | 20 B | none (needs 48 B) |
Stored Size of a 100-Byte Message
The data written into the page for a 100-byte typed message: 1 file-name flag byte, the message, 48 bytes if encrypted, then Reed-Solomon, then the 4-byte length in front.
| Settings | Bytes stored (with the 4-byte length) | Bits (slots needed) |
|---|---|---|
| No password, no Reed-Solomon | 105 | 840 |
| Password | 153 | 1,224 |
| Reed-Solomon rs-20 | 141 | 1,128 |
| Password and rs-20 | 189 | 1,512 |
Where Each Method Writes and What It Skips
| Method | Where a bit goes | Never touched |
|---|---|---|
| Zero-Width Characters | After each visible character in text (an emoji sequence or a letter with its accents counts as one; an entity such as & is never split). 0 = U+200B, 1 = U+200C. | Tags and attribute values, comments, CDATA, <script>, <style> |
| HTML Comments | After each closing tag written as </name>. 0 = <!--·-->, 1 = <!--··-->, where · stands for one literal space and is shown only to make the difference visible. | Closing tags inside scripts, styles and comments |
| Whitespace / Newline | End of each line. 0 = one space, 1 = one tab. Existing trailing whitespace on a used line is replaced; a CR LF ending keeps its CR LF. | Lines after the message ends |
| Attribute Order | A start tag with two or more attributes whose names all differ. 0 = names in ascending order, 1 = descending. Tags that repeat a name, or end in />, are skipped. | Attribute values, tags with a repeated name |
| Case Variation | Each opening and closing tag name. 0 = lower case, 1 = upper case. Names the HTML parser spells in camel case inside inline SVG (linearGradient, foreignObject and similar) are skipped. | Everything except the tag name |
| Entity Substitution | Each & < > " or its numeric form, in text and attribute values. 0 = named, 1 = numeric (& < > "  ). | Scripts, styles, comments, CDATA |
Measured: Which Methods Survive Two Transformations
Tested with html-minifier-terser 7.2.0 and parse5 7.1.2 on the figures page (npm run test:measure:html-transforms). Cloudflare, React, specific CMSs and other tools were not tested; the result for one page is not a guarantee for yours.
| Method | Minifier: collapse whitespace, remove comments, decode entities, sort attributes | Minifier: collapse whitespace only | Parse, then write out again (parse5) |
|---|---|---|---|
| Zero-Width Characters | Readable | Readable | Readable |
| HTML Comments | Lost | Readable | Readable |
| Whitespace / Newline | Lost | Lost | Lost |
| Attribute Order | Lost | Readable | Readable |
| Case Variation | Lost | Lost | Lost |
| Entity Substitution | Lost | Readable | Lost |
Examples
Input: Hide the 2-letter message "Hi" in the figures page with Zero-Width Characters.
Result: 56 bits are written: the 4-byte length (00 00 00 03), the file-name flag (00), then 48 and 69. The file grows by 168 bytes, 56 characters of 3 bytes each.
Meaning: Each hidden bit is one inserted character, and each character costs 3 bytes in UTF-8. The characters go in document order: the first ones land after the visible characters at the start of the page, including the line breaks between tags.
Input: The same "Hi" with HTML Comments.
Result: The first used closing tags become </head><!-- -->, </p><!-- --> and so on. A 1 bit would be <!--··--> with two literal spaces (· marks a space). The file grows by 456 bytes.
Meaning: Comments are visible in the page source but not on the page. 56 of the 104 available closing tags are used.
Input: Attribute Order with "Hi" on the figures page.
Result: The file does not change in size. <p class="note" id="n0"> stays as it is for a 0 bit (class before id); for a 1 bit it would become <p id="n0" class="note">. The first 1 bit of this message is the 31st one, so the first changed tag is the 31st paragraph.
Meaning: The 4-byte length of this 3-byte stored message begins with 30 zero bits, so a short message leaves the start of the file untouched.
Input: Case Variation and Entity Substitution with the same message.
Result: <p> becomes <P> for a 1 bit; & becomes & for a 1 bit. In both cases the file size can change by a few bytes, never by a block of added text.
Meaning: These two methods rewrite text that is already there, which is why they carry little and why a tool that compares with the original finds them.
Input: A page whose script contains the text i<n; and "a && </div>".
Result: Every method leaves the script byte for byte as it was. Zero-Width does not put characters into it, and Entity Substitution does not rewrite its &.
Meaning: Scripts and styles are code, not text. Changing them could stop the page from working, so no method does.
Input: Hide 20 bytes in a page of one short paragraph.
Result: The tool refuses and says the cover is too small for the method, with the number of usable positions.
Meaning: It never writes a partial message. Capacity is checked by embedding and reading back, not by an estimate.
How the HTML Hider Works
1. Read the file as text
The file is read as UTF-8 text in your browser. A scanner splits it into tags, comments, CDATA, declarations, script and style content, and text, and every method works only on the regions it is allowed to change.
2. Build the bit string
A file-name flag byte (and the name, for a file) is put in front of your data. If you set a password the result is encrypted with AES-256-GCM; if you chose Reed-Solomon parity is added. A 4-byte length goes in front, and the bytes become bits.
3. Write one bit per slot
The chosen method writes the bits in document order, one per slot, and leaves everything after the last bit unchanged.
4. Read it back
The tool reads the finished file with the same code the Extractor uses and compares the result with what it meant to hide. If a message does not fit, or the cover contains markup the method would misread, it stops with an error instead of giving you a file.
5. Compare and download
The result page shows the original and the new page in a sandbox where scripts do not run and outside resources are blocked, and the size change. You download the new file with the same extension.
What the results mean
| Result | What it means | What to do |
|---|---|---|
| Method, Payload, Size change | The method used, the number of bytes you hid, and the change in file size in percent. | A large percentage on a small page is normal for Zero-Width and Comments. Check the Encoding Report for exact bytes. |
| Encryption | AES-256-GCM when you set a password, otherwise None. | None means anyone with a decoder can read the message. |
| Notes about the cover | How many zero-width characters or empty comments already in the file were removed before writing. | Review the output if the page used those characters for anything. |
| What Happens to the Data (measured) | A short table of the transformation results above for the method you used. | If your pipeline minifies or re-serialises the HTML, choose a method marked Readable. |
| Rendered Preview | The original and the new page side by side, scripts off, outside resources blocked. | Compare them by eye. The tool does not compare the rendering. |
| Encoded HTML Source and Encoding Report | The first 5,000 characters of the new source, the original and new size, and the number of bits written. | Use it to see exactly what was added. |
| Watermark Batch Export | One copy per recipient (up to 200) with a different ID in each, plus a separate key file that maps IDs to recipients. | Keep the key file private. A copy that could not be made is left out of the ZIP and listed in the key file. |
Limitations
- Nothing here makes a hidden message undetectable. Every method is visible to a tool that compares the file with the original, and Zero-Width, Comments and Whitespace are visible in the source. No detection tool was tested against any method.
- Survival was measured with two tools on one page (see the measured table). Other minifiers, CMSs, CDNs, editors, formatters, email clients and chat apps were not tested.
- Reed-Solomon repairs changed bytes inside the stored data. It does not bring back a method that was erased, and the 4-byte length at the start is not covered.
- Whitespace can change content inside <pre> and <textarea>. Zero-Width puts invisible characters into text everywhere, including <title>.
- Case Variation is for HTML only: it is refused for SVG, XML and XHTML files because their tag names are case-sensitive. In HTML it skips the SVG names the parser spells in camel case. Attribute Order skips tags that repeat an attribute name, because the first one wins in a browser. On XML, Whitespace marks only lines that end in a tag and Entity Substitution skips ; this was checked with xmllint only (see the HTML, SVG and XML section).
- The Zero-Width, Comments, Whitespace and Entity Substitution methods change the text or whitespace of the file, so a program that reads that text (a search index, a diff, a checksum, a screen reader on the title) can see a difference even when the page looks the same. The tool does not compare the rendering.
- An editor or formatter that normalises line endings, trailing whitespace or tag case would remove what Whitespace and Case Variation wrote (the formatters and editors themselves were not tested).
- The tool opens files up to 50 MB, and the Extractor refuses a stored message longer than 50 MB.
Privacy
The file is read and changed in your browser tab; the tool's own code does not upload your HTML or your message. Usage statistics may record which options were used and coarse facts such as sizes, never your content. Fetch URL is the exception that talks to another website: the request for the page is sent by your browser straight to that website, which can see your IP address and browser details like any visitor, and it only works when that site allows other sites to read it (CORS). The response does not pass through StegoToolkit's servers, and the address you type is not recorded in the usage statistics. If the fetch fails you are told why; download the file and upload it or paste its source instead. The previews are shown in a sandboxed frame with a policy that blocks scripts and every outside request.
Which Tool Next?
- HTML Steganography Extractor — read a message back from an HTML file this tool made.
- Invisible Text Hider — the zero-width idea for plain text, with platform-aware character sets.
- Unicode Steganography Hider — zero-width, variation selector and look-alike letter methods for plain text.
- Whitespace Steganography Hider — trailing space and tab patterns in text files and source code.
Frequently Asked Questions
Will the encoded HTML look different in a browser?
For most ordinary HTML pages the rendered appearance should stay the same, but not in every case. Whitespace can change content inside <pre> and <textarea>, and Zero-Width Characters adds invisible characters to text, including inside <title>. Script and style contents are not modified. Compare the original and the encoded previews on the result page before using the output; we did not compare screenshots pixel by pixel.
Which method should I use?
Zero-Width Characters: the largest capacity, and the only method that survived both of our transformation tests (an aggressive HTML minifier and a parse + re-serialise). The others are fragile and are mainly useful for puzzles or when you control the file. No method was tested against detection tools.
Does this work with minified HTML?
Zero-Width Characters work in already-minified HTML. Whitespace needs several lines, Comments need closing tags, Attribute Order needs tags with 2+ attributes. But running a minifier AFTER encoding is a different matter: in our test only Zero-Width survived an aggressive minifier.
Is my HTML sent to a server?
No. Processing runs in your browser tab; your HTML is not sent to a server. Usage statistics may record which options were used, never your HTML.
What happens if the HTML is processed by a CDN or CMS?
On one 80-block test page we ran the output through html-minifier-terser 7.2.0 (collapse whitespace, remove comments, decode entities, sort attributes) and through a parse5 parse + re-serialise (what an HTML parser or server-side renderer does): Zero-Width survived both; Comments were lost to comment removal; Whitespace, Case Variation and Entity Substitution were lost to both; Attribute Order survived re-serialising but not attribute sorting. Cloudflare, React and specific CMSs were not tested, so test your own pipeline.
What is the maximum payload size?
Capacity is exact for your file and shown before you encode: it is the number of places the method can write, minus a 4-byte length and a 1-byte file-name flag, then divided by 8 (about 1 byte per 8 characters of text for Zero-Width). Encryption adds 48 bytes and Reed-Solomon adds parity. A message that does not fit is refused, not cut off.
Related Tools
HTML Steganography Extractor
Find hidden messages in HTML, SVG and XML files made with the HTML Steganography Hider. Auto-Detect tries all six extraction methods, and Deep HTML Analysis lists zero-width characters, comments and whitespace in any file. CTF flag detection and AES-256-GCM decryption. 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.