Unicodeの異体字セレクターを使ったステガノグラフィ:秘密の文字列をテキストにこっそり隠し込む方法 #JavaScript - Qiitaを実際に試してみたかったが、どうせならTypeScriptで実装して、ちゃんと動かしたい。
というわけで実装した。
コード
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] }}ポイント
- 異体字セレクター(Variation Selector): U+FE00-U+FE0Fの範囲を使用
- ZERO WIDTH NO-BREAK SPACE(U+FEFF): 隠し文字列の開始マーカー
- エンコード: 文字をHEXに変換し、異体字セレクターで表現
- デコード: 正規表現で抽出し、元の文字列を復元
使用例
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); // "秘密のメッセージ"まとめ
Unicodeの異体字セレクターを活用することで、見た目には判別できない形で秘密の文字列をテキストに埋め込める。
実用性はともかく、Unicodeの仕組みを学ぶ良い題材だ。
hsb.horse