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의 이체자 셀렉터를 활용하는 것으로, 외형에는 판별할 수 없는 형태로 비밀의 문자열을 텍스트에 묻을 수 있다.
실용성은 어쨌든 유니코드의 구조를 배우는 좋은 소재다.
hsb.horse