I wanted to actually try out Steganography using Unicode variant selectors: How to secretly hide secret strings in text #JavaScript - Qiita, but I wanted to implement it in TypeScript and make it work properly.
So I implemented it.
Code
class Steganography {
static #components = { VS_BEGIN: 0xfe00, STR_EMPTY: "", REPLACER: /\uFEFF[\uFE00-\uFE0F]+/u, toHex(value: number) { return value.toString(16); }, toHexDecimal(str: string): number { return Number.parseInt(str, 16); }, encode: { PREFIX: "\u{FEFF}" } }
static encode(body: string, hidden: string): string { const { VS_BEGIN, STR_EMPTY, encode, toHex, toHexDecimal } = this.#components; return encode.PREFIX + [...hidden].flatMap(c => [...toHex(c.charCodeAt(0))]) .map((s) => String.fromCodePoint(VS_BEGIN + toHexDecimal(s))) .join(STR_EMPTY) + body }
static decode(value: string): [body: string, hidden: string] { const { VS_BEGIN, STR_EMPTY, REPLACER, toHex, toHexDecimal } = this.#components;
let hidden = STR_EMPTY; const body = value.replace(REPLACER, ([_, ...variationSelectors]) => { hidden += variationSelectors .map((c) => toHex(c.codePointAt(0)! - VS_BEGIN)) .reduce<string[][]>((acc, _, index, arr) => { return index % 2 === 0 ? [...acc, arr.slice(index, index + 2)] : acc; }, []) .map(([a, b]) => String.fromCharCode(toHexDecimal(a + b))) .join(STR_EMPTY); return STR_EMPTY; }); return [body, hidden] }}Points
- Variation Selector: Use the range U+FE00-U+FE0F
- ZERO WIDTH NO-BREAK SPACE(U+FEFF): Hidden string start marker
- Encoding: Convert characters to HEX and express with variant selector
- Decode: Extract with regular expression and restore original string
Usage example
const body = "公開されるテキスト";const hidden = "秘密のメッセージ";
const encoded = Steganography.encode(body, hidden);console.log(encoded); // 見た目は「公開されるテキスト」
const [decodedBody, decodedHidden] = Steganography.decode(encoded);console.log(decodedBody); // "公開されるテキスト"console.log(decodedHidden); // "秘密のメッセージ"summary
By leveraging Unicode’s glyph selector, you can embed secret strings into text in a way that is visually undetectable.
Regardless of its practicality, it’s a good subject to learn how Unicode works.
hsb.horse