퓨니코드란?
한글.com이라는 도메인을 브라우저에 입력하면 어떻게 될까요? 놀랍게도 실제로 접속이 됩니다. 하지만 DNS 시스템은 오직 ASCII 문자만 이해할 수 있습니다. 그렇다면 한글 도메인은 어떻게 동작하는 걸까요? 그 비밀은 바로 퓨니코드(Punycode) 에 있습니다.
DNS와 ASCII의 벽
인터넷 초창기, DNS(Domain Name System)는 미국에서 설계되었습니다. 당시에는 영문자와 숫자, 하이픈만으로 충분했습니다. DNS 레이블은 다음 규칙을 따릅니다:
- ❧오직
a-z,0-9,-문자만 허용 - ❧대소문자 구분 없음
- ❧각 레이블은 63바이트 이하
이 제한은 RFC 1035에 명시되어 있으며, 전 세계 DNS 서버들이 이 규칙을 따릅니다. 한글, 일본어, 아랍어 등 비ASCII 문자는 원천적으로 차단됩니다.
IDN과 퓨니코드의 등장
2003년, IETF는 IDN(Internationalized Domain Names) 표준을 발표합니다(RFC 3490). 핵심 아이디어는 단순합니다:
❝비ASCII 문자를 ASCII로 변환하여 기존 DNS 인프라를 그대로 활용한다.
❞
이 변환 알고리즘이 바로 퓨니코드입니다. RFC 3492에 정의된 퓨니코드는 Bootstring 알고리즘을 기반으로 합니다.
퓨니코드의 구조
퓨니코드로 인코딩된 도메인은 특별한 접두사로 시작합니다:
xn--이 접두사를 ACE(ASCII Compatible Encoding) prefix 라고 부릅니다(RFC 5891). 예를 들어:
| 원본 도메인 | 퓨니코드 |
|---|---|
| 한글 | xn--bj0bj06e |
| 日本 | xn--wgv71a |
| münchen | xn--mnchen-3ya |
| 中文 | xn--fiq228c |
Bootstring 알고리즘의 원리
Bootstring은 세 단계로 동작합니다:
✦1단계: 기본 코드 포인트 분리
먼저 ASCII 문자(기본 코드 포인트)를 그대로 복사합니다. münchen의 경우 mnchen이 먼저 추출됩니다.
✦2단계: 델타 인코딩
비ASCII 문자의 위치와 코드 포인트를 델타(delta) 값으로 인코딩합니다. 델타는 "이전 위치에서 얼마나 떨어져 있는가"를 나타냅니다.
예를 들어, münchen에서 ü(U+00FC)는 2번째 위치에 있습니다. 알고리즘은 코드 포인트를 오름차순으로 처리하면서, 각 비ASCII 문자의 위치와 코드 포인트 값을 하나의 델타 숫자로 압축합니다. ü의 경우, 코드 포인트 252(0xFC)와 위치 정보가 결합되어 델타 값이 계산됩니다.
핵심 아이디어는 다음과 같습니다:
- ❧문자열을 선형으로 스캔하면서 비ASCII 문자의 위치를 기록
- ❧각 문자의 유니코드 코드 포인트를 순차적으로 처리
- ❧작은 델타 값이 자주 나타나므로 가변 길이 인코딩이 효율적
✦3단계: 일반화된 가변 길이 정수 인코딩
델타 값을 base-36 문자(a-z, 0-9)로 변환합니다. 이때 적응형 바이어스(adaptive bias) 를 사용해 자주 나타나는 값을 더 짧게 인코딩합니다.
// 의사 코드: 바이어스 적응 함수 (RFC 3492 Section 6.1)
function adapt(delta, numpoints, firsttime) {
delta = firsttime ? Math.floor(delta / DAMP) : delta >> 1;
delta += Math.floor(delta / numpoints);
let k = 0;
while (delta > ((BASE - TMIN) * TMAX) >> 1) {
delta = Math.floor(delta / (BASE - TMIN));
k += BASE;
}
return k + Math.floor((BASE - TMIN + 1) * delta / (delta + SKEW));
}여기서 상수들은 RFC 3492에 정의된 값입니다: BASE는 36(알파벳+숫자), TMIN은 1, TMAX는 26, DAMP는 700, SKEW는 38입니다. 이 함수는 자주 나타나는 델타 값에 더 짧은 인코딩을 할당하여 압축 효율을 높입니다.
이 적응형 바이어스 덕분에 퓨니코드는 다양한 언어에서 효율적인 압축률을 보입니다.
이론을 이해했다면, 실제 예제를 통해 확인해 봅시다.
실제 인코딩 과정: "한글" 예제
한글을 퓨니코드로 변환하는 과정을 따라가 봅시다.
입력: "한글"
유니코드: U+D55C (한), U+AE00 (글)- ❧ASCII 문자가 없으므로 기본 부분은 비어 있음
- ❧가장 작은 코드 포인트 U+AE00(글)부터 처리
- ❧델타 계산: 첫 번째 문자의 위치와 코드 포인트
- ❧base-36 인코딩 수행
결과: xn--bj0bj06e실습: JavaScript로 확인하기
브라우저와 Node.js 모두에서 URL API를 통해 퓨니코드 변환을 확인할 수 있습니다:
// 브라우저 & Node.js 공통: URL API
const url = new URL('https://한글.com');
console.log(url.hostname); // xn--bj0bj06e.comNode.js에서는 punycode 모듈을 사용해 직접 인코딩/디코딩할 수도 있습니다:
// Node.js 전용: punycode 모듈
const punycode = require('punycode/');
console.log(punycode.encode('한글')); // bj0bj06e
console.log(punycode.decode('bj0bj06e')); // 한글브라우저 주소창에서는 사용자 편의를 위해 원래 문자로 표시하지만, 실제 DNS 쿼리는 퓨니코드로 이루어집니다.
아래 변환기로 직접 퓨니코드 인코딩/디코딩을 체험해 보세요:
호모그래프 공격: 퓨니코드의 보안 문제
퓨니코드가 다양한 문자를 허용하면서 새로운 보안 문제가 등장했습니다. 호모그래프 공격(Homograph Attack) 은 시각적으로 유사한 문자를 이용해 사용자를 속이는 공격입니다:
apple.com (정상)
аpple.com (а는 키릴 문자)
→ xn--pple-43d.com육안으로는 구분이 불가능하지만 전혀 다른 도메인입니다. 이 때문에 현대 브라우저들은 혼합 스크립트 도메인을 퓨니코드 형태로 표시하는 등의 방어책을 적용하고 있습니다. 자세한 브라우저별 정책은 Chromium IDN Display Policy와 Unicode Security Guide(TR39)를 참고하세요.
표준의 변화: IDNA 2003 vs IDNA 2008
퓨니코드를 실제로 사용하는 표준은 IDNA(Internationalized Domain Names in Applications) 입니다:
예를 들어 독일어 ß는:
- ❧IDNA 2003:
ss로 자동 변환 - ❧IDNA 2008: 그대로 유지 (
xn--로 인코딩)
현재 대부분의 시스템은 IDNA 2008을 따르지만, 하위 호환성을 위해 두 표준이 공존하고 있습니다.
마치며
퓨니코드는 인터넷의 국제화를 가능하게 한 핵심 기술입니다. 단순해 보이는 xn-- 접두사 뒤에는 정교한 압축 알고리즘과 수십 년에 걸친 표준화 노력이 숨어 있습니다. 브라우저 주소창에 한글 도메인을 입력할 때, 그 뒤에서 벌어지는 변환 과정을 한 번쯤 떠올려 보는 것도 좋겠습니다.
참고 자료
- ❧RFC 1035 - Domain Names: Implementation and Specification
- ❧RFC 3490 - IDNA (2003)
- ❧RFC 3492 - Punycode: A Bootstring encoding of Unicode
- ❧RFC 5891 - IDNA (2008): Protocol
- ❧RFC 5894 - IDNA (2008): Background, Explanation, and Rationale
- ❧Unicode Technical Report #39 - Security Mechanisms
- ❧Chromium IDN Display Policy