Encedo WG  key custody for WireGuard networks 0.9.2 · MIT · Encedo HEM

The private key problem

Your identity is
a string in a file.
It does not have to be.

A WireGuard-compatible client whose Curve25519 private key is generated inside an Encedo HEM and never leaves it. The tunnel asks the module to perform the Diffie–Hellman at every handshake. A host that never holds the key cannot lose it, and an operator who runs the network cannot take it.

/etc/wireguard/wg0.conf what leaves the file
[Interface]
Address = 10.99.0.7/32
PrivateKey = uH4kL2nQ8vB1xR7cP0dS ← the whole problem
HEM_KID = bd18991ec27721e35c ← a reference, not a secret
 
[Peer]
PublicKey = OKtW5hG8JAqNzOnARgNa
Endpoint = vpn.example.com:51820
AllowedIPs = 10.99.0.0/24

Why this keeps happening

The configuration file is the target

Not the protocol. The cryptography is not what fails — the file holding its key is, and it fails at industrial scale. These are the last eighteen months.

74,000devices, June 2026

Configuration files and the credentials inside them stolen from Fortinet firewalls and VPN gateways in the leak known as FortiBleed. Named organisations in the dump include Chevron, Samsung, AT&T and Mercedes-Benz.

A stolen configuration is a stolen identity only when the identity is inside it.

15,000devices, January 2025

Configs, addresses and VPN credentials for FortiGate devices published openly. The files were the payload; no one needed to break the tunnel to get in through it.

Here the file carries references. Publishing it hands over the ability to do nothing.

29Msecrets pushed in 2025

Hardcoded secrets added to public repositories in a single year, up 34% (State of Secrets Sprawl). Attackers harvest them within five minutes; the median fix takes 94 days; 64% of 2022’s leaks still worked in 2026.

At five minutes against ninety-four days, rotation is not a control. Having nothing to rotate is.

None of these were failures of WireGuard, and none would have been prevented by a stronger cipher. They are all the same shape: a secret written to a file, and a file that went somewhere files go.

Two guarantees, not one

Taking the key out of the file solves half the problem

These properties fail separately and protect against different people. One decides who can steal an identity. The other decides who can quietly redirect traffic while that identity stays perfectly safe.

First · every tier with hardware

The key cannot be taken

Generated inside the module, non-exportable, and never present on the host. A root shell, a hypervisor snapshot, a disk image, a departing administrator with full access — none of them contain it, because it was never there. Each handshake sends a public value in and receives a shared secret back.

Second · v2 only

The routing cannot be altered unnoticed

A file with no key in it is still a file somebody can edit. Change one AllowedIPs entry and traffic goes somewhere else, silently, with the key untouched. So addresses, routes, endpoints and DNS move into the module as well, under a MAC computed inside it. A failed check refuses the start. There is no degraded mode.

Why this is not already everywhere

The hard part was never storing the key. It is being asked for it mid-handshake.

Keeping a key inside a module is old and well understood. Using one inside WireGuard is neither, and the reason sits in the handshake itself. The exchanges that involve the other side’s ephemeral public key cannot be prepared in advance, because that value does not exist until its packet arrives. The module has to answer while the handshake is still open, twice, every time. A key store that is merely safe is not enough. It has to be on the path, and it has to be quick.

That rules out most of what already exists. No mainline WireGuard implementation speaks PKCS#11, and the attempts that come close need kernel patches, custom builds, or give up and move to IPsec. A cloud KMS will hold the key, but it is reached over the network the tunnel was supposed to be providing — the dependency points back at itself. A TPM protects a key for the one machine it is soldered into, which is the opposite of an identity you can carry to the next one.

What is left is a module that performs a raw Curve25519 exchange over a link you control, fast enough that the far end never notices. That is the whole of it, and it is why the protocol did not have to change to accommodate any of this.

The product line

Three states of the same file

Each tier removes something from the machine. Start where your deployment already is and move when the threat model asks for it — the far end never changes, and neither does the protocol.

v0 · software only standard WireGuard What almost everyone runs today. Fast, clean, and the key is a string on a disk.
wg0.confon disk
[Interface]
PrivateKey = uH4kL2nQ8v
Address = 10.99.0.7/32
[Peer]
AllowedIPs = 10.99.0.0/24
Key readable by root, by backups, by snapshots Routing editable, and no one is told
v1 · hardware key wg-quick-encedo The file you already have, minus the one line that mattered. The smallest possible move.
wg1.confsafe to leak
[Interface]
HEM_KID = bd18991ec2
Address = 10.99.0.7/32
[Peer]
AllowedIPs = 10.99.0.0/24
Key in the module, never exported Routing still a file anyone may edit
v2 · hardware everything wg-hem Nothing on disk at all. The configuration lives in the module and is authenticated there.
no configuration file the module holds it addresses · routes · DNS · peers
under a MAC computed inside
Key in the module, never exported Routing authenticated; tampering stops the start

v2 in practice

Nothing on the machine means nothing to move, protect or restore

The operational consequences of an empty disk turn out to matter as much as the security ones, and they show up on the first day rather than after an incident.

Portable

The identity travels, the machine does not

Move the module to another host, run one command, and it is the same tunnel with the same key and the same peers. Nothing is copied between machines, so nothing is in transit to intercept and nothing has to be securely deleted from the old one.

Disposable hosts

A compromised machine is re-imaged, not re-keyed

After an incident you rebuild the host and carry on. You do not rotate the identity, re-issue peers or coordinate a change window with the other side, because the identity was never on the machine that was compromised.

One command

Deployment with nothing to template

The module answers at a fixed address on its own link, so the client starts with no arguments at all. There is no file for Ansible to render, no secret for a vault to inject, and no first-boot step that has to be trusted with key material.

Quiet backups

Snapshots stop being a custody question

No file to include or exclude, nothing to scrub from an image, no rule to write about which directories a backup agent may touch. A routine snapshot cannot become an identity leak when the identity is not in the filesystem.

Works with what you already run

Half of a tunnel can be upgraded. The other half never has to know.

Your side gains hardware key custody. The far end stays an ordinary WireGuard endpoint holding an ordinary [Peer] entry — same public key, same handshake, same bytes on the wire. Any implementation that follows the standard is a valid other end, and it is not asked to prove it.

So there is no migration. No coordination window with the other side, no vendor to renegotiate with, no fleet-wide change to schedule, no provider to leave. Fit it to one laptop this afternoon and the rest of the network does not notice — which is also how you evaluate it without committing to anything.

Threat model

What someone gets, by where they start

Verified against a stock kernel implementation on an unmodified distribution: the other end needed no software, no patches and no knowledge that hardware was involved.

If someone… v0 · standard v1 · hardware key v2 · config-free
reads the configuration fileIdentity takenA reference, useless aloneNo file exists
restores a backup or snapshotIdentity takenNo key in the imageNo key in the image
obtains a root shellIdentity takenNothing to dumpNothing to dump
edits the routingTraffic redirected, silentlyTraffic redirected, silentlyStartup refused
takes the hardware itself—Authentication requiredAuthentication required

The other side of the ledger

What this costs you

A design that puts the key in hardware has to reach that hardware. These are consequences of the guarantee rather than defects awaiting a fix, and they are worth knowing before you buy, not after.

The module must stay reachable
Two calls per handshake, and the protocol renegotiates roughly every two minutes. It sits on the handshake path, not the data path — measured round-trip time through the tunnel matched the raw path to within about a millisecond — but a module that goes away ends the tunnel at the next rekey.
Failure is closed, deliberately
Three retries, then the interface comes down cleanly. There is no mode that keeps a tunnel running without the key that authenticates it, because that mode would be the thing you paid to eliminate.
Sessions are bounded
A token is authorised when the tunnel starts; when it expires the tunnel ends and a person restarts it. Refreshing silently would mean keeping the passphrase alive for the life of the process.
A person starts every tunnel
On the command line it holds the foreground. Behind the graphical client a privileged service runs the tunnel, but it holds no passphrase: the window authenticates a person and hands over one scoped token, and when the window lets go the tunnel ends. Nothing restarts unattended, because that would mean re-authenticating to the module without a human and putting a credential back on the host.

Where the separation earns its keep

Custody apart from operation

Provision the key, hand the machine to whoever runs it, and running the network and holding its identity become different powers — enforced by hardware rather than by a policy document that assumes good behaviour.

Regulated environments

Key material stays in an approved store rather than in a file, and every use of the private key is an operation the module performs and can account for. Custody becomes demonstrable instead of asserted.

Edge and industrial sites

Equipment in places you do not control, reachable by people you have not vetted. Physical access to the host stops being equivalent to possession of the identity.

Managed service providers

Operate a client's infrastructure and prove non-access to their keys at the same time. The proof is structural: there is nothing on the machine to hand over.

Getting there

What starting actually looks like

01

Provision once. The module generates the identity and stores the configuration. Nothing is written to disk, and the client public key comes back on standard output for whoever runs the other end.

02

Add one ordinary peer on the server. It is a normal WireGuard entry; the server administrator needs to know nothing else.

03

Bring it up. The handshake completes against a key that exists only inside the module.

$ wg-hem provision \
    --address 10.99.0.7/32 \
    --peer 'pubkey=…,endpoint=vpn:51820,allowed-ips=10.99.0.0/24'
  → aIGeAZjTRcM2Tqcpy9cODxs0DOaWRGUIMPnCNBC7y3k=

$ sudo wg-hem up
  → Interface wg0 is up.
  → Handshake with "hq" completed.

Open source under the MIT licence at github.com/encedo/encedo-wg-hsm. The hardware is an Encedo HEM.

If this fits something you run

Tell us what you would point it at.

This is 0.9.2 and the people building it read what arrives. Say what you run today and what you would try it against — that is worth more to us, and to you, than a place on a mailing list.

Honest status

Where the project actually is

Release
0.9.2 · MIT
Verified end to end
Linux and Windows
Packaged
.deb and MSI for x64 and ARM64, built on every release tag; Windows signed
In progress
macOS
Requires
Encedo HEM, TLS 1.3
Peer requirements
None

Provisioning, configuration verification, tunnel establishment, peer failover, rekey and status have been exercised against real hardware, on Linux and on Windows, where a graphical client and a privileged service install from an MSI. A long-running soak, loss of the module mid-tunnel, token expiry and full-tunnel routing have not. The Windows binaries carry the company’s signature since September 2026, made through Azure Artifact Signing: a tag asks for it, a person approves the one job that can sign, and no key lives in the repository. macOS is under way: the command line and the graphical client are built for Intel and Apple silicon, and testing against a module and Apple signing and notarisation are still in progress. This page will say so until each of these changes.