Lesson 04 / 21
Symmetric Encryption
The shared key's trust root and distribution arithmetic: decryption depending on four items — key, nonce, tag, and body — all being correct at the same time, a per-pair key scheme needing 7.69×10^7 key items at 12,401 endpoints, a star scheme cutting that number to 12,400 while turning the center into a single root, and a single shared key scheme where one item falling brings down every connection.
Published Updated
In this lesson
- Counts the items that symmetric decryption requires to be correct at the same time by actually running it
- Calculates the key item count and the item a new participant adds, based on the number of participants
- Compares three distribution schemes in terms of connections lost and endpoints renewed at the moment of a break
Contents
The previous three lessons revolved around one-way operations, and not reversing was the whole point. The fictional measurement network’s actual traffic, though, has to reverse: a reading coming in from the field is opened at the center and turned into a bill. The first lesson tied this operation to two items — the key and an uncorrupted ciphertext. This lesson takes up that first item together with its real problem. If both sides need to know the same key, how did that key get there, and how many are kept?
This is not a question of convenience. The key count is the direct counterpart of this course’s second column — how many items must be correct at the same time — and how it grows together with the number of participants is countable.
The Same Key on Both Sides
CF11: in symmetric encryption, the encrypting and decrypting sides use the same key. The trust root is that key existing only on these two sides.
CF12: for decryption to succeed, the key, the nonce (a value used once per encryption), the authentication tag, and the ciphertext body must all be correct at the same time; if one of the four items is wrong, decryption does not happen.
// symmetric/mode.mjs — the items that must be correct at the same time, from root to use, in // symmetric encryption are actually counted. A meter reading from the fictional regional measurement // network is encrypted with AES-256-GCM. The key is derived from a fixed seed; no randomness. import { createCipheriv, createDecipheriv, scryptSync } from "node:crypto"; // CF11: in symmetric encryption, the encrypting and decrypting sides use the same // key; the trust root is that key existing only on these two sides. // CF12: for decryption to succeed, the key, nonce, and authentication tag must be correct at the same // time; if one of the three items is wrong, decryption does not happen. const READING = "SAY-4417|2026-03-01T06:00Z|41982"; const deriveKey = (t) => scryptSync(t, "fictional-salt", 32, { N: 2 ** 12, r: 8, p: 1 }); const KEY = deriveKey("branch-01-center"), OTHER_KEY = deriveKey("branch-02-center"); const N1 = Buffer.alloc(12, 1), N2 = Buffer.alloc(12, 2); function encrypt(m, key, nonce) { const s = createCipheriv("aes-256-gcm", key, nonce); return { body: Buffer.concat([s.update(m, "utf8"), s.final()]), tag: s.getAuthTag() }; } function decrypt({ body, tag }, key, nonce) { const c = createDecipheriv("aes-256-gcm", key, nonce); c.setAuthTag(tag); return Buffer.concat([c.update(body), c.final()]).toString(); } const attempt = (f) => { try { return f() === READING ? "yes" : "no"; } catch { return "no"; } }; const printRow = (g, ...s) => console.log(s.map((v, i) => (g[i] < 0 ? String(v).padEnd(-g[i]) : String(v).padStart(g[i]))).join("")); const packet = encrypt(READING, KEY, N1); const corrupted = { body: Buffer.from(packet.body), tag: packet.tag }; corrupted.body[2] ^= 1; const D = [-9, -9, -9, -10, 9]; printRow(D, "key", "nonce", "tag", "body", "decrypted"); for (const [a, n, e, g, f] of [ ["correct", "correct", "correct", "intact", () => decrypt(packet, KEY, N1)], ["wrong", "correct", "correct", "intact", () => decrypt(packet, OTHER_KEY, N1)], ["correct", "wrong", "correct", "intact", () => decrypt(packet, KEY, N2)], ["correct", "correct", "wrong", "intact", () => decrypt({ ...packet, tag: Buffer.alloc(16) }, KEY, N1)], ["correct", "correct", "correct", "corrupted", () => decrypt(corrupted, KEY, N1)], ]) printRow(D, a, n, e, g, attempt(f)); console.log("\nsame key, same plaintext:"); const p1 = encrypt(READING, KEY, N1).body.toString("base64"); const p2 = encrypt(READING, KEY, N1).body.toString("base64"); const p3 = encrypt(READING, KEY, N2).body.toString("base64"); console.log(" are two encryptions with the same nonce equal :", p1 === p2 ? "yes" : "no"); console.log(" are two encryptions with different nonces equal :", p1 === p3 ? "yes" : "no"); console.log(" items that must be correct at the same time : 4 (key, nonce, tag, body)");
key nonce tag body decrypted correct correct correct intact yes wrong correct correct intact no correct wrong correct intact no correct correct wrong intact no correct correct correct corrupted no same key, same plaintext: are two encryptions with the same nonce equal : yes are two encryptions with different nonces equal : no items that must be correct at the same time : 4 (key, nonce, tag, body)
Only one of the five rows gives yes. All four items of the chain have to be correct at the
same time, and this is the full expansion, in this mode of operation, of the chain that was
counted as two in the first lesson. The four have separate jobs: the key carries secrecy, the
nonce makes every encryption come out separately, and the tag and body together carry
integrity.
The last two lines show why the nonce is a separate item in the chain. The same plaintext encrypted with the same key and the same nonce gives the same ciphertext. This is encryption’s counterpart to the determinism measured for hashing in the second lesson, and it produces the same consequence: if two records are equal, their outputs are equal too. In the fictional measurement network, this means that the packets of two meters sending the same reading are indistinguishable from each other. The nonce’s job is not secrecy, it is breaking equality — the same job salt has in a password record.
The nonce is not secret and travels along with the packet; its only condition is that it not be used twice with the same key. This condition is the chain’s silent item: when forgotten, it produces no error message at all, but its cost is not just that the same plaintext gives the same output — a nonce repeated with the same key gives away the subkey that produces the authentication tag, and the entire protection collapses.
How Many Messages Does One Key Carry
The nonce condition has a countable bound, and that bound is 96 bits in this mode. If the nonce is chosen randomly for every message, two messages landing on the same nonce is subject to the birthday bound from the second lesson: in a 96-bit space, the expected collision arises at around 2^48 messages, that is, 2.8×10^14 messages.
In the fictional network, this number is not a real constraint. If 12,400 endpoints each send twenty-four readings a day, the daily message count is 297,600; reaching the bound takes 9.5×10^8 days, or more than two and a half million years. Still, the number’s existence turns into a design decision, because in a setup where message volume grows, the same calculation gives a different result.
The second option is to generate the nonce with a counter instead of choosing it randomly. In that case the collision probability drops to zero and the bound rises to 2^96 messages; but a new item is added to the chain — the counter has to persist across restarts. A random nonce is stateless and carries a countable bound; a counter-based nonce removes the bound and brings in a persistent-state item. The choice between the two comes down to whether the setup’s endpoints can hold persistent state.
Key length is a separate column, though, and it is not an item expected to run out. AES’s block size is 128 bits, and its key length is 128, 192, or 256 bits; a 256-bit key’s search space is 2^256, and this number is on the same order as the preimage bound written in the second lesson. The fragile item in the chain is not the key’s length, but where the key sits and how it got there.
Key Distribution Arithmetic
One of the four items — the key — is set apart from the others: it cannot travel with the packet. It has to already be present on both sides beforehand, and that brings a countable burden.
// symmetric/distribution.mjs — symmetric key distribution arithmetic (model). The fictional regional // measurement network has a center, six branches, and field endpoints; participant counts are fictional. // CF13: in symmetric encryption, a separate key is kept for each participant pair; the key item count // grows with the square of the participant count, while the cost of a new participant grows linearly. const printRow = (g, ...s) => console.log(s.map((v, i) => (g[i] < 0 ? String(v).padEnd(-g[i]) : String(v).padStart(g[i]))).join("")); const format = (x) => x >= 1e7 ? x.toExponential(2) : Math.round(x).toLocaleString("en-US"); const pairs = (n) => (n * (n - 1)) / 2; console.log("separate key per participant pair:"); const A = [13, 12, 12, 12, 16]; printRow(A, "participant", "key", "newcomer", "if one key", "if an endpoint"); printRow(A, "", "count", "adds", "leaks", "is compromised"); for (const n of [2, 4, 7, 12, 40, 12_400]) printRow(A, format(n), format(pairs(n)), format(n), 1, format(n - 1)); console.log("\nthree separate schemes, the same 7 participants (center + 6 branches):"); const n = 7; const B = [-27, 9, 12, 22, 11]; printRow(B, "scheme", "key", "newcomer", "if one item falls", "renewal"); printRow(B, "", "count", "adds", "connections affected", "endpoints"); const schemes = [ ["separate key per pair", pairs(n), n, 1, 2], ["single shared key", 1, 0, pairs(n), n], ["star via the center", n - 1, 1, 1, 2], ]; for (const [name, k, added, affected, ep] of schemes) printRow(B, name, format(k), format(added), format(affected), format(ep)); console.log("\nsame three schemes at the field endpoints (12,400 endpoints + 1 aggregator):"); const m = 12_401; for (const [name, k, added, affected, ep] of [ ["separate key per pair", pairs(m), m, 1, 2], ["single shared key", 1, 0, pairs(m), m], ["star via the aggregator", m - 1, 1, 1, 2], ]) printRow(B, name, format(k), format(added), format(affected), format(ep));
separate key per participant pair:
participant key newcomer if one key if an endpoint
count adds leaks is compromised
2 1 2 1 1
4 6 4 1 3
7 21 7 1 6
12 66 12 1 11
40 780 40 1 39
12,400 7.69e+7 12,400 1 12,399
three separate schemes, the same 7 participants (center + 6 branches):
scheme key newcomer if one item falls renewal
count adds connections affected endpoints
separate key per pair 21 7 1 2
single shared key 1 0 21 7
star via the center 6 1 1 2
same three schemes at the field endpoints (12,400 endpoints + 1 aggregator):
separate key per pair 7.69e+7 12,401 1 2
single shared key 1 0 7.69e+7 12,401
star via the aggregator 12,400 1 1 2
The first table gives the arithmetic. For seven participants, 21 keys are kept; for forty participants, 780; for 12,400 endpoints, 7.69×10^7. When a new participant joins, the item added equals the number of participants currently in the network — adding one endpoint to a 12,400-endpoint network requires 12,400 new keys. The key item count grows quadratically, the participation cost grows linearly.
The last two columns measure breaking. One key leaking drops only one connection; this is the sole rationale for keeping a separate key per pair. An endpoint being fully compromised, though, drops every key that endpoint carries: 12,399 connections in a 12,400-endpoint network.
Three Schemes, Three Separate Costs
The second and third tables compare the same set of participants across three schemes, and each of the three has an unacceptable number in one column.
Separate key per pair is the scheme that best bounds breaking — when one item falls, one connection drops, and renewal is done at two endpoints. Its cost is in the key item count: for 12,401 endpoints, 7.69×10^7 keys would have to be kept, and every new endpoint would add 12,401 items. This number is not a management problem, it is an impossibility.
Single shared key is at the opposite end. The key item count is one, and a new participant adds no item at all; in exchange, when that one item falls, all 7.69×10^7 connections drop, and renewal is done at 12,401 endpoints. This is the sharpest example of the course’s axis: reducing the item count to one also reduces the root to one.
Star scheme sits between the two, and it is mostly the scheme chosen in real setups. Every endpoint shares one key with the center alone; the item count drops to 12,400, and the cost of a new endpoint drops to one item. If one endpoint’s key leaks, one connection drops, and renewal is done at two endpoints. The cost is where the table does not show it: endpoints cannot talk to each other directly, and the center becomes the single point that sees all traffic in the clear. The center being compromised drops all 12,400 keys at once.
The renewal cost across the three schemes is 2, 12,401, and 2 endpoints, respectively. But the phrase “renewal at two endpoints” is not a count of operations, it is an access question: how will the new key be delivered to those two endpoints?
The Item the Tag Proves
The authentication tag proves the body has not been corrupted — but it proves one more thing, and that thing depends on the scheme. Since the tag can only be produced by a party that knows the key, a valid tag says “someone who knows the key sent this message.” How much information that statement carries depends on how many parties know the key.
The number is three separate values across the three schemes. In the separate-key-per-pair scheme, two parties know the key; the tag narrows the sender down to two candidates. The same number holds in the star scheme: the endpoint and the center. In the single-shared-key scheme, 12,401 parties know the key, and the tag narrows the sender down to 12,401 candidates — that is, it says nothing.
This is symmetric encryption’s structural limit, and it is measured by the item count. Even in the best case, the candidate count is two; there is no item to distinguish a reading sent by a meter endpoint from one sent by the center, because the center can produce the same tag too. Since nothing stops the receiver from attributing a message it produced itself to the sender, a symmetric tag cannot be presented as proof to a third party. The seventh lesson takes up this gap by name.
The Chain’s Unresolved Item
The same item stays unresolved in all three schemes. The key has to have reached both sides before it is used, and this delivery cannot be done by encrypting it — encrypting it would already require a key.
In the fictional network, this translates into operations done by hand: loading the key onto the device while the meter endpoint is set up, carrying the key to the branch server over a separate channel. For 12,400 endpoints, that is 12,400 items by hand, and the items are paid again at every renewal, not just once at setup. When a key leaks, most of the cost is here, not in the encryption operations.
The trust root is therefore in the distribution, not in the algorithm. AES itself is the chain’s strongest item; where the chain breaks is the channel the key travels over, and how many times that channel is used.
Summary
- Symmetric decryption requires four items to be correct at the same time: key, nonce, authentication tag, and uncorrupted body; only one of five attempts decrypts.
- The nonce is not secret, but it must not be used twice with the same key; if it is, the same plaintext gives the same ciphertext, and equality becomes visible.
- In the separate-key-per-pair scheme, the key item count grows quadratically: 21 at 7 participants, 7.69×10^7 at 12,401 endpoints; a new participant adds as many items as the current participant count.
- A single shared key reduces the item count to one, but it reduces the root to one too: when the one item falls, every connection drops, and renewal is done at 12,401 endpoints.
- In all three schemes, delivering the key to both sides for the first time cannot be done by encrypting it; in a 12,400-endpoint network, that is 12,400 items by hand, repeated at every renewal.
- The authentication tag narrows the sender down to the parties that know the key: 2 candidates in the per-pair scheme, 12,401 candidates in the single-shared-key scheme; a symmetric tag cannot offer proof to a third party.
Next Step
This lesson’s unresolved item stands in a single sentence: to start an encrypted conversation, a pre-shared secret is needed, and sharing that secret itself needs an encrypted channel. The loop cannot be broken from inside symmetric encryption, because a single key carries both the lock and the key that opens it. The next lesson splits this lock in two: one key locks, a separate key unlocks, and one of the two can be given to everyone. The split itself turns into a countable question — of the four possible key-operation combinations, which works, which gives secrecy, which gives identity, and what none of them gives.
To keep your progress and take notes, Log in
My notes
Log in to take notes.