CVE-2023-46129 - The NATS.io nkeys Encryption Vulnerability Explained and Exploited
The TL;DR:
If you’re running NATS Server 2.10. through 2.10.3 or using the nkeys Go library .4. through .4.5, pay attention: any encryption handled by nkeys in these versions uses an all-zeros key. This basically wrecks the security for protected data, especially for authentication callouts added in nats-server 2.10. What follows is an exclusive, easy-to-read guide that walks through the bug, the impact, references, plus code to understand (and exploit) the problem.
What is NATS.io and nkeys?
NATS.io is a blazingly fast, open-source messaging platform used for cloud, on-premises, IoT, and edge computing. It lets different services or devices talk to one another via the *publish-subscribe* model.
To secure these chats, NATS uses nkeys, a cryptographic library (in Go) for managing key pairs and, recently, encrypting and decrypting data.
> Fun fact: nkeys originally only did signing/authentication but in 2023, v.4. added encryption support.
CVE-2023-46129 targets the nkeys crypto library. Here’s what happened
- In nkeys versions .4. through .4.5 (used by NATS Server 2.10. through 2.10.3), the new *encryption* code had a bug in how it passed around arrays (the encryption key buffers).
- A function passed the array by value (so the changes didn’t affect the real data) instead of by reference.
- When the function tried to populate the buffer with the real encryption key, it was updating a *copy*, not the buffer actually used for encryption.
- Bottom line? When you tried to encrypt data, the key being used was a 32-byte array of all zeroes—failing entirely at data secrecy.
Important: This doesn’t affect signing/authentication; only encryption is broken.
Below is a simplified, illustrative example (not the exact code, but the heart of the issue)
func xorKey(b [32]byte) {
for i := ; i < len(b); i++ {
b[i] = getRealKeyByte(i) // Real key bytes
}
// Here's the catch: 'b' is a copy, so changes lost!
}
func encrypt(data []byte) []byte {
var key [32]byte // Starts all zeroes!
xorKey(key)
for i := range data {
data[i] ^= key[i % 32] // Actually using all-zeros key :(
}
return data
}
Result: Data is XOR'd with zeroes—i.e., not encrypted at all. Anyone can "decrypt" it trivially.
How Should It Be?
func xorKey(b *[32]byte) {
for i := ; i < len(b); i++ {
b[i] = getRealKeyByte(i)
}
}
func encrypt(data []byte) []byte {
var key [32]byte
xorKey(&key) // Pass by reference!
for i := range data {
data[i] ^= key[i % 32]
}
return data
}
---
Exploit: How an Attacker Could Read Your Secret Data
Let’s see how this breaks confidentiality. Assume someone intercepts encrypted authentication payloads heading to a NATS authentication callout microservice.
Suppose you catch this blob
ciphertext = b93a52... // (just example hex)
In the broken version, actual decryption looks like this in Python
# Real NATS encrypted bytes
ciphertext = bytes.fromhex("b93a52...")
# Normally, key is secret, but here:
key = bytes([]*32)
# XOR with zeros does nothing!
plaintext = bytes([c ^ k for c, k in zip(ciphertext, key*(len(ciphertext)//32+1))])
print(plaintext)
# Output is the original data!
Or, if you like Go
for i := range ciphertext {
plaintext[i] = ciphertext[i] ^ // XOR zero => same byte
}
// plaintext == ciphertext!
What are Auth Callouts?
Starting in NATS Server 2.10., you can configure authentication "callouts":
When a client connects and authenticates, NATS passes data to another backend system for approval. That sensitive data is supposed to be *encrypted* with nkeys.
So, What Goes Wrong?
- If you use Go for auth callouts and the affected nkeys library, your payloads are "encrypted" with the zero-key.
- Attackers who can snoop on traffic between NATS Server and the callout backend can simply read sensitive info, like authentication tokens, as if there was no encryption.
- Any secrets sent this way are fully exposed, *breaking all assumptions of secure backend comms* in your auth pipeline.
Fixing the Hole
No workarounds. Upgrade to nkeys .4.6+ (released with NATS Server 2.10.4+ where this bug is fixed):
- Key handling now passes the buffer by reference, so the real key gets filled in and used for actual secure encryption.
- Update your Go app dependencies, recompile, redeploy your servers and callout microservices together.
Are you relying on nkeys-encrypted payloads between backend components?
If yes:
Upgrade nkeys to v.4.6+ immediately, recompile and redeploy both sides!
Links & References
- NATS Security Advisory: CVE-2023-46129
- GitHub Issue: NKEYS Encryption bug
- Patched nkeys v.4.6 Release
- NATS Server Releases
- Summary at cve.org
Quick Summary Table
| Version | Affected? | Fix? |
|------------------|-------------------|-------------|
| nkeys < .4. | No encryption | Not affected|
| nkeys .4.–.4.5| All encryptions bad| Yes (.4.6) |
| nkeys .4.6+ | Fixed! | Use it |
| NATS Server 2.10.–2.10.3 | Affected | Yes (2.10.4)|
| NATS Server 2.10.4+ | Safe | |
Conclusion
CVE-2023-46129 isn’t your usual edge-case crypto slip—it’s a critical bug where encryption is completely bypassed for a major server’s authentication flows. For anyone using NATS with auth callouts (new since v2.10), your sensitive traffic was naked if on vulnerable versions.
There’s *no mitigation except to update both your servers and backend Go apps* to nkeys .4.6+ and NATS Server 2.10.4+. Don’t sleep on this one!
Found this helpful? Protect your infra—upgrading takes minutes, regret can last months.
*This post is based on public CVE information and official advisories, with added code and explanation for exclusive clarity.*
Timeline
Published on: 10/31/2023 00:15:09 UTC
Last modified on: 11/29/2023 03:15:42 UTC