最終更新:

見えない文字の世界 - ゼロ幅文字・不可視文字が引き起こすトラブル

約 9 分で読めます

あなたが入力した文字列は 10 文字のはずなのに、システムは 12 文字だと言い張る。目を凝らしても余計な文字は見えない。犯人は「ゼロ幅文字」- 画面上に一切表示されないのに、データとしては確かに存在する不可視の文字です。この記事では、Unicode に定義された不可視文字の種類と用途、文字数カウントへの影響、そして実際に起きたトラブル事例と対処法を解説します。

不可視文字の一覧 - 見えないのに存在する文字たち

Unicode には、画面に表示されない (または幅がゼロの) 文字が複数定義されています。これらは「バグ」ではなく、テキスト処理に必要な正当な理由で存在しています。

文字名コードポイント用途文字数カウント表示幅
ゼロ幅スペース (ZWSP)U+200B改行可能位置の指定1 文字としてカウント0
ゼロ幅接合子 (ZWJ)U+200D文字の結合 (絵文字合成)1 文字としてカウント0
ゼロ幅非接合子 (ZWNJ)U+200C文字の結合を防止1 文字としてカウント0
左から右マーク (LRM)U+200Eテキスト方向の制御1 文字としてカウント0
右から左マーク (RLM)U+200Fテキスト方向の制御1 文字としてカウント0
バイトオーダーマーク (BOM)U+FEFFエンコーディングの識別通常カウントしない0
ソフトハイフン (SHY)U+00ADハイフネーション位置の指定1 文字としてカウント通常 0 (改行時のみ表示)
ワードジョイナー (WJ)U+2060改行禁止位置の指定1 文字としてカウント0

これらの文字はすべて、テキスト処理において正当な役割を持っています。問題は、意図せずテキストに混入した場合に、目に見えないまま文字数カウントを狂わせることです。

ゼロ幅スペース (U+200B) - 最も厄介な不可視文字

ゼロ幅スペース (Zero Width Space, ZWSP) は、テキスト中に「ここで改行してもよい」という情報を埋め込むための文字です。タイ語やクメール語のように単語間にスペースを入れない言語で、ブラウザが適切な位置で改行できるようにするために使われます。

しかし、ZWSP は Web ページからテキストをコピー&ペーストする際に混入しやすく、以下のようなトラブルを引き起こします。

特に深刻なのはパスワードへの混入です。Web サイトからパスワードをコピーして貼り付けた際に ZWSP が紛れ込むと、見た目は正しいパスワードなのにログインできない事態が発生します。パスワードの文字数と安全性を考える上でも、不可視文字の存在は無視できません。

ゼロ幅接合子 (U+200D) - 絵文字を合成する魔法の文字

ゼロ幅接合子 (Zero Width Joiner, ZWJ) は、不可視文字の中で最もポジティブな役割を果たしている文字です。絵文字の文字数カウントで詳しく解説していますが、ZWJ は複数の絵文字を結合して新しい絵文字を作り出します。

表示される絵文字構成要素コードポイント数文字数カウント (JavaScript)
👨‍👩‍👧‍👦 (家族)👨 + ZWJ + 👩 + ZWJ + 👧 + ZWJ + 👦711 (サロゲートペア含む)
👩‍💻 (女性テクノロジスト)👩 + ZWJ + 💻35
🏳️‍🌈 (レインボーフラッグ)🏳️ + ZWJ + 🌈46
👨‍🍳 (男性料理人)👨 + ZWJ + 🍳35

家族の絵文字 👨‍👩‍👧‍👦 は、見た目は 1 つの絵文字ですが、内部的には 4 つの絵文字と 3 つの ZWJ で構成されています。JavaScript の .length プロパティでは 11 と返されます。SNS の文字数制限では、この絵文字 1 つで大量の文字数を消費する可能性があります。

方向制御文字 - 右から左に書く言語のための仕組み

アラビア語やヘブライ語は右から左 (RTL: Right-to-Left) に書く言語です。これらの言語と英語 (左から右、LTR) が混在するテキストでは、文字の表示方向を制御する不可視文字が必要になります。

U+200E (Left-to-Right Mark) と U+200F (Right-to-Left Mark) は、テキストの方向を明示的に指定するための文字です。これらが意図せず混入すると、テキストの表示順序が乱れたり、文字数カウントが狂ったりします。

2021 年には、方向制御文字を悪用したセキュリティ脆弱性「Trojan Source」が報告されました。ソースコード中に方向制御文字を埋め込むことで、人間の目には正常に見えるコードが、コンパイラには異なるロジックとして解釈される攻撃手法です。この脆弱性は、不可視文字がセキュリティ上のリスクにもなりうることを示しました。

BOM (U+FEFF) - ファイルの先頭に潜む不可視文字

バイトオーダーマーク (BOM: Byte Order Mark) は、テキストファイルのエンコーディングを識別するためにファイルの先頭に付加される文字です。UTF-8 の BOM は 3 バイト (EF BB BF) で、Windows のメモ帳で保存したファイルに付加されることがあります。

BOM は多くのプログラムで無視されますが、以下のケースで問題を引き起こします。

ゼロ幅文字を使ったステガノグラフィ (透かし技術)

不可視文字の「見えない」という特性を逆手に取った技術がステガノグラフィ (電子透かし) です。テキスト中にゼロ幅文字のパターンを埋め込むことで、見た目を変えずに隠し情報を仕込むことができます。

手法使用する文字用途検出難易度
ゼロ幅文字エンコーディングU+200B, U+200C, U+200D, U+FEFFテキストに隠しメッセージを埋め込む高い (目視不可能)
ユーザー追跡同上情報漏洩時の流出元特定高い
コピー検出同上コンテンツの無断コピーを検出中程度

例えば、4 種類のゼロ幅文字を 2 ビットの情報として扱い (U+200B = 00, U+200C = 01, U+200D = 10, U+FEFF = 11)、テキストの各単語の間にゼロ幅文字を挿入することで、バイナリデータを隠し持たせることができます。

この技術は、企業が機密文書の流出元を特定するために使われることがあります。各受信者に異なるゼロ幅文字パターンを埋め込んでおけば、文書が外部に流出した際に、どの受信者から漏れたかを特定できます。

不可視文字の検出と除去

不可視文字が混入したテキストを正しく処理するには、検出と除去の方法を知っておく必要があります。

方法対象コード例
JavaScript 正規表現主要なゼロ幅文字str.replace(/[\u200B-\u200F\u2028-\u202F\uFEFF]/g, '')
Python 正規表現同上re.sub(r'[\u200b-\u200f\u2028-\u202f\ufeff]', '', text)
テキストエディタ全不可視文字VS Code: 「制御文字の表示」を有効化
コマンドラインファイル内の不可視文字cat -v filename または xxd filename
PHP主要なゼロ幅文字preg_replace('/[\x{200B}-\x{200F}\x{FEFF}]/u', '', $str)

JavaScript の正規表現 /[\u200B-\u200F\u2028-\u202F\uFEFF]/g は、最も一般的なゼロ幅文字と方向制御文字をまとめて除去します。Web フォームの入力値をサーバーに送信する前にこのフィルタを適用すれば、不可視文字による文字数カウントの不一致を防げます。

ただし、すべての不可視文字を無条件に除去するのは危険です。ZWJ は絵文字の合成に必要であり、除去すると絵文字が分解されてしまいます。ZWNJ はペルシア語やヒンディー語の正しい表記に不可欠です。不可視文字の除去は、用途と文脈を理解した上で慎重に行う必要があります。

プログラミング言語ごとの不可視文字の扱い

プログラミング言語によって、ソースコード中の不可視文字の扱いは異なります。一部の言語は不可視文字を無視し、別の言語はエラーとして検出します。

言語ソースコード中の ZWSP文字列リテラル中の ZWSP検出ツール
JavaScript構文エラーにならない場合がある文字列の一部として保持ESLint の no-irregular-whitespace
PythonSyntaxError文字列の一部として保持pylint, flake8
Javaコンパイルエラー文字列の一部として保持Checkstyle
Goコンパイルエラー文字列の一部として保持go vet
Rustコンパイルエラー (警告付き)文字列の一部として保持clippy
C/C++コンパイラ依存文字列の一部として保持clang-tidy

JavaScript は特に注意が必要です。ZWSP (U+200B) は JavaScript の仕様上「ホワイトスペース」として扱われないため、変数名の一部として解釈される場合があります。つまり、var hellovar he\u200Bllo は異なる変数として扱われます。見た目は同じ「hello」なのに、別の変数。これがバグの原因になった事例は実際に報告されています。

変数名・関数名の長さの目安を考える際にも、不可視文字の混入リスクは頭の片隅に置いておくべきです。コードレビューでは目視で検出できないため、リンターやエディタの設定で自動検出する仕組みを整えることが重要です。

実際に起きたトラブル事例

不可視文字が原因で発生した実際のトラブルをいくつか紹介します。

文字数カウントツールと不可視文字

文字数カウントツールが不可視文字をどう扱うかは、ツールによって異なります。一部のツールは不可視文字を無視してカウントし、別のツールはそのままカウントします。Unicode の基本を理解していないと、ツール間で文字数が一致しない原因を特定できません。

テキストの文字数を正確にカウントしたい場合は、まず不可視文字の有無を確認し、必要に応じて除去してからカウントすることをお勧めします。「見えない文字」の存在を知っているだけで、文字数にまつわる多くのトラブルを未然に防ぐことができます。

不可視文字とセキュリティ - 見えない脅威

不可視文字はセキュリティ上の脅威にもなりえます。2021 年に発表された「Trojan Source」攻撃は、方向制御文字 (U+202A, U+202B, U+202C, U+202D, U+202E, U+2066, U+2067, U+2068, U+2069) を悪用して、ソースコードの見た目と実際の実行ロジックを乖離させる手法です。

攻撃手法使用する不可視文字影響対策
Trojan Source方向制御文字 (U+202A〜U+2069)コードレビューで見抜けない悪意のあるロジックコンパイラの警告を有効化
ホモグラフ攻撃見た目が同じ異なる文字 (U+0430 vs U+0061)フィッシング URL の偽装Punycode 表示の確認
ZWSP インジェクションU+200B入力バリデーションの回避サーバーサイドでの不可視文字除去
BOM インジェクションU+FEFFファイルパーサーの誤動作BOM の自動除去処理

Trojan Source 攻撃の具体例を示します。以下のコードは、人間の目には「アクセスが許可された場合のみ処理を実行する」ように見えますが、方向制御文字が埋め込まれているため、実際にはアクセスチェックが無効化されています。

この攻撃は、コードレビューという人間の目による検証プロセスを無力化する点で特に危険です。対策としては、コンパイラやリンターで方向制御文字の使用を警告する設定を有効にすること、CI/CD パイプラインに不可視文字の検出ステップを組み込むことが推奨されます。

Git コミットメッセージの書き方の記事でも触れていますが、コードの品質管理においてリンターの活用は不可欠です。不可視文字の検出も、リンターの重要な役割のひとつです。

Unicode やテキスト処理の技術書は Amazon でも見つかります

この記事を共有