ドメイン・DNS
Punycode
post:
Punycodeは日本語などを含むUnicodeの文字列を、ASCIIの文字列で表す符号化方式です。 IDN(国際化ドメイン名)をDNSで扱えるASCIIの名前に変換するときに使われます。
Punycodeとは?
Punycodeは、2003年にRFC 3492として定められました。 Webなどで使うホスト名には、英字・数字・ハイフンを基本とする制約があります。 IDNでは文字の確認や入力の変換を行ったうえでPunycodeを使い、既存のDNSと互換性のある名前に変換します。 Punycodeで変換した文字列は、元のUnicodeの文字列に戻せます。
RFC 3492は、PunycodeをIDNのために設計した方式と位置付けています。 そのため、Punycodeを目にするのはほとんどの場合ドメイン名の中です。
変換の例
次の表は、いくつかの文字列をPunycodeで変換した結果です。
表が画面に収まらない場合は横にスクロールできます。
| 元の文字列 | Punycode | 特徴 |
|---|---|---|
うたカモ用語集 | p8j7bymyh4432a350ao1r | ひらがな・カタカナ・漢字を含む例 |
用語集 | jnyu17b00i | ASCIIの文字がないため、全体が符号化された文字列になる |
アプリ | cckvf7b | 元の文字列の意味は、変換結果から読み取れない |
これらの例をIDNのラベルとして使うときは、変換結果の先頭にxn--を付けます。
例えばうたカモ用語集.exampleはxn--p8j7bymyh4432a350ao1r.example、用語集.exampleはxn--jnyu17b00i.exampleになります。
Punycodeの変換の仕組み
Punycodeは、ASCIIの文字とそれ以外の文字を分けて記録します。うたカモ用語集の変換は、おおまかに次の順で行われます。
- 元の文字列からASCIIの文字を取り出す。この例にはASCIIの文字がない
- ASCIIの文字が1つ以上あれば区切りのハイフン
-を付けるが、この例では付かない - 残りの文字の種類と位置を数値で表し、英小文字と数字を使って書く(
p8j7bymyh4432a350ao1r)
この変換結果には、元の文字を復元するための情報が含まれています。
元に戻すときは符号化された数値を読み取り、文字を順に挿入してうたカモ用語集を復元します。
PunycodeではASCIIの文字をそのままコピーするため、大文字や記号も出力に残ります。例えばABCうはABC-263bになります。
一方、263bのように数値を表す部分は英字の大文字・小文字を区別せずに読み取れます。
符号化の手順では、挿入する位置と文字の情報をまとめて1つの数値として扱い、短い文字列で表せるように調整しています。 そのため、同じ文字が続く場合や近い範囲の文字が並ぶ場合は、変換後の文字列が比較的短くなります。
Punycodeとxn--の関係
xn--は、Punycodeの一部ではありません。IDNの仕組みであるIDNAが定めた目印で、後ろにPunycodeで変換したラベルが続くことを示します。
- 元のラベル
うたカモ用語集- Punycodeの変換結果
p8j7bymyh4432a350ao1rxn--を付けたA-labelxn--p8j7bymyh4432a350ao1r
xn--を付けてA-labelにする例表が画面に収まらない場合は横にスクロールできます。
| 呼び方 | 例 | 説明 |
|---|---|---|
| U-label | うたカモ用語集 | 利用者が読むUnicodeの形のラベル |
| Punycodeの結果 | p8j7bymyh4432a350ao1r | 符号化しただけの文字列。単独ではIDNのラベルと区別できない |
| A-label | xn--p8j7bymyh4432a350ao1r | xn--を付け、DNSで使うASCIIの形のラベル |
p8j7bymyh4432a350ao1rだけでは、Punycodeで変換した結果なのか、もともとそういう名前なのかを判断できません。
先頭にxn--があれば、ブラウザなどのソフトウェアはそのラベルをUnicodeの形に戻して表示できます。
ただし、xn--で始まる文字列がすべて正しいA-labelとは限りません。
Punycodeとして復元できないものや、復元した結果にIDNで使えない文字が含まれるものは正しいA-labelとして扱われません。
例えば、ブラウザと同じ規則でURLを解析するJavaScriptのURLは、https://xn--a.example/を不正なURLとしてエラーにします。
ドメイン名の長さとPunycode
DNSでは、ドメイン名の1つのラベルを63オクテット(ASCIIでは63文字)以内にする必要があります。
IDNの場合、この制限はPunycodeで変換し、xn--を付けたA-labelの長さに適用されます。
例えば6文字のドメイン名例は、A-labelではxn--eckwd4c7cu47r2wfの20文字になります。
変換後の長さは文字の種類や並びによって変わるため、日本語のラベルに使える文字数は一律に決まりません。
xn--で始まるドメインを見かけたとき
アドレスバーやログにxn--で始まるドメインが表示されていても、それだけで危険なサイトという意味ではありません。
日本語のドメインなど、IDNを正しくASCIIで表した結果であることが多いためです。
一方で、見た目の似た文字を使って正規のサイトに見せかけるIDNホモグラフ攻撃では、Punycodeの形がなりすましを見抜く手がかりになります。
例えば、先頭のaだけをキリル文字のа(U+0430)に置き換えたаpple.exampleは、画面ではapple.exampleとほとんど区別できません。
しかし、A-labelにするとxn--pple-43d.exampleとなり、ASCIIだけのapple.exampleとは別の名前であることが分かります。
ブラウザによっては、このような紛らわしい名前をあえてA-labelのまま表示します。 Unicodeの表示だけでは紛らわしい文字を見分けられないこともあります。 ログインや決済を行うサイトは、保存したブックマークや公式アプリから開く方法も有効です。 ドメイン名を調べるときは、ASCII形式も含めて正規の名前と照合します。
プログラムでPunycodeを扱うときの注意
ドメイン名を変換するときは、Punycodeの処理だけを直接呼び出すのではなく、IDNAの変換を行う関数を使うのが基本です。 ブラウザなどではPunycodeを適用する前に、UTS #46などの規則に従って大文字や全角文字をそろえ、文字列を確認します。 IDNA2008自体は、このような入力の変換を必須としていません。 Punycodeの処理だけを使うと、こうした入力の変換や確認が抜け、ブラウザとは異なる名前を作ってしまうことがあります。
例えばNode.jsでは、組み込みのpunycodeモジュールは非推奨とされています。ドメイン名の変換にはurl.domainToASCII()やURLを使えます。
Pythonの標準のidnaコーデックは古いIDNA2003に従うため、現在のブラウザと変換結果が異なる場合があります。
IDNA2003とIDNA2008の違いはIDNの解説で扱っています。
参考資料
- RFC 3492: Punycode: A Bootstring encoding of Unicode for Internationalized Domain Names in Applications (IDNA) — Punycodeの変換手順と大文字・小文字の扱い
- RFC 5890: Internationalized Domain Names for Applications (IDNA): Definitions and Document Framework —
xn--、A-label、U-labelの定義 - RFC 1035: Domain Names - Implementation and Specification — ラベルの長さの上限
- Unicode Technical Standard #46: Unicode IDNA Compatibility Processing — 入力の変換と文字の確認
- WHATWG: URL Standard — IDNA — ブラウザでのホスト名の変換
- Node.js: Punycode — 組み込みモジュールの非推奨の扱い
- Python: encodings.idna — 標準のIDNAコーデック