DNSCrypt stamps decoded: what's really inside sdns://
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.
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:
- Self-contained: no out-of-band configuration. The stamp carries address, port, keys and metadata together.
- Copy-pasteable: the binary record is base64url-encoded, so it survives email, chat and QR codes without escaping.
- Protocol-agnostic: the same envelope describes DNSCrypt, DoH, DoT, Anonymized DNSCrypt and Oblivious DoH (ODoH). The first byte tells the parser which shape to expect.
- Trust-pinning: for protocols that support it, the stamp embeds a hash or key that pins the server identity independently of the public CA system.
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 byte | Protocol | What follows |
|---|---|---|
0x00 | Plain DNS | address only |
0x01 | DNSCrypt | address, public key, provider name |
0x02 | DNS-over-HTTPS (DoH) | address, hashes, hostname, path |
0x03 | DNS-over-TLS (DoT) | address, hashes, hostname |
0x04 | DNS-over-QUIC (DoQ) | address, hashes, hostname |
0x05 | Oblivious DoH (ODoH) target | hostname, path |
0x81 | Anonymized DNSCrypt relay | address |
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:
| Bit | Value | Meaning when set |
|---|---|---|
| 0 | 0x01 | Server supports DNSSEC validation |
| 1 | 0x02 | Server does not log queries (no-log) |
| 2 | 0x04 | Server 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:
- 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). - 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.
- 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:
- The stamp pins a long-term Ed25519 public key (the provider's signing key).
- On connect, the client fetches the server's short-lived resolver certificate. That certificate is signed by the long-term key in the stamp, and the client verifies the Ed25519 signature locally.
- The certificate carries a short-lived Curve25519 (X25519) public key used for the actual query encryption.
- Every query is encrypted to that ephemeral key; an authenticated-encryption construction guarantees confidentiality and integrity.
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.
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:
- The protocol byte matches what you expect (a DoH stamp where you assumed DNSCrypt is a red flag).
- The property flags match the operator's stated policy — if they advertise no-logging, bit 1 should be set.
- 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