什么是Punycode?
Punycode是在RFC 3492中定义的编码语法,它将Unicode字符串转换为DNS支持的有限ASCII字符集。它是使国际化域名(IDN)可能的技术基础,允许任何语言的域名与现有DNS基础设施配合工作。
Punycode解决的问题
域名系统是在1980年代设计的,只考虑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:
用户输入:中文.com
浏览器发送:xn--fiq228c.com(到DNS)
浏览器显示:中文.com(在地址栏中)
开发者实现
JavaScript(Node.js):const punycode = require('punycode/');
// 编码为Punycode
const encoded = punycode.toASCII('münchen.de');
// 结果:xn--mnchen-3ya.de
// 从Punycode解码
const decoded = punycode.toUnicode('xn--mnchen-3ya.de');
// 结果:münchen.de
Python:
domain = 'münchen.de'
encoded = domain.encode('idna').decode('ascii')
# 结果:xn--mnchen-3ya.de
URL处理
在处理包含IDN的URL时:
// URL API自动处理Punycode
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(西里尔'а'和'р')
apple.com(拉丁字母)
两者在某些字体中显示相同,但是是不同的域名。
浏览器保护
为了对抗欺骗,浏览器实现了保护:
1. 混合脚本检测:为可疑的IDN显示Punycode而不是Unicode
2. 易混淆检测:标记使用与ASCII类似的字符的域名
3. 白名单:仅为知名TLD允许Unicode显示
在API中使用Punycode
在构建域名工具时:
始终存储Punycode:在内部使用ASCII形式保持一致性和数据库索引。 接受两种形式:让用户输入Unicode或Punycode,根据需要转换。 显示Unicode:在用户界面中显示人类可读的形式。function normalizeDomain(input) {
const punycode = require('punycode/');
// 转换为小写Punycode以供内部使用
return punycode.toASCII(input.toLowerCase());
}
Punycode对大多数用户是透明的,但对构建国际化网络应用的开发者至关重要。