Steganography is the practice of hiding information inside ordinary data so that the very existence of the hidden message is concealed. While many early techniques relied on image or audio carriers, text steganography has gained attention because text is ubiquitous and easy to distribute. The Devanagari scriptused for Hindi, Marathi, Nepali, Sanskrit, and several other Indian languagesoffers a rich set of orthographic features that can be exploited for covert communication. This page explains the most practical Devanagaribased steganographic methods, discusses their security considerations, and provides sample code snippets.
U+200C (ZERO WIDTH NONJOINER) and U+200D (ZERO WIDTH JOINER) to control ligature formation, which are invisible in rendering. (U+090F) vs (U+090F U+0902) where the chandrabindu may be omitted without changing meaning in casual writing.Unicode contains several zerowidth characters that are completely invisible when displayed. By mapping binary data to a selection of these characters, we can embed bits without altering the visible text. The most common set includes:
| Character | Code Point | Binary representation |
|---|---|---|
| Zero Width Space | U+200B | 00 |
| Zero Width NonJoiner | U+200C | 01 |
| Zero Width Joiner | U+200D | 10 |
| Word Joiner | U+2060 | 11 |
Implementation steps:
Original: Encoded :
Devanagari vowel signs (matras) can be placed before, after, above, or below the base consonant. Certain matras (e.g., , , ) have two possible Unicode sequences:
U+0906) + U+0905 U+093E)Choosing one representation over the other does not affect rendering but can encode a single bit. For longer messages, the same principle can be extended to use multiple matras.
Word: ""OptionA: U+092A U+093E U+0910 (precomposed) bit 0 OptionB: U+092A U+0902 U+093E (base + anusvara + matra) bit 1
The nukta (dot) modifies a consonant to represent sounds borrowed from Persian, Arabic, or English. Some phonemes can be written with or without the nukta depending on regional spelling conventions. For example:
(U+0915 U+093C) vs + (U+0915 U+200C U+093C)Using the presence (1) or absence (0) of a ZWNJ before the nukta provides another binary channel.
In Devanagari, a halant (U+094D) suppresses the inherent vowel, creating conjunct consonants. Certain consonant clusters can be expressed in two ways:
Choosing between the two encodes a bit. Example with the cluster :
Explicit: (U+0915 U+094D U+200D U+0937) bit 0 Conjunct : (U+0915 U+094D U+0937) bit 1
To increase capacity, a hybrid approach can be used. A typical encoding pipeline:
This method balances payload size with imperceptibilitylarger payloads require more varied text, but the natural diversity of Devanagari reduces suspicion.
The following minimal code demonstrates embedding using zerowidth characters. It can be extended to include matra and nukta variants.
// Simple ZWC steganography for Devanagari textconst zwcMap = { '00': '\u200B', // Zero Width Space '01': '\u200C', // Zero Width NonJoiner '10': '\u200D', // Zero Width Joiner '11': '\u2060' // Word Joiner};const revZwcMap = Object.fromEntries( Object.entries(zwcMap).map(([bits, char]) => [char, bits]));function textToBinary(text) { // UTF8 encoding then binary string const encoder = new TextEncoder(); return Array.from(encoder.encode(text)) .map(b => b.toString(2).padStart(8, '0')) .join('');}function binaryToZwc(bin) { let zwc = ''; for (let i = 0; i < bin.length; i += 2) { const bits = bin.substr(i, 2); zwc += zwcMap[bits]; } return zwc;}function embed(secret, cover) { const bin = textToBinary(secret); const zwcSeq = binaryToZwc(bin); // Insert after every punctuation or at the end if none const punct = /[!?]/g; let idx = 0, result = ''; for (let i = 0; i < cover.length; i++) { result += cover[i]; if (punct.test(cover[i]) && idx < zwcSeq.length) { result += zwcSeq[idx++]; } } // Append remaining ZWCs result += zwcSeq.slice(idx); return result;}function extract(stego) { const zwcChars = Object.values(zwcMap).join(''); const filtered = Array.from(stego).filter(ch => zwcChars.includes(ch)).join(''); let bits = ''; for (const ch of filtered) { bits += revZwcMap[ch]; } // Convert bits to bytes const bytes = []; for (let i = 0; i < bits.length; i += 8) { const byte = bits.substr(i, 8); if (byte.length === 8) bytes.push(parseInt(byte, 2)); } const decoder = new TextDecoder(); return decoder.decode(new Uint8Array(bytes));}// Example usage:const cover = ' ';const secret = 'HideMe';const stego = embed(secret, cover);console.log('Stego text:', stego);console.log('Recovered:', extract(stego));Devanagaris inherent visual flexibility, combined with Unicodes invisible control characters, offers a fertile ground for text steganography. By exploiting zerowidth characters, matra alternatives, nukta variants, and optional halant representations, it is possible to embed a meaningful amount of secret data while keeping the carrier text indistinguishable from ordinary Hindi prose. Careful attention to normalization, font handling, and statistical camouflage is essential for a robust implementation. With the example code above as a starting point, developers can build custom tools tailored to specific threat models and communication channels.
