UnveilTech

UnveilDNS Blog

← All articles

Try UnveilDNS free

DNSCrypt stamps decoded: what's really inside sdns://

Posted 2026-06-09 · 6 min read · DNSCryptprivacy

Configuring an encrypted DNS client used to mean juggling four or five separate fields: a server IP, a port, a provider name, a public key, and sometimes a TLS hostname for certificate matching. Get one wrong and you either fail to connect or — worse — connect to the right address while silently trusting the wrong key. A DNS stamp collapses all of that into a single opaque string that starts with sdns://. Paste it once, and the client knows exactly where to go and exactly which server to trust.

That opacity is the point and the problem. A stamp looks like random noise, so most people copy it around without ever knowing what it asserts about the connection — whether the server promises not to log, whether it does its own filtering, and which cryptographic key it is pinned to. This article pulls the string apart byte by byte so you can read one yourself, and explains why the key baked into a DNSCrypt stamp makes a Certificate Authority compromise irrelevant to your DNS privacy.

The short version. A stamp is a base64url-encoded, length-prefixed binary record. The first byte names the protocol, the next eight bits are property flags (DNSSEC, no-log, no-filter), and the rest is the address, the pinned public key, and the provider name. For DNSCrypt that key is the whole trust story — there is no CA in the loop.

Why stamps exist at all

Plain DNS needs one thing from you: an IP address. Encrypted DNS needs more, and the "more" varies by protocol. DNS-over-HTTPS needs a URL. DNS-over-TLS needs an address plus a hostname to validate the certificate against. DNSCrypt needs the server's public signing key and a provider name, because it does not use the web PKI at all. Asking users to assemble these by hand is a recipe for misconfiguration.

The DNS Stamps specification — maintained alongside the dnscrypt-proxy project and documented at dnscrypt.info/stamps-specifications — solves this by serialising every parameter a resolver needs into one self-contained token. The design goals are simple:

The envelope: protocol byte and property flags

Strip the sdns:// scheme prefix and base64url-decode the remainder, and you get a binary blob. The very first byte is the protocol identifier. A handful of values are defined:

First byteProtocolWhat follows
0x00Plain DNSaddress only
0x01DNSCryptaddress, public key, provider name
0x02DNS-over-HTTPS (DoH)address, hashes, hostname, path
0x03DNS-over-TLS (DoT)address, hashes, hostname
0x04DNS-over-QUIC (DoQ)address, hashes, hostname
0x05Oblivious DoH (ODoH) targethostname, path
0x81Anonymized DNSCrypt relayaddress

Immediately after the protocol byte (for the resolver protocols) comes a little-endian 64-bit properties field. In practice only the low three bits carry meaning, and they describe what the operator claims about the service:

BitValueMeaning when set
00x01Server supports DNSSEC validation
10x02Server does not log queries (no-log)
20x04Server does not filter / block (no-filter)

So a properties byte of 0x03 means "DNSSEC + no-log", and 0x07 means "DNSSEC + no-log + no-filter". A filtering resolver — one that blocks malware, trackers or adult content as a feature — deliberately leaves bit 2 clear, signalling that it does filter. Reading these flags before you trust a stamp tells you whether the operator is promising the behaviour you actually want.

These flags are assertions, not proofs. The no-log bit is a claim the operator makes about itself; nothing in the stamp cryptographically enforces it. What the stamp can enforce is identity — that you are talking to the server whose key is pinned, and no one else.

Length-prefixed fields: address, key, provider

After the properties field, every variable-length value uses the same encoding: a single length byte (an LP, "length-prefixed", field) followed by that many bytes of data. This is why a parser never needs delimiters — it reads one length byte, consumes that many bytes, and moves on.

For a DNSCrypt stamp the sequence is:

  1. Address — an LP string like 198.51.100.10:5443. If the port is omitted the protocol default is assumed (DNSCrypt typically runs on a non-standard high port to avoid collisions).
  2. Public key — an LP field of exactly 32 bytes: the server's long-term Ed25519 public key used to verify the resolver certificate it serves.
  3. Provider name — an LP string such as 2.dnscrypt-cert.example. This is not a DNS hostname you resolve; it is a label, prefixed by a versioning digit, that scopes the certificate the client will fetch and verify.

The provider name's leading 2. indicates the certificate format version (the modern XSalsa20-Poly1305 / X25519 construction). The rest is an arbitrary identifier chosen by the operator — it never has to correspond to a registered domain.

How the embedded key makes the CA irrelevant

This is where DNSCrypt diverges sharply from DoH and DoT. Those two ride on TLS, which means the server presents an X.509 certificate that your client validates against the public Certificate Authority hierarchy. If any CA in that hierarchy is compromised or coerced into issuing a fraudulent certificate for the resolver's hostname, an attacker positioned in the path can impersonate the server, and TLS validation will happily succeed.

DNSCrypt does not use the web PKI. The 32-byte public key in the stamp is the trust anchor. Here is the chain of custody:

Because the root of trust is a specific key you already hold — not "any certificate a CA will vouch for" — a CA compromise gains an attacker nothing. They cannot produce a resolver certificate that verifies against a key they do not possess. This is certificate pinning taken to its logical conclusion: there is no CA to pin around, because there was never a CA in the design.

Rotation without re-pinning. The long-term key in the stamp stays stable for years; the resolver certificate it signs rotates frequently (hours to days). Compromise of one short-lived certificate does not compromise the stamp, and operators can rotate the encryption key without forcing every client to re-import a new stamp.

The same envelope for DoH, DoT and ODoH

The stamp format is not DNSCrypt-only. Change the protocol byte and the trailing fields change shape, but the LP-encoding discipline stays identical.

DoH and DoT stamps

For DoH (0x02) and DoT (0x03), there is no embedded signing key — these use TLS — so instead the stamp can carry one or more certificate hashes. Each hash is a SHA-256 digest of the server certificate's Subject Public Key Info (an SPKI pin). When present, the client refuses any TLS certificate whose SPKI does not match, layering a pin on top of ordinary CA validation. A DoH stamp also adds an LP hostname (for SNI and certificate matching) and an LP path (the query endpoint, conventionally /dns-query).

ODoH stamps

Oblivious DoH (0x05) splits knowledge across a relay and a target so neither sees both your identity and your queries. The target stamp drops the address entirely — you reach it through a relay — and carries only the hostname and path. A separate relay stamp (or an ODoH relay variant) describes the hop in front of it. The same self-contained, copy-pasteable token still does the job; only the field set differs.

A structural walkthrough

Let's decode a deliberately fake, truncated DNSCrypt stamp end to end. Do not try to use this as a real server — the key bytes are placeholder filler and the address is from the documentation range 198.51.100.0/24 (RFC 5737).

sdns://AQMAAAAAAAAAEjE5OC41MS4xMDAuMTA6NTQ0MyD---PLACEHOLDER-KEY---
        IjIuZG5zY3J5cHQtY2VydC5leGFtcGxl

Step 1 — strip scheme, base64url-decode the rest into bytes.

Step 2 — read the fields in order:

  byte  0      : 0x01            protocol = DNSCrypt
  bytes 1..8   : 0x03 00 .. 00   properties (little-endian)
                                   -> low byte 0x03 = DNSSEC + no-log
  byte  9      : 0x12 (=18)       length of address field
  bytes 10..27 : "198.51.100.10:5443"   the address (18 bytes)
  byte  28     : 0x20 (=32)       length of public key
  bytes 29..60 : <32 bytes>       Ed25519 long-term public key  (PINNED)
  byte  61     : 0x16 (=22)       length of provider name
  bytes 62..83 : "2.dnscrypt-cert.example"   provider name

Reading top to bottom, this stamp says: connect to 198.51.100.10 on port 5443 using DNSCrypt; the operator claims DNSSEC and no logging; trust only the server that can sign a resolver certificate with this exact 32-byte key; and look for a certificate scoped to the provider label 2.dnscrypt-cert.example. Every parameter your client needs, in one line, with the trust decision baked in.

Note the mechanical simplicity: a parser only ever does "read one length byte, consume N bytes." There is no JSON, no escaping, no schema negotiation. That is why a stamp is small enough to fit in a QR code and robust enough to paste through any channel.

Generating and inspecting stamps safely

You should never hand-craft a stamp's key field — the whole security model depends on the 32 bytes being the operator's genuine signing key. In practice, the resolver generates the key pair and emits the finished stamp for you, and a client decodes it back to human-readable fields on import. If you want to inspect a stamp you were given before trusting it, decode it and check three things:

  1. The protocol byte matches what you expect (a DoH stamp where you assumed DNSCrypt is a red flag).
  2. The property flags match the operator's stated policy — if they advertise no-logging, bit 1 should be set.
  3. The address points where you think it does, and the key is non-trivial (not all zeros, not an obvious placeholder).

UnveilDNS handles the generation side for you: it produces the DNSCrypt key pair, builds the resolver certificate signed by the long-term key, and emits the matching sdns:// stamp so a client only ever has to paste a single string. The decode-and-verify loop above is exactly what a careful client does under the hood every time it imports one.

A DNSCrypt stamp is the rare piece of "config" that is also a security assertion. Learn to read the protocol byte, the three property bits, and the pinned key, and you can tell at a glance not just where a resolver lives, but whether its trust model is one a leaked CA can break — and for DNSCrypt, the answer is no.

Key-pinned DNS, zero config

UnveilDNS generates your DNSCrypt keys and stamp for you — paste one string and you are encrypted.

Deploy UnveilDNS free