什么是 Punycode?
Punycode 是 RFC 3492 定义的一种编码语法,用于将 Unicode 字符串转换为 DNS 支持的有限 ASCII 字符集。它是国际化域名(IDN)的技术基础,让任何语言的域名都能使用现有 DNS 基础设施。
Punycode 解决的问题
域名系统在 20 世纪 80 年代设计时只考虑了 ASCII 字符。DNS 标签只能包含:
- 小写字母(a-z)
- 数字(0-9)
- 连字符(-)
这一限制排除了数十亿主要不使用拉丁字母的互联网用户。Punycode 通过把任意 Unicode 字符串编码为有效 ASCII,弥合了这一差距。
Punycode 编码如何工作
Punycode 使用一种巧妙的算法,在保留 ASCII 字符的同时,将非 ASCII 字符编码为紧凑的 ASCII 表示形式。
编码过程
1. 分离字符:将 ASCII 字符与非 ASCII 字符分开
2. 复制 ASCII:保留所有 ASCII 字符原本的位置
3. 编码非 ASCII:使用通用的可变长度整数编码
4. 添加前缀:添加 “xn--” 以表示 Punycode 编码
示例
| 原始(Unicode) | 编码(Punycode) |
|---|---|
| münchen | xn--mnchen-3ya |
| 北京 | xn--fiqs8s |
| münchen.de | xn--mnchen-3ya.de |
| 中文.com | xn--fiq228c.com |
| café.com | xn--caf-dma.com |
“xn--”前缀
“xn--”前缀称为 ACE(ASCII 兼容编码)前缀。它向 DNS 解析器和应用程序表明,该标签包含经过 Punycode 编码的内容。这个前缀:
- 始终使用小写
- 不会出现在普通 ASCII 域名中
- 会触发兼容软件进行 Unicode 解码
实际使用 Punycode
浏览器处理
现代浏览器会自动处理 Punycode:
User types: 中文.com
Browser sends: xn--fiq228c.com (to DNS)
Browser displays: 中文.com (in address bar)
开发者实现
JavaScript(Node.js):const punycode = require('punycode/');
// Encode to Punycode
const encoded = punycode.toASCII('münchen.de');
// Result: xn--mnchen-3ya.de
// Decode from Punycode
const decoded = punycode.toUnicode('xn--mnchen-3ya.de');
// Result: münchen.de
Python:
domain = 'münchen.de'
encoded = domain.encode('idna').decode('ascii')
# Result: xn--mnchen-3ya.de
URL 处理
处理包含 IDN 的 URL 时:
// URL API handles Punycode automatically
const url = new URL('https://中文.com/path');
console.log(url.hostname); // xn--fiq228c.com
console.log(url.href); // https://xn--fiq228c.com/path
安全影响
Punycode 可以表示任意 Unicode 字符,因此会带来安全风险:
视觉欺骗
攻击者可以注册外观与合法网站相同的域名:
аррlе.com (Cyrillic 'а' and 'р')
apple.com (Latin letters)
在某些字体中,两者显示相同,但实际是不同的域名。
浏览器保护
为防止欺骗,浏览器会实施以下保护:
1. 混合文字检测:对可疑的混合文字域名显示 Punycode
2. 易混淆字符检测:标记使用外观类似 ASCII 字符的域名
3. 白名单:仅对知名 TLD 允许显示 Unicode
在 API 中使用 Punycode
构建域名工具时:
始终存储 Punycode:在内部使用 ASCII 形式,以保持一致性并便于数据库索引。 接受两种形式:允许用户输入 Unicode 或 Punycode,并按需转换。 显示 Unicode:在用户界面中显示便于阅读的形式。function normalizeDomain(input) {
const punycode = require('punycode/');
// Convert to lowercase Punycode for internal use
return punycode.toASCII(input.toLowerCase());
}
Punycode 对大多数用户是透明的,但对于构建国际化 Web 应用的开发者却是必备知识。