Punycode – sposób kodowania napisów Unicode za pomocą ograniczonego zestawu znaków ASCII, zdefiniowany w dokumencie RFC 3492 na potrzeby mechanizmu nazw domen ze znakami narodowymi (IDNA). Przekształcenie jest jednoznaczne i odwracalne: znaki ASCII z napisu wejściowego są przepisywane dosłownie, a pozostałe znaki są reprezentowane za pomocą liter, cyfr i łączników, czyli znaków dozwolonych w etykietach nazw hostów. Punycode jest szczególnym przypadkiem ogólniejszego algorytmu Bootstring, opisanego w tym samym dokumencie, z wartościami parametrów dobranymi dla IDNA. W nazwach domen etykietę zakodowaną w ten sposób poprzedza się prefiksem xn--, na przykład nazwa kraków.pl przyjmuje postać xn--krakw-3ta.pl.
Dokument RFC 3492, którego autorem jest Adam Costello z Uniwersytetu Kalifornijskiego w Berkeley, powstał w grupie roboczej IDN organizacji IETF i został opublikowany w marcu 2003 roku ze statusem Proposed Standard. W tym samym miesiącu ukazała się specyfikacja IDNA (RFC 3490), która w operacji ToASCII wskazała Punycode jako kodowanie etykiet zawierających znaki spoza ASCII. W 2010 roku specyfikację IDNA zastąpiła nowa wersja, IDNA2008 (RFC 5890 i RFC 5891), która nie zmieniła algorytmu Punycode w żaden sposób, a jedynie formalnie zaktualizowała odwołania do niego.
Rejestr domeny .pl, prowadzony przez NASK, umożliwia rejestrację nazw ze znakami narodowymi od września 2003 roku i według NASK był pierwszym rejestrem w Europie, który to wprowadził. W 2024 roku NASK podawał, że znaków diakrytycznych użyto w ponad 25 tysiącach nazw w domenie .pl. W domenie .pl dozwolone są między innymi polskie litery ą, ć, ę, ł, ń, ó, ś, ź i ż, a także znaki innych alfabetów, przy czym w jednej nazwie nie można mieszać znaków z różnych zestawów. Przedmiotem umowy z rejestrem jest nazwa w postaci ASCII z prefiksem xn-- i w takiej postaci jest ona przechowywana w DNS.
Punycode stosuje się także w nazwach domen najwyższego poziomu. Na przykład rosyjska domena .рф jest zarejestrowana w strefie głównej od 12 maja 2010 roku jako xn--p1ai.
Bootstring przedstawia dowolny ciąg kodów znaków (ang. extended string) jako ciąg podstawowych kodów znaków (ang. basic string). Specyfikacja wymienia jego cechy: zupełność (każdy ciąg rozszerzony ma reprezentację), jednoznaczność (istnieje co najwyżej jedna reprezentacja), odwracalność, wydajność (stosunek długości ciągu podstawowego do rozszerzonego jest niewielki), prostotę implementacji oraz czytelność (podstawowe kody znaków występują w wyniku jako one same).
Kodowanie składa się z czterech elementów:
Punycode używa podstawy 36 oraz parametrów tmin = 1, tmax = 26, skew = 38, damp = 700, początkowej wartości bias równej 72 i początkowej wartości n równej 128 (0x80). Podstawowymi kodami znaków są kody ASCII (0..7F), ogranicznikiem jest łącznik (U+002D), a wartości cyfr mają litery od a do z (0–25, niezależnie od wielkości liter) oraz cyfry od 0 do 9 (26–35). Dekoder musi rozpoznawać zarówno wielkie, jak i małe litery, a koder powinien używać wyłącznie jednej wielkości liter.
Zakodowany napis może kończyć się łącznikiem tylko wtedy, gdy wejście składało się wyłącznie ze znaków podstawowych, a takich napisów IDNA nie koduje. Może natomiast zaczynać się łącznikiem, ale IDNA dodaje przed nim prefiks, dzięki czemu etykiety spełniają regułę z RFC 952, zgodnie z którą nazwa hosta nie zaczyna się ani nie kończy łącznikiem. Specyfikacja ostrzega też przed wizualną wieloznacznością par znaków, takich jak O i 0 albo I, l i 1, w napisach przepisywanych ręcznie.
Dla nazwy kraków znaki podstawowe tworzą ciąg krakw, po nim następuje łącznik, a ciąg 3ta koduje wstawienie znaku ó (U+00F3) na pozycji 4, licząc od zera. Napis zawierający wyłącznie znaki ASCII otrzymuje jedynie końcowy łącznik: w przykładzie ze specyfikacji -> $1.00 <- daje -> $1.00 <--. Czeskie zdanie Pročprostěnemluvíčesky koduje się jako Proprostnemluvesky-uyb24dma41a.
| Nazwa ze znakami narodowymi | Punycode | Etykieta w DNS (A-label) |
|---|---|---|
| kraków | krakw-3ta | xn--krakw-3ta |
| łódź | d-uga0v4h | xn--d-uga0v4h |
| żółw | w-uga1v8h | xn--w-uga1v8h |
| gdańsk | gdask-y7a | xn--gdask-y7a |
| zażółć | za-6ja4f8n1l | xn--za-6ja4f8n1l |
| münchen | mnchen-3ya | xn--mnchen-3ya |
| рф | p1ai | xn--p1ai |
W IDNA etykieta zapisana znakami Unicode w postaci znormalizowanej NFC nazywana jest U-label, a jej postać ASCII – A-label. Każdy A-label zaczyna się od prefiksu xn--, po którym następuje wynik algorytmu Punycode, a ponieważ etykieta DNS nie może przekraczać 63 oktetów, sam zakodowany ciąg ma najwyżej 59 znaków. Konwersja między obiema postaciami polega na zastosowaniu Punycode oraz dodaniu lub usunięciu prefiksu. Prefiks może być zapisany dowolną wielkością liter. Kodowaniu podlega każda etykieta nazwy osobno, a etykiet złożonych wyłącznie ze znaków ASCII IDNA nie koduje. Punycode jest przeznaczony wyłącznie do konwersji etykiet nazw domen w IDNA, a specyfikacja wprost zastrzega, że nie służy do przetwarzania dowolnego tekstu.
W Unicode istnieją znaki wyglądające identycznie jak litery łacińskie, na przykład cyrylickie а, е, о czy р, dlatego nazwa domeny ze znakami narodowymi może do złudzenia przypominać nazwę znanej witryny i służyć do phishingu. Atak tego rodzaju, nazwany atakiem homograficznym (ang. homograph attack), opisali w 2002 roku Evgeniy Gabrilovich i Alex Gontmakher, którzy w celu sprawdzenia jego wykonalności zarejestrowali wariant nazwy microsoft.com zapisany z użyciem liter rosyjskich. W 2017 roku Xudong Zheng pokazał, że domena xn--80ak6aa92e.com, złożona wyłącznie z liter cyrylicy, była wyświetlana w przeglądarkach Chrome, Firefox i Opera jako apple.com, co omijało stosowane w nich zabezpieczenia przed homografami. Google wprowadziło poprawkę w Chrome 58. W tym samym roku kampania złośliwych reklam Seamless, rozprowadzająca zestaw exploitów RIG, używała nazwy domeny złożonej ze znaków cyrylicy, zapisanej w Punycode.
W ruchu DNS nazwy ze znakami narodowymi występują wyłącznie w postaci ASCII z prefiksem xn--, dlatego zapytania o takie nazwy można wyodrębnić po tym prefiksie i porównać z podobnymi, legalnymi nazwami bez Punycode, co pozwala wykrywać próby podszywania się pod znane domeny. Na przykład nazwa аpple.com, w której pierwszą literę zastąpiono cyrylicką а (U+0430), wygląda na ekranie jak apple.com, ale w DNS ma postać xn--pple-43d.com.
Przeglądarki internetowe same decydują, czy pokazać użytkownikowi nazwę w postaci Unicode, czy w postaci Punycode. Google Chrome podejmuje tę decyzję osobno dla każdej etykiety nazwy hosta i wyświetla postać Punycode między innymi wtedy, gdy etykieta zawiera znaki niedozwolone, miesza pisma w niedopuszczalny sposób, miesza systemy liczbowe, zawiera znaki niewidoczne albo jest myląco podobna do innej nazwy według kryteriów Unicode Technical Standard #39. Standard ten określa mechanizmy wykrywania napisów wizualnie mylących (ang. confusable).
Dokument RFC 3492 zawiera w załączniku przykładową implementację kodera i dekodera. Biblioteka standardowa języka Python udostępnia kodek punycode, implementujący RFC 3492, oraz kodek idna, implementujący RFC 3490 i korzystający z poprzedniego, przy czym dokumentacja ostrzega, że algorytmy kodowania i dekodowania słabo się skalują, więc długość niezaufanych danych wejściowych należy ograniczać. Rejestr NASK udostępnia na swojej stronie narzędzie do tłumaczenia nazw domen między postacią ze znakami narodowymi a postacią ASCII.