テキストに隠し情報を埋め込むnpmモジュールを公開しました

4 min read読了の目安(約3600字

長すぎて読めない人へ

  • テキストデータに見えない情報を埋め込めるよ
  • npmモジュールもあるよ
  • 以下のような感じ ('やっぱり「きのこの山」'というテキストに'※たけのこ派です'というテキストを埋め込んでいる)

テキストデータに見えない情報を埋め込む例

実際に試してみる

以下のツイートをコピペして

https://twitter.com/redshoga/status/1368941947755270145

以下のサイトから埋め込みデータを抽出できます。

https://zero-width-watermark-web.vercel.app/

モジュールとサンプル

npmモジュールを公開しました。

https://www.npmjs.com/package/zero-width-watermark

以下のように簡単に使用することができます。

サンプル
import { embed, extract } from "zero-width-watermark";

// 埋め込み (string以外にUint8Arrayも埋め込みデータとして用いることができます)
const embeddedText = embed("sample", "hello world🐾");
console.log({ embeddedText });
// {
//   embeddedText: "s‌​​‌​‌‌‌‌​​‌‌​‌​‌​​‌​​‌‌a‌​​‌​​‌‌‌​​‌​​​​‌‌​‌‌‌‌‌m‌​​​‌​​​‌​​‌​​​​‌​​​‌‌​‌p‌​​‌​​‌‌‌​​‌‌​‌‌​​​​‌‌‌‌l​‌‌​​​​​​‌‌​‌‌‌‌​‌​​​​​‌e";
// }

// 抽出
const extractText = extract(embeddedText);
console.log({ extractText });
// { extractText: 'hello world🐾' }

上述のように以下のサイトから埋め込み、抽出をブラウザ上で試すことができます。

https://zero-width-watermark-web.vercel.app/

想定実用例: 著作権保護

テキストデータに著作権情報などを分散して埋め込むことによって、一部が切り取られてもそこから著作権情報などを抽出することができます。

埋め込み処理
const copyrightInfo = "<WrittenBy>@redshoga</WrittenBy>";
const blogText =
  "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s";

const embeddedBlogText = embed(blogText, copyrightInfo, { repeat: 10 });
抽出処理
// コピペされた一部のテキストデータ
const extraData = extract(
  "I‌​​‌‌​​​‌​​‌‌‌‌​‌‌​​​​‌‌p‌‌​‌​​​​‌​‌​‌​​​‌​​​‌‌​‌s‌​​‌​‌‌​‌​​​‌​‌‌‌​​​‌​‌‌u‌​​‌‌​‌​‌​​‌​​​‌‌​‌‌‌‌​‌m‌​​​​‌‌​‌‌​​​​​‌‌‌​​​​‌‌ ‌​‌​‌​​​‌​​​‌‌​‌‌​​‌​‌‌​i‌​​​‌​‌‌‌​​​‌​‌‌‌​​‌‌​‌​s‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​​‌‌​ ‌‌​​​​​‌‌​‌‌‌‌‌‌‌​​​‌‌​‌s‌​​‌‌​‌​‌​​‌‌​‌‌‌​​​‌‌​​i‌​​‌​‌‌‌‌​​‌​​​​‌​​‌‌​​​m‌​​‌‌‌‌​‌‌​​​​‌‌‌‌​‌​​​​p‌​‌​‌​​​‌​​​‌‌​‌‌​​‌​‌‌​l‌​​​‌​‌‌‌​​​‌​‌‌‌​​‌‌​‌​y‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​​‌‌​ ‌‌​​​​​‌‌‌​​​​‌‌‌​‌​‌​​​d‌​​​‌‌​‌‌​​‌​‌‌​‌​​​‌​‌‌u‌​​​‌​‌‌‌​​‌‌​‌​‌​​‌​​​‌m‌​‌‌‌‌​‌‌​​​​‌‌​‌‌​​​​​‌m‌​‌‌‌‌‌‌‌​​​‌‌​‌‌​​‌‌​‌​y‌​​‌‌​‌‌‌​​​‌‌​​‌​​‌​‌‌‌ ",
  {
    outputType: "string",
  }
) as string;
// extraData: ga</WrittenBy><WrittenBy>@redshoga</WrittenBy><WrittenBy>@redshoga

const author = extraData.match(/<WrittenBy>(.+)<\/WrittenBy>/)[1];
// author: @redshoga

このように著作権情報を抽出することができます。著作権情報以外に、購入者情報などを埋め込んでも良いかもしれないですね。

ただし実用を考えると以下のことを考える必要があり、注意が必要です。

  • 情報が書き換えや削除 (対攻撃性、 ロバスト性)
  • 情報量の問題 (今回の例では埋め込みデータに比例して埋め込みデータの情報量が大きくなります)

原理

ゼロ幅文字を使用しています。

https://ja.wikipedia.org/wiki/ゼロ幅スペース

埋め込むデータをバイナリに変換してデータの01をそれぞれゼロ幅文字の0x200b0x200cに置き換えているだけです。データの抽出方法はその逆です。

ゼロ幅文字は他の種類もあったはずなので、それらを用いて変換すれば少ない文字でより多くの情報量をもたせることができます。(このモジュールでは実装されていません)

ゼロ幅文字を見る方法

一番簡単なのはおそらくChrome DevToolsのConsoleにコピペする方法です。

以下のように赤い点でゼロ幅文字が表示されます。偉い。

電子透かし

このような技術を「電子透かし(Digital Watermarking)」といいます。
テキスト以外にも画像や音楽、動画に対してのアプローチがいろいろあります。この記事の例は非常に単純な例です。

学生のころ画像や動画を対象にいろいろやっていました。あまり画質に影響を与えずにデータにロバスト性をもたせるのが難しく面白い技術です。基本的にオリジナルの品質、ロバスト性、埋め込みデータ量がトレードオフになります。

Q&A

Q1. 車輪の再発明では?
A1. YES! ただ、 npmのモジュールではちゃんとしたものがすぐ見つからなかったのでお勉強がてらつくってみた文脈です。

Q2. 攻撃防げないよね? (攻撃 = 埋め込みデータを抽出できないようにする処理)
A2. YES! ちゃんとゼロ幅文字を消す処理をするときれいにデータは消えます。完全な攻撃耐性をテキストデータに持たせるのは難しいとおもう。

Q3. SNSとかに投稿したらゼロ幅文字とか削除されないの?
A3. 現時点でSlack、Twitterでは消されないことを確認しています。

アイデア等

アイデアはあるけど、作る気力はあまりないので誰かがつくってくれると喜びます。

  • Chrome拡張機能とかアプリでTwitter拡張とかをつくると、表の会話と裏の会話ができておもしろそう (※ゼロ幅文字も文字カウントされるので注意
  • 埋め込みデータを暗号化すれば特定のユーザグループだけ読み取れる裏情報みたいなことができそう
  • ブログデータの著作権保護とか (攻撃耐性はないので微妙だが)
  • 画像電子透かしでスクショ流出検出とか (最近のアプリはスクショ制御できるけどね...)
  • デジタル皮肉、デジタル京都弁などなど...