最終更新:
ステガノグラフィ - テキストに秘密のメッセージを隠す技術と文字数
この段落には秘密のメッセージが隠されています - と言われたら、あなたはどこを探しますか? 各文の頭文字? 特定の文字の間隔? それとも、目に見えない文字が埋め込まれている可能性? ステガノグラフィ (steganography) は、メッセージの存在そのものを隠す技術です。暗号化が「読めない形にする」技術なら、ステガノグラフィは「そこにメッセージがあること自体を気づかせない」技術。そしてこの技術は、文字数カウントという単純な行為で検出できることがあります。
古代から続く「隠す」技術
ステガノグラフィの歴史は紀元前 5 世紀のギリシャにまで遡ります。歴史家ヘロドトスが『歴史』に書き残した 2 つのエピソードが最古の記録とされています。1 つは、ヒスティアイオスが最も信頼する召使いの頭を剃って頭皮にメッセージを刻み、髪が伸びるのを待ってから配下のアリスタゴラスへ送り出した話です。使者自身は自分が何を運んでいるか読めず、受け取る側は「着いたら頭を剃って見よ」という指示だけで内容を取り出しました。もう 1 つは、デマラトスがギリシャへの攻撃計画を伝えるために、蝋を塗った書字板の木の下地に直接文字を書き、その上から蝋を塗り直した話です。当時の書字板は蝋を溶かして繰り返し使う筆記具だったため、蝋の面が白紙であれば誰も下地を疑いませんでした。
この 2 つは、隠し方の性質が対照的です。頭皮の刺青は髪が伸びるまで送り出せない代わりに、途中で検査されても文字が出てこない。蝋の書字板は即座に送れる代わりに、蝋を削られれば一発で露見します。「発覚しにくさ」と「伝達の速さ」がトレードオフになる構図は、後述する現代のデジタル手法でもそのまま繰り返されます。
その後も、不可視インク (レモン汁、牛乳、尿など) を使った秘密通信や、運び手が身に着けた毛糸にモールス符号を編み込む手法など、物理的に隠す工夫が長く使われてきました。20 世紀に入ると、文書を写真で極小に縮小するマイクロドットが登場します。直径 1 ミリメートル前後の円形に縮小された像は、活字のピリオドや小文字 i の点と見分けがつかず、そのまま郵便で運べました。この方式はまず戦間期のドイツで使われ、その後さまざまな国が検閲下の郵便経路を通す手段として採用しました。
テキストベースのステガノグラフィ手法
デジタル時代のテキストステガノグラフィには、いくつかの代表的な手法があります。
アクロスティック - 頭文字に隠すメッセージ
アクロスティック (acrostic) は、各行や各文の頭文字を繋げると秘密のメッセージが現れる手法です。最も古典的なテキストステガノグラフィであり、詩や歌詞に古くから使われてきました。
実際に問題になった例として、2009 年 10 月にカリフォルニア州知事アーノルド・シュワルツェネッガーが州議会議員トム・アミアーノ提出の法案に対して出した拒否権メッセージがあります。本文 3 行目から 9 行目の頭文字を縦に読むと罵倒の一句になっており、知事側は偶然だと説明しましたが、統計的に偶然とは考えにくいと数学者から反論が出ました。この一件が示すのは、アクロスティックの厄介さは埋め込みの巧妙さではなく「偶然か意図的かを本人以外が確定できない」点にあるということです。
アクロスティックは文字数を増やさずにメッセージを埋め込める一方、埋め込める情報量は行数に縛られます。10 行の文章なら 10 文字しか隠せず、しかも各行の書き出しを指定の文字で始めなければならないため、文面が不自然になりやすい。探そうと思って頭文字を拾えば誰でも見つけられる点も含め、秘匿性は現代の手法に比べて低い手法です。
空白文字の操作
単語間のスペースの数を操作してビット情報を埋め込む手法です。スペース 1 つを「0」、スペース 2 つを「1」として、バイナリデータをエンコードします。人間の目にはスペースの微妙な違いは気づきにくいですが、文字数カウントツールを使えば「見た目の単語数に対してスペースが多すぎる」ことで検出できます。
ゼロ幅文字ステガノグラフィ - 見えない文字の世界
現代のテキストステガノグラフィで最も強力な手法が、ゼロ幅の不可視文字を利用する方法です。Unicode には、画面上に表示されないが文字データとしては存在する「ゼロ幅文字」が複数定義されています。
| Unicode コードポイント | 名称 | 本来の用途 | ステガノグラフィでの役割 |
|---|---|---|---|
| U+200B | Zero Width Space | 改行可能位置の指定 | ビット「0」を表現 |
| U+200C | Zero Width Non-Joiner | 合字の抑制 | ビット「1」を表現 |
| U+200D | Zero Width Joiner | 合字の促進 | 追加のビット値 |
| U+FEFF | Zero Width No-Break Space (BOM) | バイト順マーク | 区切り文字 |
U+200B と U+200C の 2 種類のゼロ幅文字を使えば、2 値 (0 と 1) で 1 ビットを表現できます。8 つのゼロ幅文字で 1 バイト、つまり 1 つの ASCII 文字を隠せます。「Hello」という 5 文字のメッセージを隠すには、40 個のゼロ幅文字が必要です。
この 40 個のゼロ幅文字を通常のテキストの単語間に分散して埋め込めば、見た目は全く変わりません。しかし、文字数カウントツールで「見た目の文字数」と「実際のコードポイント数」を比較すると、不自然な差異が検出されます。ゼロ幅文字は UTF-8 では 1 個が 3 バイトなので、40 個で 120 バイトの増加としても現れます。Unicode の基礎知識を理解していれば、この差異の原因がゼロ幅文字であることを特定できます。
ゼロ幅文字ステガノグラフィの実装例
具体的な埋め込みの流れを見てみましょう。秘密メッセージ「Hi」を通常のテキスト「Good morning」に埋め込む場合を考えます。
「H」の ASCII コードは 72、2 進数で 01001000 です。「i」は 105、2 進数で 01101001 です。0 を U+200B (ゼロ幅スペース)、1 を U+200C (ゼロ幅非接合子) に変換すると、16 個のゼロ幅文字列が生成されます。
この 16 個のゼロ幅文字を「Good」と「morning」の間に挿入します。見た目は「Good morning」のままですが、実際のデータには 16 個の不可視文字が含まれています。テキストエディタで文字数を数えると 12 文字ですが、プログラムで Unicode コードポイント数を数えると 28 文字になります。この差の 16 文字が、隠されたメッセージの正体です。
より高度な実装では、3 種類以上のゼロ幅文字を使って 3 値以上のエンコーディングを行い、同じメッセージをより少ないゼロ幅文字で表現します。U+200B、U+200C、U+200D の 3 種類を使えば 1 文字あたり log₂3 ≒ 1.58 ビットを運べるので、8 ビット分の情報量は理屈の上では 5.05 文字に収まります。ただし実装では端数を切り上げる必要があります。3 種類 5 文字で表せる組み合わせは 3 の 5 乗 = 243 通りしかなく、1 バイトの 256 通りを覆えないため、実際には 6 文字 (3 の 6 乗 = 729 通り) を使うことになります。2 値方式の 8 文字から 6 文字へ、25 % の短縮です。
ホモグリフ攻撃 - 見た目が同じ別の文字
ホモグリフ (homoglyph) とは、見た目がほぼ同一だが Unicode のコードポイントが異なる文字のことです。例えば、ラテン文字の「a」(U+0061) とキリル文字の「а」(U+0430) は、多くのフォントで全く同じ見た目になります。
| ラテン文字 | コードポイント | キリル文字 | コードポイント | 見た目の違い |
|---|---|---|---|---|
| a | U+0061 | а | U+0430 | ほぼ同一 |
| e | U+0065 | е | U+0435 | ほぼ同一 |
| o | U+006F | о | U+043E | ほぼ同一 |
| p | U+0070 | р | U+0440 | ほぼ同一 |
| c | U+0063 | с | U+0441 | ほぼ同一 |
この特性を悪用したのがホモグリフ攻撃です。フィッシングサイトの URL で「apple.com」の「a」をキリル文字の「а」に置き換えると、見た目は同じですが全く別のドメインに誘導されます。ステガノグラフィの文脈では、テキスト中の特定の文字をホモグリフに置き換えることで、ビット情報を埋め込むことができます。
ホモグリフの検出には、文字列の各文字の Unicode コードポイントを確認する必要があります。パスワードの文字数と安全性で触れたように、見た目が同じでもバイト列が異なるケースは、セキュリティ上の重大なリスクになります。
ホモグリフ攻撃の対策として、主要なブラウザは IDN (国際化ドメイン名) の表示に制限を設けています。ドメイン名に複数のスクリプト (ラテン文字とキリル文字など) が混在している場合、ブラウザはドメイン名を Punycode (xn-- で始まるエンコード形式) で表示し、ユーザーに偽サイトであることを警告します。Firefox はバージョン 22 以降、Chrome はバージョン 51 以降がこの判定方式を採用しており、Safari は問題のある文字集合を Punycode で描画します。
テキストの透かし (ウォーターマーク) 技術
ステガノグラフィの応用として、テキストの透かし (digital watermarking) 技術があります。画像や動画の透かしは広く知られていますが、テキストにも透かしを埋め込む技術が存在します。
| 透かし手法 | 原理 | 検出方法 | 耐性 |
|---|---|---|---|
| ゼロ幅文字埋め込み | 不可視文字でビット情報を格納 | 文字数カウント | コピー&ペーストで消える場合がある |
| 同義語置換 | 「大きい」→「巨大な」のように同義語で置換 | 原文との比較 | テキスト編集に強い |
| 構文変換 | 能動態 → 受動態のように構文を変換 | 原文との比較 | テキスト編集に強い |
| 空白操作 | スペースやタブの数を操作 | 空白文字の統計分析 | フォーマット変更で消える |
同義語置換による透かしは、テキストの意味を変えずにビット情報を埋め込む手法です。例えば、「大きい」を「巨大な」に置き換えることで 1 ビットの情報を表現します。この手法は文字数が変わる可能性がありますが、テキストの編集やコピー&ペーストに対して耐性があります。
暗号化とステガノグラフィの違い
暗号化 (encryption) とステガノグラフィは、しばしば混同されますが、根本的に異なる技術です。
| 特性 | 暗号化 | ステガノグラフィ |
|---|---|---|
| 目的 | メッセージの内容を読めなくする | メッセージの存在を隠す |
| 検出可能性 | 暗号文の存在は明らか | メッセージの存在自体が不明 |
| 文字数への影響 | 元のテキストと同程度 | カバーテキストの文字数が増加する場合がある |
| 鍵の必要性 | 復号に鍵が必要 | 手法の知識があれば抽出可能な場合も |
| 組み合わせ | 単独で使用可能 | 暗号化と併用すれば発覚後も内容が守られる |
最も安全なアプローチは、メッセージを暗号化した上でステガノグラフィで隠すことです。仮にステガノグラフィが破られてメッセージの存在が発覚しても、暗号化されていれば内容は読めません。
文字数カウントによるステガノグラフィの検出
テキストベースのステガノグラフィを検出する最もシンプルな方法は、文字数カウントです。以下のような不自然な差異が検出のヒントになります。
見た目の文字数と実際の文字数 (コードポイント数) の不一致。ゼロ幅文字が埋め込まれている場合、テキストエディタで見える文字数よりも、プログラムで数えた文字数の方が多くなります。例えば、見た目は 100 文字のテキストが、実際には 180 文字分のデータを持っている場合、80 個のゼロ幅文字が埋め込まれている可能性があります。
文字のエンコーディングサイズの不自然さも手がかりになります。純粋な ASCII テキスト (英数字のみ) であれば、UTF-8 で 1 文字 = 1 バイトです。しかし、キリル文字のホモグリフが混入していると、見た目は ASCII なのに一部の文字が 2 バイトになります。テキスト全体のバイト数が文字数より大きい場合、ホモグリフの存在を疑うべきです。
Twitter (現 X) の文字数カウントとゼロ幅文字
Twitter (現 X) の文字数カウントライブラリ「twitter-text」は、文字ごとに重みを割り当て、その合計が上限 280 に収まるかで投稿の可否を判定します。設定では既定の重みが 2 で、U+0000〜U+10FF や U+2000〜U+200D といった一部の範囲だけが重み 1 と定義されています。ゼロ幅スペース (U+200B)・ゼロ幅非接合子 (U+200C)・ゼロ幅接合子 (U+200D) はこの重み 1 の範囲に含まれるため、画面に見えなくても 1 文字ずつ枠を消費します。範囲の外にある U+FEFF は既定どおり重み 2 で数えられます。
結果として、ゼロ幅文字で情報を隠した投稿は見た目より必ず「長い」ことになります。2 値方式なら秘密メッセージ 1 文字あたり 8 個のゼロ幅文字が必要なので、280 の枠を全部つぎ込んでも運べるのは 35 文字。カバーテキストの分を引けばさらに減ります。文字数の上限そのものが、ゼロ幅文字ステガノグラフィの容量上限として働いているわけです。
ステガノグラフィの検出ツールと手法
テキストベースのステガノグラフィを検出するための専門ツールやテクニックがいくつか存在します。
| 検出手法 | 対象 | 原理 | 限界 |
|---|---|---|---|
| 文字数 vs バイト数の比較 | ゼロ幅文字 | 見た目の文字数と実際のバイト数の不一致 | 正当なゼロ幅文字との区別が困難 |
| Unicode カテゴリ分析 | ホモグリフ | テキスト中の文字が属する Unicode ブロックの一貫性を検証 | 多言語テキストでは誤検出が多い |
| 統計的分析 | 空白操作 | スペースの分布が自然言語の統計と一致するか検証 | 短いテキストでは精度が低い |
| エントロピー分析 | 全般 | テキストの情報エントロピーが自然言語の範囲内か検証 | 高度な手法には対応困難 |
最もシンプルで効果的な検出方法は、テキストを一度プレーンテキストにコピー&ペーストし、元のテキストとバイト数を比較することです。ゼロ幅文字やホモグリフが含まれていれば、バイト数に差異が生じます。文字数カウントツールで「見た目の文字数」と「Unicode コードポイント数」の両方を表示する機能があれば、この差異は数字を見比べるだけで確認できます。
ゼロ幅文字が持ち込む実務上のリスク
ゼロ幅文字は、意図的な秘密通信よりも先に「意図せず混入する」形で問題になります。Web ページからコピーしたテキストにゼロ幅文字が紛れ込み、検索語が一致しない、識別子の照合が通らない、入力欄の文字数制限に引っかかる、といったつまずきです。見た目が完全に同じなので、目で見比べる限り原因にたどり着けません。「見えている文字数」と「コードポイント数」が食い違うことに気づけるかどうかが分かれ目になります。
同じ性質は追跡にも使えます。配布する文書ごとに異なるゼロ幅文字の並びを仕込んでおけば、見た目を一切変えずに配布先を識別する印を残せる、というのが文書フィンガープリンティングの考え方です。流出した文書からこの並びを読み取れば、どの配布先から出たものかを絞り込めます。ただしゼロ幅文字は、コピー&ペーストの経路やテキストを正規化する処理で失われることがあるため、印がどこまで残るかは経路に依存します。
ステガノグラフィそのものは、善悪のどちらにも寄らない技術です。通信の存在を隠して身を守る使い方もあれば、情報を持ち出す側が痕跡を消す使い方もあります。どちらの側から見ても、「見えている文字数」と「実際のコードポイント数・バイト数」を突き合わせるという単純な確認が、隠されたものに最初に触れる手がかりになります。