Skip to content
academia.sh

Lesson 06 / 21

Key Exchange

Running shared-secret generation over an insecure channel with small parameters: both sides reaching the same shared secret without ever sending their secret exponent, the intersection of the 4 items crossing the channel and the 3 items sufficient for the secret being zero, ephemeral key exchange dropping the number of retroactively opened sessions from 52,560 to 0, and the exchange verifying two of its three items while not verifying the other side's identity.

Published Updated

In this lesson

  • Runs key exchange with small parameters and shows that both sides produce the same shared secret
  • Separates the items in the eavesdropper's hands from the items sufficient for the shared secret and counts their intersection
  • Determines ephemeral exchange's effect on the retroactive-opening count and the unverified item
Contents

In the previous lesson, the hybrid path transported the key, but the side that sent the transported key chose it. This had two consequences. First, the session key’s quality depended entirely on that side’s generator. Second, and heavier: recorded encrypted traffic can be stored, and if the private key leaks years later, every session key up to that point is extracted back out of its envelope.

This lesson takes up the method that removes both at once. The two sides reach the same shared secret without ever sending their secret values; the shared secret never crosses the channel and neither side determines it on its own. The method will actually be run with small parameters, then the items left in the eavesdropper’s hands will be counted one by one.

The Secret That Does Not Cross the Channel

CF17: two sides can produce a shared secret without ever sending their secret exponent; there is no direct computational path between the items crossing the channel and the shared secret.

CF18: the shared secret is formed by both sides’ contribution; neither side determines it on its own.

The method is this. The sides openly agree on a prime p and a generator g. Each side picks a secret exponent, computes g to that exponent modulo p, and sends the result to the other side. Both sides, raising the value received from the other side to their own secret exponent, land on the same value, because exponentiation does not depend on order. The parameters below are teaching-sized and are not used in a real deployment.

// exchange/dh.mjs — key exchange is actually run with small parameters. p and g are
// small fictional values chosen for teaching; they are not used in a real deployment.
// The secret exponents are fixed so the output stays the same run to run. No randomness.
import { hkdfSync, createCipheriv, createDecipheriv } from "node:crypto";

// CF17: two sides can produce a shared secret without ever sending their secret exponent;
//       there is no direct computational path from the items crossing the channel to the shared secret.
// CF18: the shared secret is formed by both sides' contribution; neither side determines it on its own.

const p = 65267n, g = 2n;                     // fictional, teaching-sized prime and generator
const power = (t, u, m) => { let r = 1n; t %= m;
  while (u > 0n) { if (u & 1n) r = r * t % m; t = t * t % m; u >>= 1n; } return r; };

const a = 6421n, b = 8973n;                   // fictional secret exponents: branch and center
const A = power(g, a, p), B = power(g, b, p);   // public values crossing the channel
const branchSecret = power(B, a, p), centerSecret = power(A, b, p);

console.log(`items crossing the channel   : p=${p} g=${g} A=${A} B=${B}`);
console.log("item not crossing the channel: a and b (the two secret exponents)");
console.log("branch shared secret         :", String(branchSecret));
console.log("center shared secret         :", String(centerSecret));
console.log("did both sides produce the same secret:", branchSecret === centerSecret ? "yes" : "no");
console.log("did the shared secret cross the channel:",
  [p, g, A, B].includes(branchSecret) ? "yes" : "no");

// The shared secret is not used directly as a key; key derivation pulls it to a fixed size.
const raw = Buffer.from(branchSecret.toString(16).padStart(4, "0"), "hex");
const key = Buffer.from(hkdfSync("sha256", raw, Buffer.alloc(16, 9),
  "measurement-net-session", 32));
const nonce = Buffer.alloc(12, 4), READING = "SAY-4417|2026-03-01T06:00Z|41982";
const cipher = createCipheriv("aes-256-gcm", key, nonce);
const body = Buffer.concat([cipher.update(READING, "utf8"), cipher.final()]), tag = cipher.getAuthTag();
const decipher = createDecipheriv("aes-256-gcm", Buffer.from(hkdfSync("sha256",
  Buffer.from(centerSecret.toString(16).padStart(4, "0"), "hex"), Buffer.alloc(16, 9),
  "measurement-net-session", 32)), nonce);
decipher.setAuthTag(tag);
console.log("derived key length           :", key.length, "bytes");
console.log("center decrypted branch's packet:",
  Buffer.concat([decipher.update(body), decipher.final()]).toString() === READING ? "yes" : "no");
items crossing the channel   : p=65267 g=2 A=10224 B=40077
item not crossing the channel: a and b (the two secret exponents)
branch shared secret         : 41173
center shared secret         : 41173
did both sides produce the same secret: yes
did the shared secret cross the channel: no
derived key length           : 32 bytes
center decrypted branch's packet: yes

The fifth line is the whole method: the branch reached 41173, the center reached 41173, and that value was never sent. Only 65267, 2, 10224, and 40077 crossed the channel.

The last two lines show how the shared secret is used. The raw shared secret is not used directly as a key — it is a numeric value, its length depends on the parameter, and its distribution is not the distribution expected of a key. The key derivation step turns it into a fixed-size, well-distributed key. The packet encrypted with the derived key can be decrypted by the other side with the key it derives from its own shared secret; the chain works end to end.

The Items in the Eavesdropper’s Hands

What the method rests on is that going from g^a mod p back to a — the discrete logarithm, for short — is expensive. The count below writes this out item by item.

// exchange/item.mjs — key exchange's item count (model): the items in the eavesdropper's
// hands, candidate exponent count by parameter size, the number of retroactively opened
// sessions, and the number of verified items. Fictional regional measurement network numbers are used.

// CF19: key exchange produces a shared secret but does not verify the other side's identity;
//       in an unverified exchange, two sides can each agree on two separate secrets.

const printRow = (g, ...s) => console.log(s.map((v, i) =>
  (g[i] < 0 ? String(v).padEnd(-g[i]) : String(v).padStart(g[i]))).join(""));

console.log("items in the eavesdropper's hands:");
const A = [-29, 12, 19];
printRow(A, "item", "on channel", "enough for secret");
for (const [name, channel, enough] of [
  ["prime p", "yes", "no"], ["generator g", "yes", "no"],
  ["branch's public value A", "yes", "no"], ["center's public value B", "yes", "no"],
  ["branch's secret exponent a", "no", "yes"], ["center's secret exponent b", "no", "yes"],
  ["shared secret", "no", "yes"],
]) printRow(A, name, channel, enough);
console.log("items on the channel: 4, items sufficient for the secret: 3, their intersection: 0");

console.log("\ncandidate exponent count by parameter size:");
const B = [10, 22, 26];
printRow(B, "p bits", "candidate exponents", "fictional-network trial");
for (const bit of [16, 32, 64, 2048])
  printRow(B, bit, bit <= 32 ? (2 ** bit).toLocaleString("en-US") : "2^" + bit,
      bit <= 32 ? "countable" : "uncountable");

const BRANCHES = 6, SESSIONS_PER_YEAR = 24 * 365;   // fictional: hourly sessions per branch
console.log(`\nretroactive opening (fictional ${BRANCHES} branches, ${SESSIONS_PER_YEAR} sessions/year per branch):`);
const C = [-32, -23, 16];
printRow(C, "scheme", "secret determined by", "sessions opened");
printRow(C, "asymmetric envelope transport", "the sender", (BRANCHES * SESSIONS_PER_YEAR).toLocaleString("en-US"));
printRow(C, "ephemeral key exchange", "both sides together", 0);

console.log("\nitem the exchange verifies:");
const D = [-39, 13];
printRow(D, "item", "verified");
for (const [name, v] of [["the shared secret's secrecy", "yes"],
                       ["both sides producing the same secret", "yes"],
                       ["the other side's identity", "no"]]) printRow(D, name, v);
console.log("2 of 3 items are verified; the unverified item leaves the door open for a");
console.log("party in the middle to run two separate exchanges (model, 2 separate shared secrets).");
items in the eavesdropper's hands:
item                           on channel  enough for secret
prime p                               yes                 no
generator g                           yes                 no
branch's public value A               yes                 no
center's public value B               yes                 no
branch's secret exponent a             no                yes
center's secret exponent b             no                yes
shared secret                          no                yes
items on the channel: 4, items sufficient for the secret: 3, their intersection: 0

candidate exponent count by parameter size:
    p bits   candidate exponents   fictional-network trial
        16                65,536                 countable
        32         4,294,967,296                 countable
        64                  2^64               uncountable
      2048                2^2048               uncountable

retroactive opening (fictional 6 branches, 8760 sessions/year per branch):
scheme                          secret determined by    sessions opened
asymmetric envelope transport   the sender                       52,560
ephemeral key exchange          both sides together                   0

item the exchange verifies:
item                                        verified
the shared secret's secrecy                      yes
both sides producing the same secret             yes
the other side's identity                         no
2 of 3 items are verified; the unverified item leaves the door open for a
party in the middle to run two separate exchanges (model, 2 separate shared secrets).

The first table’s last line is the method’s essence: the intersection of the four items crossing the channel and the three items sufficient for the secret is zero. The eavesdropper records all four items, and none of them carries it into the third set. This is the numerical equivalent of the statement “the eavesdropper cannot reach the secret.”

The second table shows what this separation depends on. Going from the items crossing the channel back to the secret exponent is not impossible — it is only expensive, and the expense is set by the parameter size. For this lesson’s 16-bit prime, the candidate exponent count is 65,536, and that is a set an ordinary machine can enumerate. That is why it was chosen as the teaching parameter; the running example is fast because it is small, not because it is secure. The size used in real deployments is 2048 bits and up; the candidate count there is 2^2048.

The same method can also be built with an operation defined over an elliptic curve instead of exponentiation, and the logic does not change: each side picks a secret value, sends its counterpart, and processes what comes from the other side with its own secret value. The only thing that changes is the parameter size needed for the same security. According to the previous lesson’s table, a 3072-bit prime’s equivalent is a 256-bit curve parameter; that is, the public value carried each round drops from 384 bytes to 32 bytes.

In the fictional measurement network, this difference writes straight into the traffic. If 297,600 rounds a day carry a 384-byte public value per round, the daily load is roughly 109 MiB; with a 32-byte public value, the same load drops to 9.1 MiB. That twelvefold difference decides whether the exchange is feasible at all on constrained endpoints.

Two Consequences, Two Separate Numbers

The third table closes the previous lesson’s second problem. When the key is transported in an asymmetric envelope, the sender decides the session key, and the envelope is opened with the long-lived private key. If a party holding recorded traffic reaches that private key years later, it opens every one of the fictional network’s 52,560 sessions; it makes no difference when the sessions took place.

The assumption underneath this line is that encrypted traffic can be recorded. Recording is cheap, and the cost of breaking it can be deferred; in the fictional network, a whole year’s sessions fit into an archive of a few hundred gigabytes. That is why the “the key has not leaked” condition is not measured against today’s question, but against the archive’s lifetime.

In ephemeral key exchange, the number is zero. New secret exponents are chosen for every session, the shared secret is limited to that session, and the exponents are deleted when the session ends. The long-lived key leaking later opens no session retroactively, because past sessions’ secrets are not stored anywhere. This property carries the name forward secrecy, and its cost is generating a pair of exponents per session plus one extra round of exchange.

The Chain’s Seven Items

The answer to how many items of this mechanism have to be correct at the same time is seven, and six of them are inside the mechanism itself.

The first and second are the parameters: p has to actually be prime and large enough, g has to be a proper generator for that prime. The parameters are not secret and are usually constants shared by both sides; that makes them easy to audit, but it also carries a flawed parameter set into every session. A badly chosen prime drops the candidate exponent count far below the table’s 2^2048 value, and this produces no symptom at runtime — the exchange still works, and still yields the same secret.

The third and fourth items are the two secret exponents. The fifth item is the exponents being renewed every session; when they are not renewed, the zero in the forward-secrecy table reverts to the session count. The sixth item is the derivation step: if the raw shared secret is used as the key, the distribution stays broken, and the rest of the chain is built on top of that broken key.

The seventh item is outside the mechanism and is the subject of the next section. Even if all six of the six items are correct, who the other side is is not determined in any way inside this method.

Its cost is countable too. The exchange adds at least one extra round to connection setup and requires an exponentiation computation on both sides. In the fictional measurement network, if 12,400 endpoints open a session once an hour, that is 297,600 extra rounds a day and the same number of exponentiation operations. What is gained in return is the retroactive closing of the third table’s 52,560 sessions.

The Unverified Item

The fourth table is this lesson’s boundary. The exchange verifies two of three items: the shared secret stays secret, and both sides produce the same secret. The third item — the other side’s identity — is not verified.

The consequence can be written as a number. A party that gets between the exchange’s two ends can run two separate exchanges: one shared secret with one side, another shared secret with the other side. Both ends see that things worked correctly, because each produced a valid secret on its own side. The verified item count is two, the item left open is one, and the number of shared secrets that come out of it is two. This is not a configuration flaw; identity is not within the exchange method’s scope.

Agreeing on the parameters is itself inside the same gap. Since p and g cross the channel openly, which parameters the two sides agree on is also an unverified item; a party in the middle can steer both ends into picking the smallest parameter set each supports, and the table’s 2^2048 value silently drops to 2^32. The exchange still completes, both sides still produce the same secret, and no symptom appears. That is why deployments write the smallest accepted parameter size down separately as a policy item — it is not left to negotiation.

The method’s name itself carries this boundary: key exchange, not identity verification. In the fictional measurement network, an exchange between a branch and the center does not say the other side is really the center — it only says a secret was shared with whoever was on the other end of the conversation.

Summary

  • Both sides reach the same shared secret without ever sending their secret exponent: in the running example, both sides landed on 41173, and that value never crossed the channel.
  • The intersection of the 4 items crossing the channel and the 3 items sufficient for the secret is zero; the eavesdropper cannot get from the items it recorded to the shared secret.
  • Security depends on parameter size: for a 16-bit prime, the candidate exponent count is 65,536 and countable; the teaching example is fast because it is small, not because it is secure.
  • The raw shared secret is not used directly as a key; key derivation turns it into a fixed-size, well-distributed key.
  • In ephemeral exchange, the long-lived key leaking opens 0 sessions retroactively; in asymmetric envelope transport, the same leak opens every one of the fictional network’s 52,560 sessions.
  • The exchange verifies two of three items; the other side’s identity is not verified, and an unverified exchange can produce two separate shared secrets.

Next Step

The unverified item pushes one step further back. If the exchange itself cannot give the other side’s identity, an outside proof is needed, and that proof was already seen once, in the fifth lesson: an operation done with the private key is reversed with the public key, and being reversible showed the source. The next lesson takes up this direction in its own form — a signature is produced and verified, the count of how verification drops when a single bit of the signed data is flipped is measured, and the items the signature proves and the items it does not prove are written down separately. The difference between integrity and non-repudiation is also tied to a number there. The topic does not close with this lesson; the real gap will surface at the end of the seventh lesson.

To keep your progress and take notes, Log in

My notes

Log in to take notes.

Start typing to search.

↑↓ ↵ Esc navigate · open · close