logo hsb.horse
← 블로그 목록으로 돌아가기

블로그

이체 문자 선택기를 사용한 Steganography의 TypeScript 구현

Unicode의 이체자 셀렉터를 활용해, 외형에는 보이지 않는 비밀의 캐릭터 라인을 텍스트에 임베드하는 스테가노그래피 기법의 TypeScript 구현.

게시일:

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]
}
}

포인트

  1. 이체자 선택기(Variation Selector): U+FE00-U+FE0F의 범위 사용
  2. ZERO WIDTH NO-BREAK SPACE(U+FEFF): 숨겨진 문자열 시작 마커
  3. 인코드: 문자를 HEX로 변환하고 이체자 선택기로 표현
  4. 디코드: 정규식으로 추출하여 원래 문자열을 복원

사용 예

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의 이체자 셀렉터를 활용하는 것으로, 외형에는 판별할 수 없는 형태로 비밀의 문자열을 텍스트에 묻을 수 있다.

실용성은 어쨌든 유니코드의 구조를 배우는 좋은 소재다.