Skip to content

NTDLS.SecureKeyExchangev3.0.2

Easily generate a single or multi-round Diffie-Hellman key.

Stable Released Dec 31, 20246,536 downloadsMIT License

A small .NET library (net8.0 / net9.0 / net10.0) that lets two machines or processes agree on a shared secret key over an insecure channel, using a Diffie-Hellman style key exchange. It offers two levels of granularity: UnitNegotiator for a single 16-byte key, and CompoundNegotiator, which chains multiple units together to build a larger compound key of your choosing.

📦 Be sure to check out the NuGet package: https://www.nuget.org/packages/NTDLS.SecureKeyExchange

When would I use this?

Use this package when you need two parties — a local service and a remote peer, two processes, two machines — to establish a shared secret without ever transmitting the secret itself. Typical scenarios include:

  • Bootstrapping encryption between services. Two endpoints exchange short public tokens over any transport (HTTP, sockets, message queues) and end up with identical key bytes they can then feed into symmetric encryption.
  • Building arbitrarily large keys. Each unit contributes 16 bytes; by requesting n units through CompoundNegotiator you get an n × 16-byte shared key, so you are not limited to a fixed key size.
  • Lightweight, dependency-free negotiation. The whole exchange is three method calls per side and a handful of bytes on the wire — no TLS handshake, no external dependencies.

Usage

Single-round exchange with UnitNegotiator

A UnitNegotiator negotiates one 16-byte shared key. Three steps, two sides:

using NTDLS.SecureKeyExchange;

// Step 1: localHost generates a 12-byte token (shared prime, generator, public number).
var localHost = new UnitNegotiator();
byte[] negotiationToken = localHost.GenerateNegotiationToken();

// Step 2: remotePeer applies the token and returns a 4-byte reply token.
var remotePeer = new UnitNegotiator();
byte[] negotiationReply = remotePeer.ApplyNegotiationToken(negotiationToken);

// Step 3: localHost applies the reply token, completing the exchange.
localHost.ApplyNegotiationResponseToken(negotiationReply);

// Both sides now hold the same 16-byte shared secret.
if (remotePeer.SharedSecretHash != localHost.SharedSecretHash)
{
    throw new Exception("This should never happen.");
}

Console.WriteLine($"Key length: {localHost.KeyLength} bytes.");
Console.WriteLine($"Local Shared Secret:  {localHost.SharedSecretHash}");
Console.WriteLine($"Remote Shared Secret: {remotePeer.SharedSecretHash}");

Multi-round exchange with CompoundNegotiator

A CompoundNegotiator runs n units in parallel, producing an n × 16-byte compound key. The API is the same three steps; only the token sizes scale with the unit count:

using NTDLS.SecureKeyExchange;

// localHost starts the process, specifying 8 units -> a 128-byte key (8 * 16).
var localHost = new CompoundNegotiator();
byte[] negotiationToken = localHost.GenerateNegotiationToken(8);

// remotePeer applies the token (its unit count is derived from the token size)
// and returns the reply tokens.
var remotePeer = new CompoundNegotiator();
byte[] negotiationReply = remotePeer.ApplyNegotiationToken(negotiationToken);

// localHost applies the reply tokens, completing all 8 exchanges at once.
localHost.ApplyNegotiationResponseToken(negotiationReply);

// Verify both sides converged on the same secret before using it.
if (remotePeer.SharedSecretHash != localHost.SharedSecretHash)
{
    throw new Exception("This should never happen.");
}

Console.WriteLine($"Key length: {localHost.KeyLength} bytes."); // 128
Console.WriteLine($"Is complete: {localHost.IsNegotiationComplete}");
Console.WriteLine($"Shared secret hash: {localHost.SharedSecretHash}");

// The raw key bytes, ready to seed symmetric encryption.
byte[] keyBytes = localHost.SharedSecret;

Notes:

  • GenerateNegotiationToken(int unitCount) sets KeyLength to unitCount * 16.
  • On the receiving side, ApplyNegotiationToken(byte[]) derives the unit count from the incoming token size, so the receiver does not need to know it in advance.
  • SharedSecret and SharedSecretHash throw if the negotiation is not yet complete; check IsNegotiationComplete first if you want to be explicit.

License

MIT

An unhandled error has occurred. Reload 🗙