Factoring the Noise protocol matrix

The Noise protocol is one of the best things to happen to encrypted protocol design. WireGuard inherits its elegance from Noise. Noise is a cryptography engineer's darling spec. It's important not to get blindsided while fawning over it and to pay attention to where implementers run into trouble. Someone raised a concern I had run into before: Noise has a matrix.

N(rs):
← s
...
→ e, es

NN:
→ e
← e, ee

KN:
→ s

...
→ e
← e, ee, se

XN:
→ e
← e, ee
→ s, se

IN:
→ e, s
← e, ee, se

K(s, rs):
→ s
← s
...
→ e, es, ss

NK:
← s
...
→ e, es
← e, ee

KK:
→ s
← s

→ e, es, ss
← e, ee, se

XK:
← s
...
→ e, es
← e, ee
→ s, se

IK:
← s
...
→ e, es, s, ss
← e, ee, se

X(s, rs):
← s
...
→ e, es, s, ss

NX:
→ e
← e, ee, s, es

KX:
→ s
...
→ e
← e, ee, se, s, es

XX:
→ e
← e, ee, s, es
→ s, se

IX:
→ e, s
← e, ee, se, s, es

To a cryptography engineer, this matrix is beautiful. These eldritch runes describe a grammar: the number of ways you can meaningfully compose the phrases that can make up a Noise handshake into a proper protocol. The rest of the document describes what the trade-offs between them are: whether the protocol is one-way or interactive, whether you get resistance against key-compromise impersonation, what sort of privacy guarantees you get, et cetera. (Key-compromise impersonation means that if I steal your key, I can impersonate anyone to you.)

To the layperson implementer, the matrix is terrifying. They hadn't thought about key-compromise impersonation or the distinction between known-key, hidden-key and exposed-key protocols or even forward secrecy. They're going to fall back to something else: something probably less secure but at least unambiguous on what to do. As Noise matures into a repository for protocol templates with wider requirements, this gets worse, not better. The most recent revision of the Noise protocol adds 23 new "deferred" variants. It's unlikely these will be the last additions.

Which Noise variant should that layperson use? Depends on the application of course, but we can make some reasonable assumptions for most apps. Ignoring variants, we have:

N

NN

KN

XN

IN

K

NK

KK

XK

IK

X

NX

KX

XX

IX

Firstly, let's assume you need bidirectional communication, meaning initiator and responder can send messages to each other as opposed to just initiator to responder. That gets rid of the first column of the matrix.

N

NN

KN

XN

IN

K

NK

KK

XK

IK

X

NX

KX

XX

IX

The other protocols are defined by two letters. From the spec:

The first character refers to the initiator's static key:

  • N = No static key for initiator
  • K = Static key for initiator Known to responder
  • X = Static key for initiator Xmitted ("transmitted") to responder
  • I = Static key for initiator Immediately transmitted to responder, despite reduced or absent identity hiding

The second character refers to the responder's static key:

  • N = No static key for responder
  • K = Static key for responder Known to initiator
  • X = Static key for responder Xmitted ("transmitted") to initiator

NN provides confidentiality against a passive attacker but neither party has any idea who you're talking to because no static (long-term) keys are involved. For most applications none of the *N suites make a ton of sense: they imply the initiator does not care who they're connecting to.

N

NN

KN

XN

IN

K

NK

KK

XK

IK

X

NX

KX

XX

IX

For most applications the client (initiator) ought to have a fixed static key so we have a convenient cryptographic identity for clients over time. So really, if you wanted something with an N in it, you'd know.

N

NN

KN

XN

IN

K

NK

KK

XK

IK

X

NX

KX

XX

IX

The responder usually doesn't know what the key is for any initiator that happens to show up. This mostly makes sense if you have one central initiator that reaches out to a lot of responders: something like an MDM or sensor data collection perhaps. In practice, you often end up doing egress from those devices anyway for reasons that have nothing to do with Noise. So, K* is out.

N

NN

KN

XN

IN

K

NK

KK

XK

IK

X

NX

KX

XX

IX

These remaining suites generally trade privacy (how easily can you identify participants) for latency (how many round trips are needed).

IX doesn't provide privacy for the initiator at all, but that's the side you usually care about. It still has the roundtrip downside, making it a niche variant. XX and XK require an extra round trip before they send over the initiator's static key. Flip side: they have the strongest possible privacy protection for the initiator, whose identity is only sent to the responder after they've been authenticated and forward secrecy has been established.

IK provides a reasonable tradeoff: no extra round trip and the initiator's key is encrypted to the responder's static key. That means that the initiator's key is only disclosed if the responder's key is compromised. You probably don't care about that. It does require the initiator to know the static key of the responder ahead of time but that's probably true anyway: you want to check that key against a trusted value. You can also try private keys for the responder offline but that doesn't matter unless you gratuitously messed up key generation. In conclusion, you probably want IK.

This breakdown only works if you're writing a client-server application that plausibly might've used mTLS instead. WireGuard, for example, is built on Noise_IK. The other variants aren't pointless: they're just good at different things. If you care more about protecting your initiator's privacy than you do about handshake latency, you want Noise_XK. If you're doing a peer-to-peer IoT system where device privacy matters, you might end up with Noise_XX. (It's no accident that IK, XK and XX are in the last set of protocols standing.)

Protocol variants

Ignore deferred variants for now. If you needed them you'd know. PSK is an interesting quantum computer hedge. We'll talk more about quantum key exchanges in a different post, but briefly: a shared PSK among several participants protects against a passive adversary that records everything and acquires a quantum computer some time in the future, while retaining the convenient key distribution of public keys.

Conclusion

It's incredible how much has happened in the last few years to make protocols safer, between secure protocol templates like Noise, new proof systems like Tamarin, and ubiquitous libraries of safer primitives like libsodium. So far, the right answer for a safe transport has almost always been TLS, perhaps mutually authenticated. That's not going to change right away, but if you control both sides of the network and you need properties hard to get out of TLS, Noise is definitely The Right Answer. Just don't stare at the eldritch rune matrix too long. You probably want Noise_IK. Or, you know, ask your security person :)

Thanks to Katriel Cohn-Gordon for reviewing this blog post.

(This post was syndicated on the Latacora blog.)