1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【闇が深い】macOSとWindowsで「見た目同じ文字」が別物になる問題と、その被害者たち

Posted at

TL;DR

macOSとWindowsはUnicodeの正規化形式が違う。見た目が完全に同じでもバイト列が異なり、パスワードが通らない、ファイルが見つからない、検索がヒットしない等の怪奇現象が発生する。


発端:「パスワード合ってるのに入れない」

先日、こんな現象に遭遇した。

Windowsでパスワードをコピペ → 通る
MacからリモートデスクトップでWindowsを操作してコピペ → 通らない

diffを取っても差分ゼロ。目視でも完全に一致。なのに認証失敗。

お前は何を言っているんだ状態である。


犯人:Unicode正規化形式(NFC vs NFD)

Unicodeには「同じ文字を表現する複数の方法」が存在する。

例:「が」という文字

正規化形式 表現方法 コードポイント
NFC (Composed) 「が」として1文字で保持 U+304C
NFD (Decomposed) 「か」+「濁点」に分解して保持 U+304B + U+3099

そしてOSごとのデフォルトが異なる:

OS 採用形式
Windows NFC(合成)
macOS NFD(分解)
Linux NFCが多い(ディストリ依存)

見た目は同じ。しかしバイト列は違う。

これが全ての元凶である。


被害者リスト:お前もか案件

1. パスワード認証(冒頭の例)

# macOSでコピーした「パスワード」
U+30D1 U+30B9 U+30EF U+30FC U+30C8 U+3099  ← 濁点分離

# Windowsで期待される「パスワード」  
U+30D1 U+30B9 U+30EF U+30FC U+30C9        ← 合成済み

認証システム「知らない文字列ですね」

お前さぁ...


2. ファイル名が見つからない

# macOSで作成したファイル
データ.csv  # NFD形式

# Windowsで検索
> dir データ.csv
ファイルが見つかりません

# でもエクスプローラーには見えてる

Git、Dropbox、Google Drive、OneDrive、全員被害者。


3. Git差分が永遠に消えない

$ git status
modified:   ドキュメント.md

$ git diff
# 何も表示されない

$ git checkout -- ドキュメント.md

$ git status  
modified:   ドキュメント.md  # まだいる

お前は成仏しろ。

対処法:

git config --global core.precomposeunicode true

4. データベース検索がヒットしない

-- macOSのアプリから登録
INSERT INTO users (name) VALUES ('田中');

-- Windowsのバッチで検索
SELECT * FROM users WHERE name = '田中';
-- 0件

「田」も「中」もNFD/NFCで表現が変わりうる。

照合順序(Collation)の設定を見直せ。


5. CSVインポートで文字化け

# macOSのExcelで出力したCSV → WindowsでインポートしたらNGなやつ
鈴木,東京,営業部  # 「鈴」がNFD
↓
鈴木,東京,営業部  # 別の「鈴」として認識、重複レコード爆誕

マスタデータが汚染される最悪のパターン。


6. APIの署名検証が通らない

# macOSで生成した署名
payload = "ユーザー名=佐藤"
signature = hmac_sha256(secret, payload)

# Windowsのサーバーで検証
expected = hmac_sha256(secret, payload)  # 同じ文字列のはず
assert signature == expected  # False 

マジか。


7. ZIP解凍で文字化け(またはファイル名衝突)

# macOSで作成したZIP
├── 報告書.docx      # NFD
└── 報告書.docx      # NFC(別ファイル扱い)

# Windowsで解凍
> 同名ファイルが存在します。上書きしますか?

いや、別ファイルなんですけど...


8. 正規表現がマッチしない

// macOSのブラウザで入力された値
const input = "ガンダム";  // NFD

// バリデーション
const pattern = /^[ァ-ヴー]+$/;
console.log(pattern.test(input));  // false

// 「ガ」が「カ」+「濁点」に分解されてるので範囲外

日本語正規表現、難しすぎる問題。


確認方法:バイト列を見ろ

PowerShell(Windows)

# クリップボードの中身を16進ダンプ
[System.Text.Encoding]::UTF8.GetBytes((Get-Clipboard)) | Format-Hex

# 文字列のコードポイントを確認
"が".ToCharArray() | ForEach-Object { "U+{0:X4}" -f [int]$_ }

Python(どこでも)

import unicodedata

text = ""
print(f"Original: {[hex(ord(c)) for c in text]}")
print(f"NFC: {[hex(ord(c)) for c in unicodedata.normalize('NFC', text)]}")
print(f"NFD: {[hex(ord(c)) for c in unicodedata.normalize('NFD', text)]}")

bash(macOS/Linux)

echo -n "が" | xxd

対処法:正規化を統一しろ

原則:入力時にNFCに正規化

import unicodedata

def sanitize_input(text: str) -> str:
    """入力文字列をNFCに正規化"""
    return unicodedata.normalize('NFC', text)

# 使用例
password = sanitize_input(request.form['password'])
username = sanitize_input(request.form['username'])

データベース:照合順序を確認

-- PostgreSQL
SHOW lc_collate;

-- MySQL
SHOW VARIABLES LIKE 'collation%';

Git:設定を追加

# macOSユーザーは必須
git config --global core.precomposeunicode true

ファイル名:ASCII縛りが最強

# Bad
レポート_2024年12月.xlsx

# Good  
report_202412.xlsx

日本語ファイル名は甘え。(暴論)


豆知識:なぜmacOSはNFDなのか

歴史的経緯がある。

macOS(旧Mac OS X)のファイルシステムHFS+は、ファイル名の正規化にNFDを採用した。理由は「検索時に濁点の有無を無視してマッチさせたかった」から。

つまり「はな」で検索したら「ばな」も「ぱな」もヒットさせたい、というAppleの親切心が生んだ副作用。

ありがた迷惑とはこのことである。

ちなみにAPFS(現行のファイルシステム)では正規化しなくなったが、互換性のためNFD前提のコードが残っている。


まとめ

問題 対策
パスワードが通らない ASCII文字のみ使用 or 入力時にNFC正規化
ファイルが見つからない ファイル名はASCII推奨
Git差分が消えない core.precomposeunicode true
DB検索がヒットしない 照合順序確認 + 入力時正規化
API署名が通らない ペイロードをNFC正規化してから署名

結論

見た目が同じでも、バイト列は嘘をつかない。

macOSとWindowsを行き来する環境では、Unicode正規化は避けて通れない。特に日本語を扱うシステムでは、入力の入り口でNFCに正規化することを強く推奨する。

困ったら unicodedata.normalize('NFC', text) 。これだけ覚えて帰ってくれ。


参考文献


この記事が役に立ったら、同僚にも教えてあげてください。あなたの隣で「なぜかファイルが見つからない」と唸っている人を救えるかもしれません。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?