Punycode and homograph attacks: what xn-- really means

Every internationalised domain you have ever visited is transmitted as xn--something. It is normal, it is standard, and it is also the mechanism behind domains that imitate a brand using characters that look identical.

Why DNS needs an ASCII form

DNS predates Unicode and only carries ASCII labels. To support a domain like münchen.de, the label is encoded into ASCII and prefixed with xn--, producing xn--mnchen-3ya.de. This is IDNA: the Unicode label is normalised, then encoded with Punycode (RFC 3492), and the result carries the prefix. The browser shows you the Unicode form; the wire carries the ASCII form.

Display formWire form
münchen.dexn--mnchen-3ya.de
日本語.jpxn--wgv71a119e.jp
ελλάδα.grxn--hxakic4aa.gr

Encoding is per label, not per whole domain: 日本.example.com encodes only the label containing non-ASCII characters, leaving ASCII labels untouched.

What the ASCII output looks like, and why

Punycode is not a transliteration. It does not try to spell the word in Latin letters — it encodes the code points after the “basic” ASCII prefix. münchen becomes the ASCII letters mnchen plus a delimiter and an encoded suffix 3ya carrying the position and value of the ü. The result is compact and reversible, but not human-readable by design.

This is why the xn-- form looks like random letters: it is a positional encoding, not a spelling. And it is why the only way to know what a domain says is to decode it.

The homograph problem

Unicode contains many characters that render identically or nearly identically to Latin letters. а (Cyrillic a, U+0430) and a (Latin a, U+0061) look the same in most fonts. So an attacker can register a domain using Cyrillic letters that reads as a familiar brand:

apple.com      Latin   a p p l e
аpple.com      Cyrillic а at the start

Both display similarly in a URL bar. Browsers mitigate this by showing the xn-- form when a domain mixes scripts in a suspicious way, but the mitigation is heuristic — the safest check is to decode the label and look at the actual code points.

Script mixing is the real signal

A legitimate internationalised domain is usually written in one script (all Cyrillic, all Japanese, all Latin with diacritics). A lookalike usually mixes scripts: mostly Latin with one Cyrillic character, or Latin with a Greek letter. IDNA has rules about this, but registration policies vary by TLD. If a domain mixes scripts and you do not have a reason to expect it, treat it as suspicious.

What to check before trusting a link

  1. Decode it. Convert the xn-- form to Unicode and read every character. If the decoded form is not the brand you expect, stop.
  2. Check the registrable domain, not the prefix. apple.com.login.example is not Apple; the domain is example. Homograph attacks often combine a lookalike character with a misleading subdomain.
  3. Look for script mixing in the decoded label.
  4. Check the TLD. A brand's primary domain does not move to an unusual ccTLD.
  5. When in doubt, type the domain yourself rather than following the link.

None of this means internationalised domains are unsafe. Most are legitimate — xn-- is present on every IDN, including well-known ones. The signal is a mismatch between what the domain displays and what it decodes to.

How the encoding actually works

Punycode splits each label into the ASCII characters it can keep and the rest. The kept characters are written first, then a hyphen, then an encoded suffix that carries the remaining code points by position and value. That is why the output looks like a word with a scrambled tail:

münchen  →  mnchen-3ya
           └ ASCII kept ┘ └ encoded ü ┘

The suffix is a base-36 variable-length integer encoding, not a transliteration, so there is nothing to read in it. It is compact and fully reversible — decode it and you get the exact original code points — but it carries no phonetic information. A label with no non-ASCII characters is left alone entirely; example stays example, and example.com never gets an xn-- prefix.

One detail that surprises people: the prefix is per label, not per domain. In 日本語.example.com only the first label is encoded; example.com is unchanged, because it is already ASCII.

Normalisation happens before encoding

IDNA normalises the Unicode label before applying Punycode. That matters because the same visible name can be typed in composed or decomposed form (Café with one code point versus e-plus-accent), and the two would otherwise encode to different xn-- strings — meaning two different domains that look identical. Normalisation collapses them to one wire form, which is both a correctness and a security property.

Where you actually meet xn-- in the wild

  • A TLS certificate detail — certificates carry the ASCII form, so the name in the certificate is always xn--.
  • An analytics report or a server log — the wire form is what gets recorded, even when the browser displayed the Unicode form.
  • An email address — internationalised email domains are punycoded before the SMTP conversation, so user@münchen.de is transmitted as user@xn--mnchen-3ya.de.
  • A DNS query — every resolver sees ASCII only. That is the whole reason the encoding exists.
  • A link-preview or a search result — some renderers show the ASCII form rather than decoding it, which is why a legitimate site can look suspicious in a screenshot.

Seeing xn-- is therefore not itself a warning sign — it is present on every internationalised domain that has ever existed. The warning sign is a mismatch: the decoded form is not what the display implied.

Decoding is the check, and it takes one step

Because the encoding is a pure function, the only reliable way to know what a domain says is to decode it. There is no need to guess from the shape of the string, and no value in memorising the suffix alphabet. Decode, read the result, compare it to what you expected. If it differs by even one character, the domain is not the one you thought.

A useful habit for any link you are about to trust with credentials: decode the domain, then read it character by character rather than as a word. The eye corrects lookalikes automatically, which is precisely what the attack relies on.

Other tricks in the same family

Homographs are the best-known abuse of internationalised domains, but they are not the only one:

  • Combining marks. A combining character can be stacked on a Latin letter to create a glyph that still reads as the letter but is a different code-point sequence. The domain encodes differently and the browser may display it identically.
  • Zero-width characters in a domain. A zero-width joiner is invisible; IDNA rules reject many of these in registrable labels, but the rules differ per TLD and per registry.
  • Punycode in a subdomain. An attacker controls a subdomain of a legitimate service and encodes the lookalike in the subdomain, where the registrable domain is genuinely the service. The eye reads the whole URL; the trust boundary is the registrable domain.
  • Confusable TLDs. A ccTLD whose ASCII letters resemble a familiar gTLD, registered for the purpose.

The common thread is that the display form and the trust form differ. Many of these are mitigated by registries and browsers, but the mitigations are partial and change over time — the reliable check remains decoding and reading the result.

Why browsers do not simply block xn--

They could, but that would break every legitimate internationalised domain, which is a large and growing share of the web: a user in Japan, Greece or Germany reasonably expects to type their own script. The compromise browsers settled on is to display the Unicode form but fall back to the ASCII form when a label mixes scripts in a way that looks like an attack. That is a heuristic, and heuristics in this space have both false positives (a legitimate mixed-script label shown as xn--) and false negatives (a lookalike that the rules do not catch).

This is why security guidance does not rely on the browser's display. Certificate details, logs, analytics and any programmatic check all see the ASCII form, and that is the authoritative one.

A five-second check

  1. Decode the domain.
  2. Read the decoded label letter by letter, not as a word.
  3. Confirm the registrable domain is the one you expect — the last two labels, not the first.
  4. If any character is not the one you expected, stop and reach the service by a route you trust.

Check it yourself

The punycode converter converts a domain both ways, label by label, and shows the character breakdown. To inspect a suspicious string character by character, the code point inspector names each one and flags lookalikes and invisible characters.

Questions this guide raises

Is punycode a form of encryption?

No. It is a reversible encoding of Unicode labels into ASCII, defined by RFC 3492, so DNS can carry internationalised names. Anyone can decode xn--mnchen-3ya.de back to münchen.de; the prefix is a marker, not a secret.

Why do some domains start with xn--?

The xn-- prefix is the ASCII-Compatible Encoding label that signals a punycode-encoded internationalised domain. It appears when a system displays or stores the ASCII form rather than the Unicode form. Both names refer to the same domain.

Do browsers always show the Unicode form?

Most current browsers display the Unicode form and apply their own anti-phishing rules, which may fall back to punycode for mixed-script or suspicious labels. The display is a usability choice, not a guarantee: the ASCII form is what DNS resolves, and a homograph can look identical to a different domain.