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 form | Wire form |
|---|---|
| münchen.de | xn--mnchen-3ya.de |
| 日本語.jp | xn--wgv71a119e.jp |
| ελλάδα.gr | xn--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
- Decode it. Convert the
xn--form to Unicode and read every character. If the decoded form is not the brand you expect, stop. - Check the registrable domain, not the prefix.
apple.com.login.exampleis not Apple; the domain isexample. Homograph attacks often combine a lookalike character with a misleading subdomain. - Look for script mixing in the decoded label.
- Check the TLD. A brand's primary domain does not move to an unusual ccTLD.
- 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.deis transmitted asuser@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
- Decode the domain.
- Read the decoded label letter by letter, not as a word.
- Confirm the registrable domain is the one you expect — the last two labels, not the first.
- 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.