<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Dmytro Huz</title>
    <description>The latest articles on DEV Community by Dmytro Huz (@dmytro_huz).</description>
    <link>https://dev.to/dmytro_huz</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1824917%2F1549c3d8-4f61-495f-b96f-6ddd331a3c9f.PNG</url>
      <title>DEV Community: Dmytro Huz</title>
      <link>https://dev.to/dmytro_huz</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dmytro_huz"/>
    <language>en</language>
    <item>
      <title>Networking Machinery for Software Engineers Part 2: Extending The Empire and Rebuilding a Global Network</title>
      <dc:creator>Dmytro Huz</dc:creator>
      <pubDate>Thu, 03 Sep 2026 06:29:07 +0000</pubDate>
      <link>https://dev.to/dmytro_huz/networking-machinery-for-software-engineers-part-2-extending-the-empire-and-rebuilding-a-global-39jc</link>
      <guid>https://dev.to/dmytro_huz/networking-machinery-for-software-engineers-part-2-extending-the-empire-and-rebuilding-a-global-39jc</guid>
      <description>&lt;p&gt;&lt;a href="https://www.dmytrohuz.com/p/networking-machinery-for-software" rel="noopener noreferrer"&gt;We have just built our small kingdom.&lt;/a&gt; One valley, a few villages, a few signal towers, and a local network that works surprisingly well for something invented by people who still believe that medicine is mostly a matter of balancing four bodily fluids.&lt;/p&gt;

&lt;p&gt;Unfortunately, sooner or later power becomes the main goal of the most ambitious people in the kingdom, and sooner or later one kingdom becomes too small for them. Scouts discover settlements behind the mountains, armies march along roads that were previously used for sheep, and within a few years our neat little kingdom becomes an empire with several valleys somewhere beyond the horizon.&lt;/p&gt;

&lt;p&gt;The moral dilemma is outside the scope of this article, and we, as network engineers in medieval Europe, are definitely not in a position to judge the military policy of our lords. What we do need to judge is whether the communication system from Part 1 can survive this expansion.&lt;/p&gt;

&lt;p&gt;It cannot.&lt;/p&gt;

&lt;p&gt;Our old system knows how to move a frame inside one valley, but it has no idea what a valley is, where its border lies, or what to do when a destination is hidden behind three mountain passes and somebody else's tax system.&lt;/p&gt;

&lt;p&gt;So we will build the larger system in the same way we built the local one. We will remain with our towers until messages can cross the whole empire, and only after the machinery works will we return to a real computer and the Internet. This order matters because IP addresses, gateways and routing tables are much easier to remember when they arrive as answers to problems we have already felt.&lt;/p&gt;

&lt;p&gt;In this post we will discuss how IP addresses, network prefixes, gateways, routing tables and ARP turn local networks into a network of networks. We will then see how the same model appears on Linux, how DHCP gives a host the information it needs, and what changes while one packet crosses several routers.&lt;/p&gt;

&lt;p&gt;The real-world part focuses on IPv4 carried over Ethernet. Other link technologies can carry IP without Ethernet, and IPv6 uses Neighbor Discovery instead of ARP, but one carefully bounded world is enough for now. Our empire is already difficult to govern without opening several additional continents.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two orders of names
&lt;/h2&gt;

&lt;p&gt;In our existing system every tower already has a local name such as &lt;code&gt;0A&lt;/code&gt;, &lt;code&gt;0B&lt;/code&gt; or &lt;code&gt;0C&lt;/code&gt;, and that worked perfectly while the entire known world fitted inside one valley.&lt;/p&gt;

&lt;p&gt;Then we acquired another valley.&lt;/p&gt;

&lt;p&gt;It has its own towers, which may be called &lt;code&gt;1A&lt;/code&gt;, &lt;code&gt;1B&lt;/code&gt; and &lt;code&gt;1C&lt;/code&gt;. They may also be called &lt;code&gt;0A&lt;/code&gt;, &lt;code&gt;0B&lt;/code&gt; and &lt;code&gt;0C&lt;/code&gt;, because coordination between medieval kingdoms is apparently not much better than coordination between modern IT departments.&lt;/p&gt;

&lt;p&gt;Now an order addressed to &lt;code&gt;0A&lt;/code&gt; is ambiguous. There is an &lt;code&gt;0A&lt;/code&gt; in Valley 1 and another &lt;code&gt;0A&lt;/code&gt; in Valley 2, and the old name tells us who should receive a message only after we already know which local network we are talking about.&lt;/p&gt;

&lt;p&gt;This is not a flaw in the old system. A local name was invented for local delivery, and inside one valley it still does that job perfectly. The empire has simply created a second question that the local name was never meant to answer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In which valley does the destination live?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So we introduce another order of names. Tower &lt;code&gt;0A&lt;/code&gt; keeps its local address and independently receives the wider address &lt;code&gt;V1.7&lt;/code&gt;. A tower in Valley 2 may reuse the same local address &lt;code&gt;0A&lt;/code&gt; while independently receiving &lt;code&gt;V2.12&lt;/code&gt;. The local and wider addresses are not constructed from one another. They are separate identifiers assigned for different scopes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F10wvysp3718r6vs42y96.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F10wvysp3718r6vs42y96.jpg" alt="Figure 1" width="799" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 1. The local address &lt;code&gt;0A&lt;/code&gt; can be reused in both valleys. The independent wider addresses &lt;code&gt;V1.7&lt;/code&gt; and &lt;code&gt;V2.12&lt;/code&gt; identify the towers across the empire, and neither is derived from &lt;code&gt;0A&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We have just reconstructed the reason a machine can need both a MAC address and an IP address.&lt;/p&gt;

&lt;p&gt;The MAC address is the local name. It identifies the immediate sender or receiver on one Ethernet network. The IP address is the wider name. It identifies the source and destination across many connected networks.&lt;/p&gt;

&lt;p&gt;The two addresses do not compete, and one is not a more modern replacement for the other. They describe the same machine at different scopes, rather like a person's room number and full postal address. A room number can be perfectly useful inside one building even though a letter crossing the country needs more information.&lt;/p&gt;
&lt;h3&gt;
  
  
  Is the destination still inside our valley?
&lt;/h3&gt;

&lt;p&gt;Imagine that tower &lt;code&gt;0A&lt;/code&gt; in Valley 1 receives an order:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Deliver this message to &lt;code&gt;V1.12&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The destination begins with &lt;code&gt;V1&lt;/code&gt;, and our tower also belongs to &lt;code&gt;V1&lt;/code&gt;, so the destination is local. It can use exactly the machinery we built in Part 1. It discovers which local tower owns &lt;code&gt;V1.12&lt;/code&gt;, learns that tower's local address if necessary, and sends a frame through the valley.&lt;/p&gt;

&lt;p&gt;Then another order arrives:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Deliver this message to &lt;code&gt;V2.12&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This time the wider address tells us something important before a single signal is sent. The destination belongs to Valley 2, while our tower belongs to Valley 1. None of the local towers can deliver a frame directly through the mountain.&lt;/p&gt;

&lt;p&gt;Our medieval rule can still be very simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;V1.*  local
everything else  beyond the valley
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Modern networking calls the precise version of this boundary a network prefix, but we do not need the binary notation yet. For the moment, the important discovery is that an address must contain enough structure to tell us not only who the destination is, but also whether it belongs to our local world.&lt;/p&gt;

&lt;p&gt;Once &lt;code&gt;0A&lt;/code&gt; knows that &lt;code&gt;V2.12&lt;/code&gt; is remote, it needs somewhere local to send the message.&lt;/p&gt;

&lt;h2&gt;
  
  
  A gateway at the edge of the valley
&lt;/h2&gt;

&lt;p&gt;We build a special tower at the border of Valley 1. It can receive local frames from our valley, but it also has a road leading into another network. Let us call it &lt;code&gt;G1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Tower &lt;code&gt;0A&lt;/code&gt; now learns one additional rule:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If the final destination is outside Valley 1, give the packet to &lt;code&gt;G1&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This creates a situation that looks strange only until the two scopes become visible. Suppose &lt;code&gt;0A&lt;/code&gt; wants to reach &lt;code&gt;V2.12&lt;/code&gt;. The final destination is still &lt;code&gt;V2.12&lt;/code&gt;, but the next tower that can physically receive a local frame is &lt;code&gt;G1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The message therefore carries two destinations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Final IP destination: V2.12
Local MAC destination: G1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdab1fggzua1hkdj1ocpg.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdab1fggzua1hkdj1ocpg.jpg" alt="Figure 2" width="799" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 2. The local frame is an outer delivery envelope addressed to the next hop, &lt;code&gt;G1&lt;/code&gt;. The packet inside keeps the final destination, &lt;code&gt;V2.12&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The outer address gets the message across the current local network. The inner address survives beyond that network and tells every gateway where the journey should eventually end.&lt;/p&gt;

&lt;p&gt;Gateway &lt;code&gt;G1&lt;/code&gt; receives the frame because its own local address is written on the outside. It removes that local envelope and reads the wider address inside:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Destination IP: V2.12
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now &lt;code&gt;G1&lt;/code&gt; faces the same question that tower &lt;code&gt;0A&lt;/code&gt; faced, only on a larger map. Where should this packet go next?&lt;/p&gt;

&lt;h2&gt;
  
  
  A small map inside every gateway
&lt;/h2&gt;

&lt;p&gt;We hang a parchment inside &lt;code&gt;G1&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Valley 1          directly connected
Valley 2          eastern relay R1
Valley 3          northern relay R2
Everything else  royal highway G0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This parchment is our routing table.&lt;/p&gt;

&lt;p&gt;It does not describe the complete road from Valley 1 to every tower in the empire. &lt;code&gt;G1&lt;/code&gt; does not need to know every bridge, every mountain pass or every official who will lose the paperwork. It only needs to know the next useful step.&lt;/p&gt;

&lt;p&gt;For &lt;code&gt;V2.12&lt;/code&gt;, the table selects relay &lt;code&gt;R1&lt;/code&gt;. If the link between &lt;code&gt;G1&lt;/code&gt; and &lt;code&gt;R1&lt;/code&gt; is another local Ethernet-like network, the wider address of the next hop is still not enough to send a frame. &lt;code&gt;G1&lt;/code&gt; needs the local address of &lt;code&gt;R1&lt;/code&gt; on that link.&lt;/p&gt;

&lt;p&gt;This brings us back to the machinery from Part 1. If the mapping is not already known, &lt;code&gt;G1&lt;/code&gt; asks the local network which MAC address belongs to the next-hop IP address. In IPv4 over Ethernet, this is ARP.&lt;/p&gt;

&lt;p&gt;The forwarding process is therefore not one mysterious act called routing. It is a sequence of smaller questions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Where is the packet ultimately going?
        ↓
Which route best matches that destination?
        ↓
What is the next hop on that route?
        ↓
Which local address belongs to that next hop?
        ↓
Send one local frame
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Routing chooses the direction. ARP, when the current link is IPv4 over Ethernet, supplies the local delivery address needed to take the next step.&lt;/p&gt;

&lt;h3&gt;
  
  
  The same packet, a new local frame
&lt;/h3&gt;

&lt;p&gt;The route across our empire might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Tower 0A in Valley 1
        ↓
Gateway G1
        ↓
Eastern relay R1
        ↓
Gateway G2
        ↓
Tower 0B in Valley 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the first hop, the local frame is addressed to &lt;code&gt;G1&lt;/code&gt;. After &lt;code&gt;G1&lt;/code&gt; opens that frame and chooses the next route, it creates a new local frame addressed to &lt;code&gt;R1&lt;/code&gt;. Relay &lt;code&gt;R1&lt;/code&gt; repeats the process and creates another frame addressed to &lt;code&gt;G2&lt;/code&gt;. Inside Valley 2, &lt;code&gt;G2&lt;/code&gt; finally creates a frame addressed to tower &lt;code&gt;0B&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffasaw146irp1z4vz4chq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffasaw146irp1z4vz4chq.png" alt="Figure 3" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 3. The blue end-to-end destination remains &lt;code&gt;V2.12&lt;/code&gt;. Each gold local delivery ends at one next hop, after which a new local frame is created for the following link.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The IP destination does not become &lt;code&gt;G1&lt;/code&gt;, then &lt;code&gt;R1&lt;/code&gt;, then &lt;code&gt;G2&lt;/code&gt;. It remains &lt;code&gt;V2.12&lt;/code&gt;, because that is the end of the journey. What changes is the local delivery information wrapped around it.&lt;/p&gt;

&lt;p&gt;This distinction gives us the mental model for the whole article:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The IP packet describes the end-to-end conversation. The local frame describes only the next delivery.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Every gateway repeats the same small procedure. It removes the local frame, reads the destination IP, consults its routing table, chooses a next hop, and creates whatever local frame the next link requires.&lt;/p&gt;

&lt;p&gt;The routes also describe valleys rather than every individual tower. &lt;code&gt;G1&lt;/code&gt; does not need separate rules for &lt;code&gt;V2.1&lt;/code&gt;, &lt;code&gt;V2.2&lt;/code&gt;, &lt;code&gt;V2.3&lt;/code&gt; and every other address in Valley 2. One rule for &lt;code&gt;V2.*&lt;/code&gt; is enough, because the next useful step is the same for all of them.&lt;/p&gt;

&lt;p&gt;Our empire may eventually grow too large for routes to be written by hand. Routers can then exchange information and learn routes from one another, but that changes how the parchment is produced, not how a packet uses it. Forwarding still means reading the destination, selecting the best route and taking one local step.&lt;/p&gt;
&lt;h2&gt;
  
  
  The machinery above the valleys
&lt;/h2&gt;

&lt;p&gt;We have now rebuilt the complete mechanism with towers. Local names move frames inside a valley. Wider addresses identify destinations across valleys. A boundary tells a tower whether the destination is local. A gateway accepts everything beyond that boundary. A routing table chooses the next hop, and each local network carries the packet one step farther.&lt;/p&gt;

&lt;p&gt;Our little kingdom is no longer one network.&lt;/p&gt;

&lt;p&gt;It is becoming a network of networks.&lt;/p&gt;

&lt;p&gt;In other words, we have started building Layer 3.&lt;/p&gt;

&lt;p&gt;Only now are we ready to leave the towers.&lt;/p&gt;
&lt;h2&gt;
  
  
  Back to the real world
&lt;/h2&gt;

&lt;p&gt;Imagine one Linux machine connected to a home or office network:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="err"&gt;IP&lt;/span&gt; &lt;span class="py"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;      &lt;span class="s"&gt;192.168.1.23/24&lt;/span&gt;
&lt;span class="py"&gt;Interface&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;       &lt;span class="s"&gt;eth0&lt;/span&gt;
&lt;span class="err"&gt;Default&lt;/span&gt; &lt;span class="py"&gt;gateway&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;192.168.1.1&lt;/span&gt;
&lt;span class="err"&gt;MAC&lt;/span&gt; &lt;span class="py"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;     &lt;span class="s"&gt;3c:52:82:xx:xx:xx&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is almost exactly what our tower knew. The machine has a local Ethernet identity, a wider IPv4 identity, a description of its local boundary, and the address of a gateway that can accept packets leaving that boundary.&lt;/p&gt;

&lt;p&gt;Suppose an application wants to reach &lt;code&gt;192.168.1.50&lt;/code&gt;. Linux has a directly connected route for the local network, so the selected next hop is the destination itself. If the neighbor cache does not already contain the answer, ARP asks:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Who has &lt;code&gt;192.168.1.50&lt;/code&gt;?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Once the machine learns a mapping such as &lt;code&gt;192.168.1.50 → aa:bb:cc:dd:ee:ff&lt;/code&gt;, it can place the IP packet inside an Ethernet frame addressed directly to that MAC address.&lt;/p&gt;

&lt;p&gt;Nothing particularly dramatic happened. We stayed inside one valley.&lt;/p&gt;

&lt;h3&gt;
  
  
  Leaving the valley
&lt;/h3&gt;

&lt;p&gt;Now the same application wants to reach &lt;code&gt;8.8.8.8&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Linux exposes the relevant routes with &lt;code&gt;ip route&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;default via 192.168.1.1 dev eth0
192.168.1.0/24 dev eth0 scope &lt;span class="nb"&gt;link&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At first this looks like the usual Linux habit of turning a simple idea into compressed hieroglyphics, but the table contains only two important rules. Destinations in &lt;code&gt;192.168.1.0/24&lt;/code&gt; are directly reachable through &lt;code&gt;eth0&lt;/code&gt;. Everything for which no more specific route exists goes through &lt;code&gt;192.168.1.1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For &lt;code&gt;8.8.8.8&lt;/code&gt;, the local route does not match, so the default route selects &lt;code&gt;192.168.1.1&lt;/code&gt; as the next hop. The packet remains addressed to &lt;code&gt;8.8.8.8&lt;/code&gt;, while the first Ethernet frame is addressed to the MAC address of &lt;code&gt;192.168.1.1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If that MAC address is unknown, ARP asks:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Who has &lt;code&gt;192.168.1.1&lt;/code&gt;?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There is one technical correction worth making explicit here. It is common to explain the host as if it first performs a separate subnet test and then, only for a remote destination, remembers that routing tables exist. The operating system actually performs a route lookup. Configuring &lt;code&gt;192.168.1.23/24&lt;/code&gt; on &lt;code&gt;eth0&lt;/code&gt; creates the directly connected route, while configuring the gateway creates the default route. Local and remote delivery are two outcomes of the same lookup.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6aucbr1irv8h68mt4lgn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6aucbr1irv8h68mt4lgn.png" alt="Figure 4" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 4. The route lookup happens first. It selects either the destination itself or the gateway as the next hop, and only then does ARP resolve that selected next hop on Ethernet.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This ordering prevents two common confusions. ARP does not decide whether a destination is local, and it does not choose a route. It answers a local address-resolution question after routing has already chosen the next hop.&lt;/p&gt;
&lt;h2&gt;
  
  
  Where does the valley end?
&lt;/h2&gt;

&lt;p&gt;Our tower used a rule such as &lt;code&gt;V1.*&lt;/code&gt; to recognize its valley. IPv4 represents the same idea with a network prefix.&lt;/p&gt;

&lt;p&gt;An IPv4 address contains 32 bits, which we usually write as four decimal octets. In &lt;code&gt;192.168.1.23/24&lt;/code&gt;, the &lt;code&gt;/24&lt;/code&gt; says that the first 24 bits belong to the network portion and the remaining 8 belong to the host portion.&lt;/p&gt;

&lt;p&gt;For this address, the network is &lt;code&gt;192.168.1.0/24&lt;/code&gt;. Addresses such as &lt;code&gt;192.168.1.5&lt;/code&gt;, &lt;code&gt;192.168.1.50&lt;/code&gt; and &lt;code&gt;192.168.1.200&lt;/code&gt; belong to that network, while &lt;code&gt;192.168.2.5&lt;/code&gt; does not.&lt;/p&gt;

&lt;p&gt;The traditional subnet mask &lt;code&gt;255.255.255.0&lt;/code&gt; expresses the same boundary. In binary it contains 24 ones followed by 8 zeroes. The ones mark the network portion, while the zeroes leave room for host addresses.&lt;/p&gt;

&lt;p&gt;The boundary does not have to respect the dots in the decimal notation, because the dots exist for us and the bits exist for the machine. Consider &lt;code&gt;192.168.1.10/26&lt;/code&gt;. The first 24 bits and the first 2 bits of the final octet belong to the network, leaving 6 host bits.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvq0e8du6r9sorkbrpw0y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvq0e8du6r9sorkbrpw0y.png" alt="Figure 5" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 5. Each final octet contains exactly eight bit positions. A &lt;code&gt;/24&lt;/code&gt; leaves positions 1 through 8 for hosts, while a &lt;code&gt;/26&lt;/code&gt; uses positions 1 and 2 for the network and leaves positions 3 through 8, exactly six bits, for hosts.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Those four &lt;code&gt;/26&lt;/code&gt; networks begin at:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;192.168.1.0/26
192.168.1.64/26
192.168.1.128/26
192.168.1.192/26
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is why &lt;code&gt;192.168.1.10&lt;/code&gt; and &lt;code&gt;192.168.1.50&lt;/code&gt; belong to the first &lt;code&gt;/26&lt;/code&gt;, while &lt;code&gt;192.168.1.70&lt;/code&gt; belongs to the second. The decimal addresses look almost like neighbors, but the prefix has placed a real border between them.&lt;/p&gt;

&lt;p&gt;The prefix is not an intermediate technique unrelated to our story. It is the exact mathematical form of the question our first tower had to answer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Does the destination belong to my local network, or must the selected route lead through a gateway?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Who gave the host its map?
&lt;/h2&gt;

&lt;p&gt;So far our computer somehow knew its IP address, its prefix, its default gateway and usually a DNS server. On a normal home or office network, nobody sits down every morning and manually configures every laptop, phone, printer and coffee machine that has somehow acquired WiFi.&lt;/p&gt;

&lt;p&gt;Most of the time, DHCP does the paperwork.&lt;/p&gt;

&lt;p&gt;A new host already has its local Ethernet identity, but it does not yet know its IPv4 address, its network boundary or its default gateway. It cannot send a normal IP request to a server whose address it does not know from an address it does not have, so it begins with a local broadcast:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I am here. Can somebody tell me who I am supposed to be?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The basic exchange is usually remembered as DORA: Discover, Offer, Request and Acknowledgement. The server may eventually give the client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="err"&gt;IP&lt;/span&gt; &lt;span class="py"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;      &lt;span class="s"&gt;192.168.1.23&lt;/span&gt;
&lt;span class="py"&gt;Prefix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;          &lt;span class="s"&gt;/24&lt;/span&gt;
&lt;span class="err"&gt;Default&lt;/span&gt; &lt;span class="py"&gt;gateway&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;192.168.1.1&lt;/span&gt;
&lt;span class="err"&gt;DNS&lt;/span&gt; &lt;span class="py"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;      &lt;span class="s"&gt;192.168.1.1&lt;/span&gt;
&lt;span class="py"&gt;Lease&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;           &lt;span class="s"&gt;24 hours&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;DHCP did not route anything. It simply handed the host the small piece of the map it needs before routing can begin.&lt;/p&gt;

&lt;p&gt;The lease means that the address is borrowed rather than carved into stone. Before the lease expires, the client normally tries to renew it, and the server may allow the same address to remain or assign another one.&lt;/p&gt;

&lt;p&gt;The first DHCP discovery is a local broadcast, and routers normally do not forward local broadcasts. When the DHCP server lives in another network, a DHCP relay listens locally and carries the request toward the remote server. This lets one server configure many networks without joining their broadcast domains together.&lt;/p&gt;

&lt;p&gt;If DHCP is absent, nothing fundamental breaks. We can configure the IP address, prefix, gateway and DNS server manually, provided that the values are correct and the chosen address does not conflict with another host.&lt;/p&gt;

&lt;p&gt;Small networks often place DHCP, routing, NAT, firewalling, DNS forwarding and WiFi inside the same plastic box near the wall. Calling that entire creature a router is convenient but slightly misleading. Router is one role performed by the device, just as a castle can be a home, a fortress, a court, a prison and a very expensive heating problem at the same time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Following one real packet
&lt;/h2&gt;

&lt;p&gt;Host A lives at &lt;code&gt;192.168.1.23/24&lt;/code&gt;, and somewhere beyond several networks Host Z lives at &lt;code&gt;10.20.0.42/16&lt;/code&gt;. Host A's route lookup selects &lt;code&gt;192.168.1.1&lt;/code&gt; as the gateway, and ARP gives it the gateway interface's MAC address.&lt;/p&gt;

&lt;p&gt;The word &lt;em&gt;interface&lt;/em&gt; matters here. A router connects at least two networks, so it does not have one universal IP address or one universal MAC address. Each Ethernet interface belongs to a particular local network and has its own MAC address, while it will usually have an IP address in that network as well.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fosrxlydh2yz0hir8xwns.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fosrxlydh2yz0hir8xwns.jpg" alt="Figure 6" width="799" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 6. A router's interfaces belong to different local networks. The incoming frame ends at Interface A, while a new local frame leaves through Interface B with that interface's identity.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Hosts on &lt;code&gt;192.168.1.0/24&lt;/code&gt; use &lt;code&gt;192.168.1.1&lt;/code&gt; as their gateway because that address belongs to the router interface standing inside their own network. The interface on the other side belongs to another network and has different local neighbors.&lt;/p&gt;

&lt;p&gt;Host A creates the first frame:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Ethernet destination: MAC of 192.168.1.1
IPv4 destination:     10.20.0.42
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The router removes the Ethernet frame, reads the IPv4 destination, performs its own route lookup and chooses the next hop. If the outgoing link is also Ethernet, the router resolves that next hop's MAC address and creates a completely new frame. The next router repeats the same process.&lt;/p&gt;

&lt;p&gt;Eventually a router finds that &lt;code&gt;10.20.0.0/16&lt;/code&gt; is directly connected. On the final Ethernet network, it uses ARP to learn Host Z's MAC address and creates the last local frame.&lt;/p&gt;

&lt;p&gt;The IPv4 source and destination normally remain the same in this simple routed topology. NAT can deliberately rewrite addresses, but NAT is another official with another stamp and is not required to explain routing.&lt;/p&gt;

&lt;p&gt;The packet is not completely untouched, however. Every router decreases the IPv4 Time to Live, usually called TTL, by at least one. Because TTL is part of the IPv4 header, the router also updates the IPv4 header checksum.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F81ugl2kqkhywoe5fpvh7.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F81ugl2kqkhywoe5fpvh7.jpg" alt="Figure 7" width="799" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 7. The IPv4 source and destination stay end to end, each local frame is rebuilt for one link, and TTL decreases as routers forward the packet.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;TTL exists because routing information can be wrong. If two routers keep sending a packet back and forth, the packet must not wander between them until the end of history. When TTL reaches zero, the router discards it and normally sends an ICMP Time Exceeded message back toward the source.&lt;/p&gt;

&lt;p&gt;Ethernet is not mandatory on every link. Another link technology may use different framing and may have no MAC addresses or ARP at all. What survives is the larger mechanism: the router removes the old link-layer envelope, keeps the IP destination, chooses the next route and creates whatever local delivery the outgoing link requires.&lt;/p&gt;

&lt;p&gt;Layer 3 does not bypass Layer 2. It uses one local link, then another one, then another one. A global network is made from local networks that repeatedly agree to pass the packet on.&lt;/p&gt;
&lt;h2&gt;
  
  
  Routers do not know the whole journey
&lt;/h2&gt;

&lt;p&gt;A router does not normally calculate the complete road from Host A to Host Z. It needs to answer only one question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Where should I send this packet next?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is why routing tables usually describe networks rather than every individual host. One rule can say that the whole &lt;code&gt;10.20.0.0/16&lt;/code&gt; network is reachable through Router B.&lt;/p&gt;

&lt;p&gt;Sometimes several routes match the same destination:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="m"&gt;10&lt;/span&gt;.&lt;span class="m"&gt;0&lt;/span&gt;.&lt;span class="m"&gt;0&lt;/span&gt;.&lt;span class="m"&gt;0&lt;/span&gt;/&lt;span class="m"&gt;8&lt;/span&gt;       &lt;span class="n"&gt;via&lt;/span&gt; &lt;span class="n"&gt;Router&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt;
&lt;span class="m"&gt;10&lt;/span&gt;.&lt;span class="m"&gt;20&lt;/span&gt;.&lt;span class="m"&gt;0&lt;/span&gt;.&lt;span class="m"&gt;0&lt;/span&gt;/&lt;span class="m"&gt;16&lt;/span&gt;     &lt;span class="n"&gt;via&lt;/span&gt; &lt;span class="n"&gt;Router&lt;/span&gt; &lt;span class="n"&gt;B&lt;/span&gt;
&lt;span class="m"&gt;10&lt;/span&gt;.&lt;span class="m"&gt;20&lt;/span&gt;.&lt;span class="m"&gt;5&lt;/span&gt;.&lt;span class="m"&gt;0&lt;/span&gt;/&lt;span class="m"&gt;24&lt;/span&gt;     &lt;span class="n"&gt;via&lt;/span&gt; &lt;span class="n"&gt;Router&lt;/span&gt; &lt;span class="n"&gt;C&lt;/span&gt;
&lt;span class="n"&gt;default&lt;/span&gt;          &lt;span class="n"&gt;via&lt;/span&gt; &lt;span class="n"&gt;Router&lt;/span&gt; &lt;span class="n"&gt;D&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The address &lt;code&gt;10.20.5.17&lt;/code&gt; matches every line, including the default, but the &lt;code&gt;/24&lt;/code&gt; route wins because it has the longest matching prefix. A longer prefix describes a smaller and more specific group of addresses. The default route has a prefix length of zero, so it is the vaguest possible instruction: if nobody knows better, send the packet this way.&lt;/p&gt;

&lt;p&gt;For a small network, somebody can configure these routes by hand. In a much larger system, routers can exchange routes through protocols such as OSPF, IS-IS and BGP. Those protocols deserve their own story, but forwarding remains the same after the table has been built: read the destination IP, choose the most specific matching route, select the next hop, and perform one local delivery.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually happens when we type &lt;code&gt;ping&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;When we run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ping 8.8.8.8
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the operating system performs the route lookup, selects the next hop, resolves the necessary local address if the outgoing link requires it, and sends an IPv4 packet containing an ICMP Echo Request.&lt;/p&gt;

&lt;p&gt;If the remote host chooses to answer, it sends an ICMP Echo Reply back toward us. A successful reply means that the forward route worked, the return route worked, the necessary local deliveries succeeded, and the policies along the way allowed ICMP.&lt;/p&gt;

&lt;p&gt;A failed &lt;code&gt;ping&lt;/code&gt; therefore does not prove that the destination is dead. It proves only that the complete conversation did not return, which is a much less satisfying answer and a much more honest one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The mental model to keep
&lt;/h2&gt;

&lt;p&gt;We began with a local network that knew only local tower names. The second valley made those names ambiguous, so we added a wider address that named both the valley and the tower. Once a destination could be remote, we needed a boundary that distinguished our local world from everything beyond it. A remote packet needed a gateway, and the gateway needed a routing table that selected the next useful step.&lt;/p&gt;

&lt;p&gt;Every new idea appeared because the growing system made the previous model insufficient.&lt;/p&gt;

&lt;p&gt;The result can be carried in one sentence:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Routing keeps an end-to-end IP destination inside the packet while rebuilding the local delivery around it for every link.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A MAC address answers who should receive this frame here. An IP address answers where the packet is ultimately going. A prefix describes which addresses belong to a network. A route chooses the next hop, and ARP maps that next-hop IP address to a local Ethernet address when the current link needs one.&lt;/p&gt;

&lt;p&gt;None of this replaced the network from Part 1. We built above it and then reused it at every hop.&lt;/p&gt;

&lt;p&gt;Put those answers together and our valleys become a network of networks, which is rather inconveniently the same direction in which our rulers were taking their empire anyway.&lt;/p&gt;

&lt;h2&gt;
  
  
  One machine is still a black box
&lt;/h2&gt;

&lt;p&gt;There is one character in this story that we have treated with suspicious casualness: the computer itself.&lt;/p&gt;

&lt;p&gt;Throughout both parts I have written that the computer checks a route, stores a neighbor, owns a MAC address and sends a frame, but what inside the machine actually does those things? Where does a network interface begin and the operating system end? What changes when one computer has several interfaces, and what happens when some of those interfaces are not physical at all?&lt;/p&gt;

&lt;p&gt;Linux can create virtual interfaces, connect them with virtual Ethernet pairs, place them in bridges, and split the network into namespaces that behave like separate hosts. One physical machine can contain several isolated valleys and the roads between them.&lt;/p&gt;

&lt;p&gt;Which means that, instead of invading another kingdom for the next experiment, we can build almost the entire empire inside one Linux machine.&lt;/p&gt;

&lt;p&gt;That is where we go next.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>infrastructure</category>
      <category>networking</category>
      <category>systemdesign</category>
    </item>
    <item>
      <title>Networking in the Power Grid for Software Developers Part 1: Rebuilding a Local Network with Medieval Signal Towers</title>
      <dc:creator>Dmytro Huz</dc:creator>
      <pubDate>Sat, 29 Aug 2026 19:58:28 +0000</pubDate>
      <link>https://dev.to/dmytro_huz/networking-in-the-power-grid-for-software-developers-part-1-rebuilding-a-local-network-with-3n21</link>
      <guid>https://dev.to/dmytro_huz/networking-in-the-power-grid-for-software-developers-part-1-rebuilding-a-local-network-with-3n21</guid>
      <description>&lt;p&gt;Recently I started working on a &lt;a href="https://www.dmytrohuz.com/p/networking-in-the-power-grid-for" rel="noopener noreferrer"&gt;networking series for software developers who enter the electrical-grid&lt;/a&gt; world and suddenly discover that the grid speaks in packets, interfaces, switches, routes, protocols and a suspicious number of acronyms.&lt;/p&gt;

&lt;p&gt;Before going anywhere near IEC 61850, utility WANs or substation networks, I wanted to understand the ordinary networking machinery itself. Not only what a switch or ARP does, but why these things had to appear in the first place.&lt;/p&gt;

&lt;p&gt;I could start with the OSI model. I have seen that diagram enough times in my life. The problem is that a finished diagram is very good at telling us where something belongs and surprisingly bad at showing why it exists.&lt;/p&gt;

&lt;p&gt;So I had a more ridiculous idea.&lt;/p&gt;

&lt;p&gt;Imagine that I am somewhere in medieval Europe. No copper cable, no fiber, no radio and definitely no Ethernet. I have stone towers on hills, and the only useful property I can rely on is visibility. If one tower can see another, I can make light visible or hide it.&lt;/p&gt;

&lt;p&gt;Could I start from that and slowly rebuild a network?&lt;/p&gt;

&lt;p&gt;This is not a historical reconstruction. I am not claiming that medieval Europe was one good standards committee away from IEEE 802.3. The towers are only a constraint. I want to begin with the smallest possible communication system, push it until it becomes uncomfortable, fix the new problem, and then compare the thing we invented with the machinery inside a real network.&lt;/p&gt;

&lt;p&gt;That approach is much closer to how I like to understand systems. The finished technology often hides the reasons for its own shape. Broken and incomplete versions expose them.&lt;/p&gt;

&lt;p&gt;In this post we will discuss the local part of the network. We will begin with physical signalling between two towers, then add more participants and see why frames, MAC addresses, switching, flooding, broadcast and ARP appear. Near the end we will come back to Linux and look at the same machinery on a real machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two towers and one bit
&lt;/h2&gt;

&lt;p&gt;I want to begin before packets, addresses and protocols exist at all.&lt;/p&gt;

&lt;p&gt;Two towers stand on neighboring hills. Tower A can see Tower B directly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2v2z7odfvtf9bdrce0nw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2v2z7odfvtf9bdrce0nw.png" alt="Diagram 1" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Diagram 1. Our entire network consists of two towers with direct visibility.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Tower A wants to send one piece of information to Tower B. The bridge is closed, for example.&lt;/p&gt;

&lt;p&gt;But even that sentence is already too advanced for our network. We do not yet have a way to move a single bit between the towers.&lt;/p&gt;

&lt;p&gt;The simplest thing I can invent is a lantern with a shutter. During a fixed interval the light is either visible or hidden.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;light visible = 1
light hidden  = 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So a guard can produce something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1 0 1 1 0 0 1 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the other tower can observe it.&lt;/p&gt;

&lt;p&gt;This looks almost stupidly primitive, but it already contains the first real networking problem. An abstract &lt;code&gt;1&lt;/code&gt; inside one system has to become something physical, travel through a medium, and be recovered by the other system.&lt;/p&gt;

&lt;p&gt;In our case the medium is light through air. In Ethernet it may be an electrical or optical signal. Wireless uses radio. Real physical layers do not literally push abstract bits through a cable one after another. They deal with symbols, encoding, timing, synchronization, noise, attenuation, clock recovery and many other details that become a rabbit hole very quickly.&lt;/p&gt;

&lt;p&gt;Even the lantern version immediately asks awkward questions. How long does one interval last? How does Tower B know where the sequence begins? What happens in fog? If the light stays visible for three intervals, how does the receiver know that it saw &lt;code&gt;111&lt;/code&gt; rather than one long &lt;code&gt;1&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;I am going to stop before our medieval guards have to invent signal processing.&lt;/p&gt;

&lt;p&gt;The useful part for this article is that we have reconstructed the job of Layer 1: make information physical enough that it can cross one link and be recovered at the other side.&lt;/p&gt;

&lt;p&gt;That works beautifully while our entire civilization consists of two towers.&lt;/p&gt;

&lt;p&gt;Unfortunately, I want a network.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding the rest of the valley
&lt;/h2&gt;

&lt;p&gt;So I add more towers.&lt;/p&gt;

&lt;p&gt;Some can see each other directly. Some need a relay point between them. For the moment, imagine one local communication region where a relay tower has direct light paths toward several participants.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5ql3ak5w28nx3ddie81k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5ql3ak5w28nx3ddie81k.png" alt="Diagram 2" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Diagram 2. Several towers now share one local communication system through a relay point.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The light can move bits across each individual path, but the moment several towers share the system, a stream like this becomes annoyingly ambiguous:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;10110010...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Who is it for? Where does one message end and the next one begin? If the relay receives it, which path should it use? And if I later want to carry different kinds of data, how does the receiver know what is inside?&lt;/p&gt;

&lt;p&gt;This is where the simple Layer 1 picture starts to break. The physical signal still matters, but I need structure above it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Give the towers local addresses
&lt;/h2&gt;

&lt;p&gt;I will start with the most obvious missing piece and give every tower a local identifier.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Tower A = 0A
Tower B = 0B
Tower C = 0C
Tower D = 0D
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is nothing magical about these values. My tiny kingdom does not need a 48-bit address space yet.&lt;/p&gt;

&lt;p&gt;Now instead of sending naked bits, Tower A can send a structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TO:   0C
FROM: 0A
TYPE: MESSAGE
DATA: THE BRIDGE IS CLOSED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The relay no longer has to guess what the bits mean. There is a destination, a source, a description of what the payload contains, and the payload itself.&lt;/p&gt;

&lt;p&gt;That structure is already suspiciously close to an Ethernet frame.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Our tower frame&lt;/th&gt;
&lt;th&gt;Ethernet equivalent&lt;/th&gt;
&lt;th&gt;What it is doing&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;TO&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Destination MAC address&lt;/td&gt;
&lt;td&gt;Identifies the local recipient of the frame&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;FROM&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Source MAC address&lt;/td&gt;
&lt;td&gt;Identifies the local sender of the frame&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;TYPE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;EtherType&lt;/td&gt;
&lt;td&gt;Tells the receiver what kind of payload is carried&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;DATA&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Payload&lt;/td&gt;
&lt;td&gt;Carries the higher-level data&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Table 1. The fields I needed for the tower network have direct relatives in an Ethernet frame.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A real Ethernet frame has more machinery around this, and the signal on the wire has additional synchronization and encoding details. Our invented text header is obviously not its real wire format. Those details solve different problems, so I will leave them outside this model for now.&lt;/p&gt;

&lt;p&gt;The important thing is that &lt;code&gt;0A&lt;/code&gt;, &lt;code&gt;0B&lt;/code&gt; and the rest are local link-layer identities. They play the role of MAC addresses. They do not tell us how to reach a tower somewhere on the other side of Europe. They let this local network move a frame toward the right participant.&lt;/p&gt;

&lt;p&gt;This is roughly where Layer 2 enters the picture. We have stopped dealing only with signals and started dealing with frames and local delivery.&lt;/p&gt;

&lt;h2&gt;
  
  
  The relay starts remembering
&lt;/h2&gt;

&lt;p&gt;The first version of my relay can be extremely lazy. Every time it receives a transmission, it can repeat the signal toward every other light path. At the frame level, the result is that every frame becomes visible everywhere.&lt;/p&gt;

&lt;p&gt;This is roughly the world of an old Ethernet hub. A real hub operates at Layer 1 and repeats signals rather than parsing Ethernet frames, which is one place where the tower analogy should not be pushed too literally.&lt;/p&gt;

&lt;p&gt;But the relay is sitting in a very useful position. When a frame arrives from the western path and says:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM: 0A
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;it has learned something for free: whatever &lt;code&gt;0A&lt;/code&gt; is, the western path is how I just heard from it.&lt;/p&gt;

&lt;p&gt;So the relay can remember that fact.&lt;/p&gt;

&lt;p&gt;After some traffic its memory may look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0A → western path
0B → northern path
0C → eastern path
0D → southern path
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now a frame addressed to &lt;code&gt;0C&lt;/code&gt; does not have to be repeated everywhere. The relay can send it only toward the eastern path.&lt;/p&gt;

&lt;p&gt;This is the part where our relay has quietly become an Ethernet switch.&lt;/p&gt;

&lt;p&gt;A switch builds a forwarding table by looking at the source MAC address of frames arriving on its ports. The source address is evidence. If a frame from &lt;code&gt;0C&lt;/code&gt; arrived through port 3, then &lt;code&gt;0C&lt;/code&gt; is currently reachable through port 3. Real switches also age these entries because networks change, cables move and machines disappear.&lt;/p&gt;

&lt;p&gt;The destination MAC is used for the next decision. If the switch already knows where that destination lives, it forwards the frame through the corresponding port.&lt;/p&gt;

&lt;h3&gt;
  
  
  The destination that has never spoken
&lt;/h3&gt;

&lt;p&gt;This was the first place where I caught myself giving the relay knowledge it had never earned.&lt;/p&gt;

&lt;p&gt;Suppose its table contains only:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0A → western path
0B → northern path
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then a frame arrives from the west:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM: 0A
TO:   0C
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The relay can refresh &lt;code&gt;0A → western path&lt;/code&gt;, because it has just observed that fact. But the destination field tells it absolutely nothing about where &lt;code&gt;0C&lt;/code&gt; is located. &lt;code&gt;TO: 0C&lt;/code&gt; only describes what the sender wants.&lt;/p&gt;

&lt;p&gt;So the relay has one honest option: try all the other paths.&lt;/p&gt;

&lt;p&gt;It retransmits the frame everywhere except back through the path it arrived on. Every participant may see it, but only &lt;code&gt;0C&lt;/code&gt; should accept a unicast frame addressed to &lt;code&gt;0C&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Ethernet calls this unknown unicast flooding.&lt;/p&gt;

&lt;p&gt;If Tower C later sends anything:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM: 0C
TO:   0A
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;then the relay finally gets real evidence. The frame came from the east, so it can learn:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0C → eastern path
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Future frames for &lt;code&gt;0C&lt;/code&gt; can go directly there.&lt;/p&gt;

&lt;p&gt;There is a subtle point here that I want to keep separate from ARP. Unknown unicast flooding means the sender already put a destination MAC address into the Ethernet frame, but the switch does not know which port leads to that MAC. ARP solves a different problem: the sending host may not know the destination MAC address at all.&lt;/p&gt;

&lt;p&gt;In a normal fresh IPv4 conversation, ARP often causes the destination host to reply before the first IP unicast frame is sent. That reply also gives the switch a chance to learn the destination MAC. So you may not actually observe unknown unicast flooding every time you contact a new local IP address.&lt;/p&gt;

&lt;p&gt;The behavior still matters. Imagine that Tower A already knows &lt;code&gt;0C&lt;/code&gt;, perhaps from an existing neighbour-cache entry, while the switch's own forwarding entry for &lt;code&gt;0C&lt;/code&gt; has expired. Then A can send a perfectly valid unicast frame to &lt;code&gt;0C&lt;/code&gt;, and the switch may still have to flood it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frcsqv2dxnxwfxo37d9tk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frcsqv2dxnxwfxo37d9tk.png" alt="Diagram 3" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Diagram 3. The relay tower has become a Layer 2 switch by learning where local addresses are reachable.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The switch now has a useful memory, but Tower A can still be stuck for a completely different reason.&lt;/p&gt;
&lt;h2&gt;
  
  
  I know the IP address. I still cannot send the frame
&lt;/h2&gt;

&lt;p&gt;Suppose an application on Tower A wants to send data to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;192.168.1.20
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let us say that this address belongs to Tower C.&lt;/p&gt;

&lt;p&gt;For the moment I will also grant Tower A one piece of knowledge: it knows that &lt;code&gt;192.168.1.20&lt;/code&gt; is on the same local network. How the host makes that decision belongs to IP addressing, subnet masks and routing tables, which is exactly the problem I want to pick up in the next part.&lt;/p&gt;

&lt;p&gt;Tower A can create an IP packet for &lt;code&gt;192.168.1.20&lt;/code&gt;, but our local medium still does not forward an ordinary Ethernet frame by looking at that IP address. The Ethernet frame needs a destination MAC address.&lt;/p&gt;

&lt;p&gt;And Tower A does not know it.&lt;/p&gt;

&lt;p&gt;This is different from the problem we just had inside the switch.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Missing knowledge&lt;/th&gt;
&lt;th&gt;Who is missing it?&lt;/th&gt;
&lt;th&gt;Mechanism&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;MAC → switch port&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The switch&lt;/td&gt;
&lt;td&gt;Source-MAC learning, with unknown unicast flooding when the destination is unknown&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;IPv4 address → MAC&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The sending host&lt;/td&gt;
&lt;td&gt;ARP&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I like this distinction because both failures can sound like "I do not know where C is," while they live in different machines and are solved by different mechanisms.&lt;/p&gt;

&lt;p&gt;ARP, the Address Resolution Protocol, exists to solve the second one for IPv4.&lt;/p&gt;

&lt;h2&gt;
  
  
  A local shout
&lt;/h2&gt;

&lt;p&gt;There is a bootstrapping problem, though. If Tower A does not know Tower C's MAC address, it cannot send Tower C a normal unicast Ethernet frame asking for it.&lt;/p&gt;

&lt;p&gt;So I need one more local address in the tower world:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;EVERYONE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A frame sent to &lt;code&gt;EVERYONE&lt;/code&gt; is copied toward every participant in this local communication region.&lt;/p&gt;

&lt;p&gt;Ethernet already has exactly such a destination:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ff:ff:ff:ff:ff:ff
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the Ethernet broadcast MAC address.&lt;/p&gt;

&lt;p&gt;Tower A can now send something equivalent to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Who has 192.168.1.20?
Tell 192.168.1.10.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Ethernet destination is the broadcast address, so the switch floods the frame through the local broadcast domain. The switch does not need to understand the IP address in the question. In the ordinary forwarding model it is still doing a Layer 2 job: it sees a broadcast destination MAC and forwards accordingly.&lt;/p&gt;

&lt;p&gt;Inside that Ethernet frame is an ARP message. ARP is not an IP packet, and the switch does not use ARP to decide where ordinary unicast Ethernet frames should go. ARP is a separate protocol carried directly inside Ethernet, identified by EtherType &lt;code&gt;0x0806&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Every IPv4 host in the local broadcast domain may receive the request. Most of them inspect it, notice that &lt;code&gt;192.168.1.20&lt;/code&gt; is not theirs, and move on with their lives.&lt;/p&gt;

&lt;p&gt;Tower C recognizes its own IP address and replies with its MAC address:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;192.168.1.20 is at 02:00:00:00:00:0c
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The reply can be sent as a unicast Ethernet frame back to Tower A because the ARP request already contained A's addresses.&lt;/p&gt;

&lt;p&gt;That reply does two useful things in two different places. Tower A can store:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;192.168.1.20 → 02:00:00:00:00:0c
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;in its neighbour cache, while the switch sees a frame arriving from Tower C and can learn &lt;code&gt;02:00:00:00:00:0c → eastern port&lt;/code&gt; from the source MAC address.&lt;/p&gt;

&lt;p&gt;Nobody had to teach the switch about &lt;code&gt;192.168.1.20&lt;/code&gt;. It still does not need that information for normal Layer 2 forwarding.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flqvvu7mk2hnjiakxeloi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flqvvu7mk2hnjiakxeloi.png" alt="Diagram 4" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Diagram 4. Broadcast lets ARP discover the local MAC address associated with an IPv4 address.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This is the part of the experiment I find especially satisfying because we now have two small pieces of memory that look similar on paper but belong to completely different parts of the system.&lt;/p&gt;
&lt;h2&gt;
  
  
  Two different maps of the same local network
&lt;/h2&gt;

&lt;p&gt;Tower A may remember:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;192.168.1.20 → 02:00:00:00:00:0c
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is host knowledge. On Linux it lives in the neighbour table. For IPv4, ARP is the mechanism that normally creates and refreshes this mapping.&lt;/p&gt;

&lt;p&gt;The switch may remember:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;02:00:00:00:00:0c → eastern port
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is switch knowledge. It was learned from the source MAC address of a frame that arrived through the eastern port.&lt;/p&gt;

&lt;p&gt;The two tables answer different questions:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Machine&lt;/th&gt;
&lt;th&gt;What it knows&lt;/th&gt;
&lt;th&gt;Why it needs it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Host&lt;/td&gt;
&lt;td&gt;&lt;code&gt;IPv4 address → MAC address&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;To build an Ethernet frame for a local IPv4 destination&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Switch&lt;/td&gt;
&lt;td&gt;&lt;code&gt;MAC address → port&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;To decide where to forward that Ethernet frame&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This also makes the MAC and IP distinction less mystical.&lt;/p&gt;

&lt;p&gt;A MAC address is useful for delivery on the current Layer 2 network. An IP address is the logical address of the packet's destination in the larger Layer 3 system.&lt;/p&gt;

&lt;p&gt;When A and C are sitting in the same valley, both addresses appear to identify the same machine, so having two of them can feel redundant. The redundancy is mostly an illusion created by the small example. Once the packet has to cross a router, the reason for both becomes much clearer.&lt;/p&gt;

&lt;p&gt;For a local destination, A may build something conceptually like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Ethernet:
  dst MAC = 02:00:00:00:00:0c
  src MAC = 02:00:00:00:00:0a
  type    = IPv4

IPv4 packet inside:
  dst IP  = 192.168.1.20
  src IP  = 192.168.1.10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The switch cares about the Ethernet destination MAC when forwarding the frame. Tower C eventually receives the frame, sees that the payload is IPv4, and passes the enclosed IP packet upward.&lt;/p&gt;

&lt;p&gt;The complete first exchange is therefore not one magic lookup. It is several small mechanisms cooperating:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application wants to send to 192.168.1.20
            ↓
Host decides 192.168.1.20 is local
            ↓
Neighbour cache has no MAC for 192.168.1.20
            ↓
Host sends an ARP request in an Ethernet broadcast frame
            ↓
Switch learns A's source MAC and floods the broadcast
            ↓
C recognizes its IP address and sends an ARP reply
            ↓
Switch learns C's source MAC from the reply
            ↓
A stores 192.168.1.20 → C's MAC in its neighbour cache
            ↓
A puts the IP packet into a unicast Ethernet frame for C's MAC
            ↓
Switch uses C's MAC to select the correct port
            ↓
C receives the frame and extracts the IP packet
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a simplified path, but it is finally a simplification I can reason with. Each table has an owner. Each address has a purpose. The switch never needs to resolve &lt;code&gt;192.168.1.20&lt;/code&gt;, and ARP never tells the switch which destination port to use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Back on a real Linux machine
&lt;/h2&gt;

&lt;p&gt;After spending enough time with imaginary towers, I wanted to see whether the same structure is visible without the analogy.&lt;/p&gt;

&lt;p&gt;Linux exposes the host-side neighbour table with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ip neigh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For an IPv4 neighbour, an entry can look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;192.168.1.20 dev eth0 lladdr 02:00:00:00:00:0c REACHABLE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There it is: the same &lt;code&gt;IPv4 address → MAC address&lt;/code&gt; mapping that Tower A had to learn. The Linux neighbour table is more general than ARP and also contains IPv6 neighbour information, but for IPv4 on Ethernet, ARP is the mechanism we care about here.&lt;/p&gt;

&lt;p&gt;The exchange itself is even nicer because we can watch it happen:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;tcpdump &lt;span class="nt"&gt;-i&lt;/span&gt; eth0 &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; arp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;eth0&lt;/code&gt; is not your actual interface name, which is very likely on a modern Linux machine because apparently &lt;code&gt;eth0&lt;/code&gt; was too easy, replace it with the real interface.&lt;/p&gt;

&lt;p&gt;With no cached neighbour entry, contacting &lt;code&gt;192.168.1.20&lt;/code&gt; should first produce an ARP request sent to the Ethernet broadcast address. Then the owner of that IP replies with its MAC address. After the mapping exists, the host can send normal unicast Ethernet frames.&lt;/p&gt;

&lt;p&gt;If the neighbour entry already exists, the absence of ARP traffic is not evidence that the mechanism disappeared. Linux is simply using what it already knows.&lt;/p&gt;

&lt;p&gt;🔎&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try it yourself:&lt;/strong&gt; run &lt;code&gt;ip neigh&lt;/code&gt;, start &lt;code&gt;sudo tcpdump -i  -n -e arp&lt;/code&gt;, and then ping another machine on the same local network. If a cached entry prevents the ARP exchange from appearing, remove only that entry with &lt;code&gt;sudo ip neigh del  dev&lt;/code&gt; and repeat the experiment. Use a machine and network you control.&lt;/p&gt;

&lt;p&gt;I will stop the Linux part here. Later in Act I I want to open one host properly and look at interfaces, bridges, network namespaces, virtual Ethernet devices and the rest of the machinery Linux piles underneath an innocent socket. Here I only wanted to verify that our medieval invention leaves fingerprints on a real machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Then I add another valley
&lt;/h2&gt;

&lt;p&gt;So far everybody belongs to one local communication region. Broadcast can reach the whole group, and one Layer 2 system is enough.&lt;/p&gt;

&lt;p&gt;Then I put another valley behind the mountains.&lt;/p&gt;

&lt;p&gt;It has its own towers, its own local relay, its own broadcasts and its own local MAC addresses. Tower A cannot simply shout an ARP request across the mountains and expect all of Europe to participate.&lt;/p&gt;

&lt;p&gt;Still, Tower A needs to send a message there.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fojg4th5446rnbm24ccxv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fojg4th5446rnbm24ccxv.png" alt="Diagram 5" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Diagram 5. The local-network model stops being sufficient when the destination belongs to another network.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This is where my local-network model finally runs out of road.&lt;/p&gt;

&lt;p&gt;I could try to connect both valleys into one gigantic Layer 2 domain and let broadcasts spread farther and farther. Then I could keep adding valleys until one ARP request lights half the continent. Apart from being a rather beautiful medieval spectacle, it would also turn every local broadcast into everybody else's problem.&lt;/p&gt;

&lt;p&gt;The cleaner solution is to let each valley remain a local network and introduce a machine that knows how to move packets between networks.&lt;/p&gt;

&lt;p&gt;That machine is a router.&lt;/p&gt;

&lt;p&gt;And here the two-address idea starts paying rent.&lt;/p&gt;

&lt;p&gt;If Tower A wants to send an IP packet to a host in the remote valley, the packet can keep the remote host as its IP destination. But the Ethernet frame used in A's local valley is not addressed to that distant machine. It is addressed to the local router, the next machine that can move the packet closer to where it belongs.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;IP destination:       remote host in another valley
Ethernet destination: local router
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The router receives the local frame, removes that Layer 2 envelope, examines the IP packet, chooses the next route, and places the packet into a new Layer 2 frame appropriate for the next link.&lt;/p&gt;

&lt;p&gt;This is the point where the medieval analogy also needs a new piece. A relay inside one valley was enough while everybody shared one local addressing and broadcast system. Communication between valleys requires a gateway that understands something larger than local tower addresses.&lt;/p&gt;

&lt;p&gt;That larger system is what I want to rebuild in Part 2: IP addresses, subnet masks, the decision that a destination is local or remote, default gateways, routing tables and routers.&lt;/p&gt;

&lt;p&gt;The towers got us surprisingly far, but the mountains finally forced us into Layer 3.&lt;/p&gt;

&lt;p&gt;Which we cover in the next part!&lt;/p&gt;

&lt;p&gt;Stay tuned!&lt;/p&gt;




&lt;h2&gt;
  
  
  References and further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;RFC 826: An Ethernet Address Resolution Protocol&lt;/li&gt;
&lt;li&gt;IEEE 802.3 Ethernet Working Group&lt;/li&gt;
&lt;li&gt;Linux ip-neighbour manual&lt;/li&gt;
&lt;li&gt;Linux kernel TUN/TAP documentation, useful later when we rebuild networking machinery inside one Linux host&lt;/li&gt;
&lt;li&gt;A Developer's Map of the European Power Grid, the wider series context for software developers entering the energy domain&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>networking</category>
      <category>learning</category>
      <category>energy</category>
      <category>programming</category>
    </item>
    <item>
      <title>A Developer’s Map of the European Power Grid</title>
      <dc:creator>Dmytro Huz</dc:creator>
      <pubDate>Tue, 04 Aug 2026 12:18:26 +0000</pubDate>
      <link>https://dev.to/aws-builders/a-developers-map-of-the-european-power-grid-4ij1</link>
      <guid>https://dev.to/aws-builders/a-developers-map-of-the-european-power-grid-4ij1</guid>
      <description>&lt;p&gt;A year ago, if you had asked me how the European power grid worked, I would have confidently answered with things that turned out to be mostly wrong.&lt;/p&gt;

&lt;p&gt;I thought electricity simply flowed from power plants to consumers.&lt;/p&gt;

&lt;p&gt;I thought the grid was mostly cables, transformers, and substations.&lt;/p&gt;

&lt;p&gt;I thought software was just another supporting tool somewhere in the background.&lt;/p&gt;

&lt;p&gt;Then I joined the energy industry.&lt;/p&gt;

&lt;p&gt;And I discovered that I had stepped into one of the most fascinating software ecosystems I have ever seen.&lt;/p&gt;

&lt;p&gt;The deeper I went, the stranger it became.&lt;/p&gt;

&lt;p&gt;Electricity doesn’t behave the way I imagined. The grid isn’t a giant pipeline. Keeping millions of homes synchronized across an entire continent requires markets, regulations, forecasting, optimization, balancing algorithms, communication protocols, distributed control systems, and an astonishing amount of software quietly working behind the scenes.&lt;/p&gt;

&lt;p&gt;It felt less like learning another technology and more like discovering another continent.&lt;/p&gt;

&lt;p&gt;The frustrating part was that almost everything I found was written either for electrical engineers or for energy specialists who already understood the vocabulary.&lt;/p&gt;

&lt;p&gt;As a software engineer, I wasn’t looking for equations or protection settings.&lt;/p&gt;

&lt;p&gt;I wanted a map.&lt;/p&gt;

&lt;p&gt;I wanted to understand who the different players were, why they existed, what problems they solved, and where software fit into the whole picture.&lt;/p&gt;

&lt;p&gt;I couldn’t find that map.&lt;/p&gt;

&lt;p&gt;So I decided to draw one myself.&lt;/p&gt;

&lt;p&gt;Over the last several months, I’ve been rebuilding the European power system from first principles, one topic at a time, trying to explain it the way I wish someone had explained it to me when I started.&lt;/p&gt;

&lt;p&gt;The result is A Developer’s Map of the European Power Grid.&lt;/p&gt;

&lt;p&gt;It isn’t a textbook, and it isn’t a complete guide to power engineering.&lt;/p&gt;

&lt;p&gt;It’s a collection of mental models for software engineers who suddenly find themselves working in energy and wondering how all these generators, substations, TSOs, markets, protocols, and regulations fit together.&lt;/p&gt;

&lt;p&gt;So far, the series includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Part 1: Electricity Is Not Water&lt;/li&gt;
&lt;li&gt;Part 2: The Grid Is Not a Pipeline&lt;/li&gt;
&lt;li&gt;Part 3: How Europe’s Power System Is Organised&lt;/li&gt;
&lt;li&gt;Part 4: Where Software Lives in the Power Grid&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each article builds one piece of the picture. By the end, my hope is that you’ll have the same mental map that took me months to build.&lt;/p&gt;

&lt;p&gt;If you’re already working in the industry, I’d genuinely appreciate your feedback. Every article has changed my own understanding, and I’m sure there are still blind spots.&lt;/p&gt;

&lt;p&gt;And if you’re just entering the energy world, I hope this series saves you some of the confusion I went through.&lt;/p&gt;

&lt;p&gt;The series:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Hub Article — A Developer’s Map of the European Power Grid: &lt;a href="https://www.dmytrohuz.com/p/a-developers-map-of-the-european" rel="noopener noreferrer"&gt;https://www.dmytrohuz.com/p/a-developers-map-of-the-european&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 1 — Electricity Is Not Water: &lt;a href="https://www.dmytrohuz.com/p/electricity-is-not-water-a-better" rel="noopener noreferrer"&gt;https://www.dmytrohuz.com/p/electricity-is-not-water-a-better&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 2 — The Grid Is Not a Pipeline: &lt;a href="https://www.dmytrohuz.com/p/the-grid-is-not-a-pipeline" rel="noopener noreferrer"&gt;https://www.dmytrohuz.com/p/the-grid-is-not-a-pipeline&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 3 — How Europe’s Power System Is Organised. Markets, TSOs, DSOs and Everyone in Between: &lt;a href="https://www.dmytrohuz.com/p/how-europes-power-system-is-organised" rel="noopener noreferrer"&gt;https://www.dmytrohuz.com/p/how-europes-power-system-is-organised&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 4 — Where Software Lives in the Power Grid: 
&lt;a href="https://www.dmytrohuz.com/p/where-software-lives-in-the-power" rel="noopener noreferrer"&gt;https://www.dmytrohuz.com/p/where-software-lives-in-the-power&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>learning</category>
      <category>programming</category>
      <category>europe</category>
    </item>
    <item>
      <title>Adding Homemade TLS to a Homemade Web Server</title>
      <dc:creator>Dmytro Huz</dc:creator>
      <pubDate>Sat, 04 Jul 2026 20:40:58 +0000</pubDate>
      <link>https://dev.to/dmytro_huz/adding-homemade-tls-to-a-homemade-web-server-1a6d</link>
      <guid>https://dev.to/dmytro_huz/adding-homemade-tls-to-a-homemade-web-server-1a6d</guid>
      <description>&lt;p&gt;&lt;strong&gt;Original series:&lt;/strong&gt; &lt;a href="https://dev.to/dmytro_huz/building-your-own-web-server-part-1-theory-and-foundations-3kgo"&gt;Building Your Own Web Server&lt;/a&gt; + &lt;a href="https://www.dmytrohuz.com/p/rebuilding-tls-from-scratch-my-complete" rel="noopener noreferrer"&gt;Rebuilding TLS from Scratch&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This article connects two earlier series. In the web-server series, I rebuilt the HTTP side from raw sockets up to routing and file serving. In the TLS series, I rebuilt the secure-channel side: key exchange, certificates, key derivation, and encrypted records.&lt;/p&gt;

&lt;p&gt;This piece is where the two meet: what has to change when a web server stops receiving plaintext HTTP bytes and starts receiving encrypted TCP bytes?&lt;/p&gt;




&lt;p&gt;I expected adding TLS to my web server to change almost everything.&lt;/p&gt;

&lt;p&gt;It did not.&lt;/p&gt;

&lt;p&gt;The HTTP parser stayed boring. The router stayed boring. The file-serving code stayed boring.&lt;/p&gt;

&lt;p&gt;The real change happened one layer below HTTP.&lt;/p&gt;

&lt;p&gt;I had two separate learning projects before this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a small web server built from scratch: sockets, HTTP parsing, &lt;code&gt;location&lt;/code&gt; matching, file serving, and eventually a single-threaded event-loop server;&lt;/li&gt;
&lt;li&gt;a small TLS-like secure channel built from scratch: ephemeral key exchange, certificate chains, server authentication, HKDF, AES-GCM records, and the difference between identity keys and session keys.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Putting them together led to one question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What changes in the web server when the bytes coming from TCP are encrypted?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The useful answer is smaller than I expected:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TCP socket
   ↓
TLS handshake / record layer
   ↓ decrypted plaintext bytes
HTTP parser / router / file serving
   ↓ plaintext HTTP response
TLS record protection
   ↓ encrypted bytes
TCP socket
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the whole architecture of the companion project.&lt;/p&gt;

&lt;p&gt;This is not production HTTPS. It does not implement the real TLS 1.3 wire format, and browsers will not connect to it with &lt;code&gt;https://localhost:8443&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It is a learning project. The goal is to make the integration boundary visible.&lt;/p&gt;

&lt;p&gt;Once that boundary becomes clear, HTTPS feels much less magical.&lt;/p&gt;




&lt;h2&gt;
  
  
  The plain web server had one assumption
&lt;/h2&gt;

&lt;p&gt;Before TLS, the web server had a simple mental model.&lt;/p&gt;

&lt;p&gt;A client connects over TCP. The server reads bytes from the socket. Those bytes are HTTP bytes. The server appends them to a per-connection buffer, tries to parse an HTTP request, matches the path, reads a file, and writes an HTTP response.&lt;/p&gt;

&lt;p&gt;The flow looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;accept TCP connection
   ↓
read bytes from socket
   ↓
append bytes to HTTP input buffer
   ↓
parse HTTP request
   ↓
match URL against location config
   ↓
read file from root
   ↓
write HTTP response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The buffer is important because TCP is a stream.&lt;/p&gt;

&lt;p&gt;One &lt;code&gt;recv()&lt;/code&gt; call can give you half a request. Or exactly one request. Or multiple requests joined together. The parser cannot assume that one socket read equals one HTTP request.&lt;/p&gt;

&lt;p&gt;In my server, the parser consumes complete HTTP messages from a buffer. If the request is incomplete, the server waits for more bytes. If there are extra bytes after a complete request, they stay in the buffer for the next parse.&lt;/p&gt;

&lt;p&gt;For plain HTTP, socket bytes and HTTP bytes are the same thing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;http_input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_handle_http_messages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That line works only because the bytes from the socket are already plaintext HTTP.&lt;/p&gt;

&lt;p&gt;TLS breaks that assumption.&lt;/p&gt;




&lt;h2&gt;
  
  
  After TLS, socket bytes are no longer HTTP bytes
&lt;/h2&gt;

&lt;p&gt;Once TLS is added, the TCP socket no longer carries a readable HTTP request.&lt;/p&gt;

&lt;p&gt;The client still wants to send this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="nf"&gt;GET&lt;/span&gt; &lt;span class="nn"&gt;/&lt;/span&gt; &lt;span class="k"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt;
&lt;span class="na"&gt;Host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;localhost&lt;/span&gt;
&lt;span class="na"&gt;Connection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;close&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But that request is not placed directly on the wire.&lt;/p&gt;

&lt;p&gt;It is wrapped inside TLS records. After the handshake, those records are encrypted and authenticated. The bytes arriving at the server socket are now protocol bytes for the TLS layer, not text for the HTTP parser.&lt;/p&gt;

&lt;p&gt;That is the shift:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;plain listener:
TCP bytes are HTTP bytes

TLS listener:
TCP bytes are TLS records
TLS records decrypt into HTTP bytes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the HTTP parser starts caring about certificates, keys, record sequence numbers, or AES-GCM tags, the abstraction has leaked.&lt;/p&gt;

&lt;p&gt;The HTTP parser should parse HTTP.&lt;/p&gt;

&lt;p&gt;The TLS layer should turn unsafe network bytes into authenticated plaintext bytes.&lt;/p&gt;




&lt;h2&gt;
  
  
  The integration point belongs below HTTP
&lt;/h2&gt;

&lt;p&gt;Production servers use the same high-level boundary.&lt;/p&gt;

&lt;p&gt;NGINX does not ask the HTTP parser to understand encrypted TLS records. A TLS implementation such as OpenSSL handles the connection security first. After that, the HTTP processing layer receives plaintext HTTP bytes.&lt;/p&gt;

&lt;p&gt;My project copies that architecture in a smaller educational form:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Production-ish mental model:
TCP → OpenSSL/TLS state → plaintext HTTP → NGINX HTTP processing

This project:
TCP → ToyTLSServerConnection → plaintext HTTP → HTTPParser / route matcher
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The analogy is architectural, not protocol-compatible.&lt;/p&gt;

&lt;p&gt;The project uses a small TLS-like protocol so the moving pieces are possible to read in one sitting. The handshake messages are simplified. The record framing is simplified. Many real TLS 1.3 details are missing on purpose.&lt;/p&gt;

&lt;p&gt;But the boundary is the point:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;socket bytes in
   ↓
connection layer decides whether they are plain HTTP or TLS records
   ↓
HTTP layer receives plaintext HTTP bytes either way
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That boundary lets the same HTTP code serve both ports.&lt;/p&gt;




&lt;h2&gt;
  
  
  The config makes the boundary visible
&lt;/h2&gt;

&lt;p&gt;The server uses an NGINX-style config because that was part of the original web-server project.&lt;/p&gt;

&lt;p&gt;A single server block can listen on a plain port and on a TLS port:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;http&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;8443&lt;/span&gt; &lt;span class="s"&gt;ssl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;localhost&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kn"&gt;ssl_certificate&lt;/span&gt; &lt;span class="nc"&gt;certs/server&lt;/span&gt;&lt;span class="s"&gt;_cert.pem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;ssl_certificate_key&lt;/span&gt; &lt;span class="nc"&gt;certs/server&lt;/span&gt;&lt;span class="s"&gt;_key.pem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;ssl_certificate_chain&lt;/span&gt; &lt;span class="nc"&gt;certs/intermediate&lt;/span&gt;&lt;span class="s"&gt;_cert.pem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kn"&gt;root&lt;/span&gt; &lt;span class="s"&gt;html&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;listen 8080;&lt;/code&gt; creates a normal HTTP listener.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;listen 8443 ssl;&lt;/code&gt; creates a listener that attaches TLS state to every accepted connection.&lt;/p&gt;

&lt;p&gt;Both listeners still share the same routing and file-serving code. The difference is what happens between &lt;code&gt;recv()&lt;/code&gt; and &lt;code&gt;HTTPParser.parse_message(...)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That is the part I wanted the project to expose.&lt;/p&gt;




&lt;h2&gt;
  
  
  The blocking TLS demo had to become a state machine
&lt;/h2&gt;

&lt;p&gt;The original TLS demo was linear and blocking.&lt;/p&gt;

&lt;p&gt;That is perfect for teaching the handshake:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;receive ClientHello
send ServerHello
send ServerAuth
receive encrypted request
send encrypted response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But the web server I wanted to integrate with was not a one-client-at-a-time demo. It was a selectors-based server.&lt;/p&gt;

&lt;p&gt;One event loop handles listening sockets and client sockets. A slow client should not freeze the whole process. That means the TLS implementation cannot sit inside a function waiting for the next record to arrive.&lt;/p&gt;

&lt;p&gt;So the TLS code had to become buffer-oriented.&lt;/p&gt;

&lt;p&gt;Instead of blocking on the socket, the TLS connection exposes three operations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;tls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;feed_wire_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;socket_bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;pending&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pop_pending_wire_data&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;plaintext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_plaintext&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;feed_wire_data(...)&lt;/code&gt; accepts whatever bytes TCP happened to deliver.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;pop_pending_wire_data()&lt;/code&gt; returns handshake bytes or encrypted application data that need to be sent back to the client.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;read_plaintext()&lt;/code&gt; returns decrypted HTTP bytes after the TLS layer has enough complete records to process.&lt;/p&gt;

&lt;p&gt;The web server does not need to know whether TCP split a TLS record into three reads or merged several records together. The TLS record buffer owns that problem.&lt;/p&gt;

&lt;p&gt;That small interface is the bridge between the two projects.&lt;/p&gt;




&lt;h2&gt;
  
  
  Plain and TLS reads now differ by one layer
&lt;/h2&gt;

&lt;p&gt;Here is the core read path in the integrated server.&lt;/p&gt;

&lt;p&gt;For plain HTTP, the socket bytes go directly into the HTTP input buffer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Plain path: socket bytes are already HTTP bytes.
&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;http_input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_handle_http_messages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the TLS listener, the same socket bytes first pass through the TLS connection state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# TLS path: socket bytes are handshake/application records, not HTTP.
&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;feed_wire_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_queue_output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pop_pending_wire_data&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

&lt;span class="n"&gt;plaintext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_plaintext&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;plaintext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;http_input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;plaintext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_handle_http_messages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The HTTP handler never receives encrypted bytes. It receives plaintext after the TLS layer has done its job.&lt;/p&gt;

&lt;p&gt;After that, request handling is the same:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;consumed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;HTTPParser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;http_input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;http_input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;consumed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;should_close&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;build_http_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;server_block&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;base_dir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_send_application_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response path mirrors the read path.&lt;/p&gt;

&lt;p&gt;The HTTP layer builds a normal plaintext HTTP response. If the connection is plain HTTP, those bytes are written directly to the socket. If the connection is TLS, the server wraps the response in an encrypted application record first.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tls&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;wire&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;protect_application_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;plaintext_response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;wire&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;plaintext_response&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the part I like most about the final design.&lt;/p&gt;

&lt;p&gt;The web server still feels like a web server.&lt;/p&gt;

&lt;p&gt;TLS becomes a connection concern, not an HTTP concern.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the toy TLS layer does
&lt;/h2&gt;

&lt;p&gt;The TLS-like layer in this project is intentionally small, but it keeps the important shape of the original TLS series.&lt;/p&gt;

&lt;p&gt;The handshake starts with ephemeral X25519 keys.&lt;/p&gt;

&lt;p&gt;The client sends a &lt;code&gt;ClientHello&lt;/code&gt; containing its ephemeral public key. The server generates its own ephemeral key for this connection and sends back a &lt;code&gt;ServerHello&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Then the server sends authentication material:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the server certificate,&lt;/li&gt;
&lt;li&gt;intermediate certificates,&lt;/li&gt;
&lt;li&gt;a signature over the two ephemeral public keys.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That signature is the simplified version of &lt;code&gt;CertificateVerify&lt;/code&gt;. It proves that the peer presenting the certificate also controls the matching private key for this specific key exchange.&lt;/p&gt;

&lt;p&gt;After that, both sides compute the same X25519 shared secret and derive directional AES-GCM keys through HKDF.&lt;/p&gt;

&lt;p&gt;Directional keys matter. The client-to-server key protects client records. The server-to-client key protects server records.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;client_write_key: records sent by the client
server_write_key: records sent by the server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Application data records also carry sequence numbers into the authenticated encryption. That lets the receiver detect tampering and sequence mismatches.&lt;/p&gt;

&lt;p&gt;Again, this is not real TLS 1.3. The real protocol has much more structure: transcript binding, alerts, Finished messages, SNI, ALPN, session resumption, real record headers, and many other details.&lt;/p&gt;

&lt;p&gt;For learning the web-server boundary, the smaller version is enough:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;handshake creates keys
records protect HTTP bytes
HTTP parser sees only decrypted bytes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The useful lesson was not only cryptography
&lt;/h2&gt;

&lt;p&gt;I started the project thinking the interesting part would be TLS.&lt;/p&gt;

&lt;p&gt;The cryptographic pieces do matter. It is useful to understand why ephemeral keys exist, why certificates are about identity rather than encryption, and why authenticated encryption needs sequence numbers.&lt;/p&gt;

&lt;p&gt;But the integration lesson was more general.&lt;/p&gt;

&lt;p&gt;When you add a lower-level protocol to an existing system, the hard part is often choosing the seam.&lt;/p&gt;

&lt;p&gt;If the seam is wrong, everything above it starts learning details it should not know.&lt;/p&gt;

&lt;p&gt;If the seam is right, most of the application code stays boring.&lt;/p&gt;

&lt;p&gt;In this case, the seam is between TCP and HTTP.&lt;/p&gt;

&lt;p&gt;The HTTP parser should not know about X25519. The route matcher should not know about certificates. The file-serving code should not care whether the client used the plain port or the TLS port.&lt;/p&gt;

&lt;p&gt;Those parts operate on HTTP.&lt;/p&gt;

&lt;p&gt;The connection layer is responsible for turning the outside world into HTTP bytes.&lt;/p&gt;

&lt;p&gt;That pattern shows up in a lot of infrastructure code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;network bytes become parsed messages,&lt;/li&gt;
&lt;li&gt;encrypted records become plaintext streams,&lt;/li&gt;
&lt;li&gt;unreliable external systems become retried operations,&lt;/li&gt;
&lt;li&gt;hardware signals become typed events,&lt;/li&gt;
&lt;li&gt;logs become structured facts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A design question I keep coming back to is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What should the next layer be allowed to assume?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For this project, the HTTP layer is allowed to assume one thing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;I receive plaintext HTTP bytes.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything below that belongs to the connection layer.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why build this if it is not real HTTPS?
&lt;/h2&gt;

&lt;p&gt;A fair objection is: if the project is not browser-compatible, why build it?&lt;/p&gt;

&lt;p&gt;For me, browser-compatible TLS is too large if the goal is to understand the integration boundary.&lt;/p&gt;

&lt;p&gt;If I used OpenSSL directly, the project would be more practical, but the interesting internals would disappear behind a library call.&lt;/p&gt;

&lt;p&gt;If I tried to implement full TLS 1.3, the project would become mostly about protocol correctness. That is valuable, but it would bury the web-server lesson under too much detail.&lt;/p&gt;

&lt;p&gt;The educational middle ground is a TLS-like secure channel with the same high-level shape:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;handshake
certificate verification
key derivation
encrypted records
application data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I can integrate that channel into the web server as if it were a real TLS stack.&lt;/p&gt;

&lt;p&gt;The result is small enough to read, but realistic enough to expose the architecture.&lt;/p&gt;

&lt;p&gt;That is the kind of learning project I like: simplified around one specific question.&lt;/p&gt;

&lt;p&gt;Here the question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What does a web server need from TLS?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;My answer after building it:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A secure byte stream that produces plaintext HTTP for the existing HTTP layer.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  How to run the companion project
&lt;/h2&gt;

&lt;p&gt;The repository is here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/DmytroHuzz/tls_web_server" rel="noopener noreferrer"&gt;https://github.com/DmytroHuzz/tls_web_server&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The visual walkthrough is here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dmytrohuzz.github.io/tls_web_server/" rel="noopener noreferrer"&gt;https://dmytrohuzz.github.io/tls_web_server/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From a fresh clone:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; venv .venv
&lt;span class="nb"&gt;source&lt;/span&gt; .venv/bin/activate
python3 &lt;span class="nt"&gt;-m&lt;/span&gt; pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--upgrade&lt;/span&gt; pip
python3 &lt;span class="nt"&gt;-m&lt;/span&gt; pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;".[dev]"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run the one-command demo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 scripts/demo.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It starts the server in a background thread, sends one plain HTTP request, then sends one HTTP request through the self-written TLS-like layer.&lt;/p&gt;

&lt;p&gt;Both paths should return:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="k"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt; &lt;span class="m"&gt;200&lt;/span&gt; &lt;span class="ne"&gt;OK&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are also separate &lt;code&gt;server_side.py&lt;/code&gt; and &lt;code&gt;client_side.py&lt;/code&gt; files if you want to see the workflow from each side, closer to the style of the original projects.&lt;/p&gt;

&lt;p&gt;The tests cover the integration points that are easy to get wrong:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;fragmented TLS handshake records,&lt;/li&gt;
&lt;li&gt;fragmented encrypted application records,&lt;/li&gt;
&lt;li&gt;wrong DNS name certificate rejection,&lt;/li&gt;
&lt;li&gt;tampered encrypted record rejection,&lt;/li&gt;
&lt;li&gt;sequence-number mismatch rejection,&lt;/li&gt;
&lt;li&gt;HTTP requests split across multiple encrypted TLS records,&lt;/li&gt;
&lt;li&gt;the same web server serving plain HTTP and HTTP-over-self-written-TLS.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The mental model I keep now
&lt;/h2&gt;

&lt;p&gt;Before building this, I would have described HTTPS as “HTTP with encryption.”&lt;/p&gt;

&lt;p&gt;That is fine as a user-level description, but it is not precise enough when writing the server.&lt;/p&gt;

&lt;p&gt;The server-side model that helped me is this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TCP gives bytes.
TLS turns encrypted bytes into authenticated plaintext bytes.
HTTP parses the plaintext bytes.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That sounds simple, but it changes where the code belongs.&lt;/p&gt;

&lt;p&gt;You do not sprinkle TLS checks through the HTTP parser.&lt;/p&gt;

&lt;p&gt;You do not make the route matcher aware of certificates.&lt;/p&gt;

&lt;p&gt;You do not duplicate the file server for secure and insecure connections.&lt;/p&gt;

&lt;p&gt;You put a protocol layer below HTTP and keep the rest of the server honest.&lt;/p&gt;

&lt;p&gt;That is what I wanted from this project.&lt;/p&gt;

&lt;p&gt;Not a production HTTPS server. Not a replacement for OpenSSL.&lt;/p&gt;

&lt;p&gt;Just a clearer picture of where HTTPS belongs.&lt;/p&gt;

&lt;p&gt;If you want to follow the full path from encrypted TCP bytes to a parsed HTTP request and back again, I put the code and visual walkthrough here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Companion repo: &lt;a href="https://github.com/DmytroHuzz/tls_web_server" rel="noopener noreferrer"&gt;https://github.com/DmytroHuzz/tls_web_server&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Visual walkthrough: &lt;a href="https://dmytrohuzz.github.io/tls_web_server/" rel="noopener noreferrer"&gt;https://dmytrohuzz.github.io/tls_web_server/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>networking</category>
      <category>security</category>
      <category>tutorial</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Rebuilding TLS, Part 4- Certificate, Trust, and MITM Protection</title>
      <dc:creator>Dmytro Huz</dc:creator>
      <pubDate>Mon, 27 Apr 2026 06:53:03 +0000</pubDate>
      <link>https://dev.to/aws-builders/rebuilding-tls-part-4-certificate-trust-and-mitm-protection-171j</link>
      <guid>https://dev.to/aws-builders/rebuilding-tls-part-4-certificate-trust-and-mitm-protection-171j</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;This article is part of my Rebuilding TLS from Scratch series. The full code and runnable walkthrough are available on &lt;a href="https://github.com/DmytroHuzz/rebuilding_tls/tree/main/part_4/implementation" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A secure connection to the wrong server is still a broken connection.&lt;/p&gt;

&lt;p&gt;That sentence looks strange at first. If traffic is encrypted, if nobody can read it, if nobody can modify it, then what is still missing?&lt;/p&gt;

&lt;p&gt;The missing piece is identity.&lt;/p&gt;

&lt;p&gt;In the previous parts of this series, we slowly moved from a plain TCP connection to something that started to look like a real secure channel. First, &lt;a href="https://www.dmytrohuz.com/p/rebuilding-tls-part-1-why-encryption" rel="noopener noreferrer"&gt;we added encryption&lt;/a&gt;. Then we &lt;a href="https://www.dmytrohuz.com/p/rebuilding-tls-part-2-adding-integrity" rel="noopener noreferrer"&gt;added integrity&lt;/a&gt;. Then we &lt;a href="https://www.dmytrohuz.com/p/rebuilding-tls-part-3-building-our" rel="noopener noreferrer"&gt;stopped using fixed shared keys and introduced a handshake with X25519 and HKDF&lt;/a&gt;, so the client and server could derive fresh session keys for every connection.&lt;/p&gt;

&lt;p&gt;That was a big step forward, but it was still not enough.&lt;/p&gt;

&lt;p&gt;The client could now create a strong encrypted channel, but it still had no reliable way to know &lt;strong&gt;who&lt;/strong&gt; it had created that channel with. It could be the real server. It could also be an attacker sitting in the middle, performing a separate key exchange with the client and another one with the real server.&lt;/p&gt;

&lt;p&gt;In this post we will discuss why key exchange is not the same as authentication, why certificates exist, how certificate chains work, and how I added a simplified certificate-based authentication layer to my small TLS-like protocol.&lt;/p&gt;

&lt;p&gt;By the end, the protocol will finally move from:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“I have an encrypted channel with whoever answered.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;to:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“I have an encrypted channel with the server that proved its identity.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is the moment where this toy protocol starts to feel much closer to real TLS.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The Problem We Still Had After Key Exchange&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;At the end of Part 3, the protocol already had a real handshake.&lt;/p&gt;

&lt;p&gt;The client and server exchanged ephemeral X25519 public keys. They used the resulting shared secret as input to HKDF. From that, they derived separate keys for client-to-server and server-to-client traffic. Then they used those keys to protect application data with AES-GCM.&lt;/p&gt;

&lt;p&gt;That is already much better than hardcoding a shared key into both programs.&lt;/p&gt;

&lt;p&gt;A hardcoded key has many problems. If someone gets it once, every connection using that key is compromised. If you want to rotate it, you need to update both sides. If two clients use the same key, one compromised client can affect other connections. It is simple for a demo, but it is not a good model for a real secure protocol.&lt;/p&gt;

&lt;p&gt;X25519 and HKDF solved a different problem. They allowed the client and server to create fresh session keys for every connection without sending the actual secret over the network.&lt;/p&gt;

&lt;p&gt;But there was still a hole.&lt;/p&gt;

&lt;p&gt;The client received a public key from “the server,” but it had no way to know whether that public key really belonged to the server. The protocol could protect traffic after the handshake, but the handshake itself was not authenticated.&lt;/p&gt;

&lt;p&gt;That means a man-in-the-middle could sit between the client and server and do this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Client  &amp;lt;---- key exchange ----&amp;gt;  Attacker  &amp;lt;---- key exchange ----&amp;gt;  Server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From the client’s point of view, everything looks fine. It completed a key exchange and derived keys.&lt;/p&gt;

&lt;p&gt;From the server’s point of view, everything also looks fine. It completed a key exchange and derived keys.&lt;/p&gt;

&lt;p&gt;But the attacker is in the middle of both secure channels.&lt;/p&gt;

&lt;p&gt;This is the uncomfortable lesson:&lt;/p&gt;

&lt;p&gt;A secure key exchange with an unauthenticated peer can still give you a secure channel to the attacker.&lt;/p&gt;

&lt;p&gt;That is why Part 4 exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Encryption Is Not the Same as Trust&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;It is easy to mix these ideas together because HTTPS makes them feel like one thing.&lt;/p&gt;

&lt;p&gt;When we open a website over HTTPS, we usually think:&lt;/p&gt;

&lt;p&gt;The connection is encrypted, so it is secure.&lt;/p&gt;

&lt;p&gt;But real TLS gives us several properties at the same time. Encryption is only one of them.&lt;/p&gt;

&lt;p&gt;A useful way to separate the ideas is this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Encryption answers:
Can outsiders read the data?

Integrity answers:
Can outsiders modify the data without being detected?

Key exchange answers:
Can both sides create fresh session keys?

Authentication answers:
Do I know who is on the other side?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before Part 4, our protocol had the first three. It did not have the fourth.&lt;/p&gt;

&lt;p&gt;That distinction matters because an attacker does not always need to break encryption. Sometimes the attacker only needs to become the endpoint that you encrypt to.&lt;/p&gt;

&lt;p&gt;If the client encrypts data to the attacker’s key, the encryption still works. The math is not broken. AES-GCM still protects the records. HKDF still derives keys. X25519 still creates a shared secret.&lt;/p&gt;

&lt;p&gt;The problem is not the cryptography. The problem is that the client trusted the wrong public key.&lt;/p&gt;

&lt;p&gt;So the next question becomes simple:&lt;/p&gt;

&lt;p&gt;How does the client know that the server’s handshake key belongs to the real server?&lt;/p&gt;

&lt;p&gt;This is where certificates enter the story.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What Certificates Actually Add&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;A certificate is not magic. It is also not just a random file that makes browsers happy.&lt;/p&gt;

&lt;p&gt;At a practical level, a certificate connects an identity to a public key.&lt;/p&gt;

&lt;p&gt;For example, a server certificate says something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;This public key belongs to this server identity.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But the client should not just believe that statement because the server said so. Anyone can generate a key pair and create a file that claims to be &lt;code&gt;example.com&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So certificates are signed.&lt;/p&gt;

&lt;p&gt;That means another key, usually belonging to a Certificate Authority, signs the certificate data. The client can then verify the signature using the Certificate Authority’s public key.&lt;/p&gt;

&lt;p&gt;In the real Web PKI, this usually forms a chain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Root CA
  signs
Intermediate CA
  signs
Server Certificate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The root certificate is already trusted by the client’s system or browser. The server sends its certificate chain during the handshake. The client verifies each signature in the chain until it reaches a trusted root.&lt;/p&gt;

&lt;p&gt;In my simplified implementation for Part 4, I use the same conceptual model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Root CA
  ↓
Intermediate CA
  ↓
Server Certificate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The point is not to recreate all of Web PKI. The point is to make the trust chain visible.&lt;/p&gt;

&lt;p&gt;The client does not trust the server certificate because the server sent it. The client trusts it because it can verify that the certificate was signed through a chain that ends at a trusted root.&lt;/p&gt;

&lt;p&gt;Now the client has something it did not have before:&lt;/p&gt;

&lt;p&gt;A public identity key that is connected to the server certificate and can be verified through a certificate chain.&lt;/p&gt;

&lt;p&gt;But there is still one more important step.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Why the Server Must Sign the Handshake&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;A certificate chain proves that a certificate is valid.&lt;/p&gt;

&lt;p&gt;It does not automatically prove that the server currently speaking in this connection owns the private key for that certificate.&lt;/p&gt;

&lt;p&gt;That difference is important.&lt;/p&gt;

&lt;p&gt;If the server only sends a certificate chain, an attacker could potentially copy that public certificate chain and send it to the client. Public certificates are public. They are not secrets.&lt;/p&gt;

&lt;p&gt;So the server must prove ownership of the corresponding private key.&lt;/p&gt;

&lt;p&gt;In real TLS, this is done with a handshake signature. In TLS 1.3, the server signs data derived from the handshake transcript. This binds the server’s authenticated identity to the exact handshake that is happening now.&lt;/p&gt;

&lt;p&gt;In my simplified Part 4 implementation, I use the same core idea, but in a smaller form.&lt;/p&gt;

&lt;p&gt;The server has two different types of keys:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Long-term identity key
= connected to the server certificate

Ephemeral X25519 key
= used only for this connection’s key exchange
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The server sends its ephemeral X25519 public key, its certificate chain, and a signature over the handshake data.&lt;/p&gt;

&lt;p&gt;The client verifies three things:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Is the certificate chain valid?

2. Does the server certificate contain the expected identity key?

3. Did the server sign this handshake using the private key that matches the certificate?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the key conceptual step.&lt;/p&gt;

&lt;p&gt;The ephemeral X25519 key gives us fresh session keys. The certificate gives us identity. The signature connects both worlds together.&lt;/p&gt;

&lt;p&gt;Without that signature, the certificate and the key exchange are two separate facts.&lt;/p&gt;

&lt;p&gt;With the signature, the server says:&lt;/p&gt;

&lt;p&gt;I own the private key for this certificate, and I am binding that identity to this ephemeral key exchange.&lt;/p&gt;

&lt;p&gt;That is what stops the man-in-the-middle from silently replacing the handshake key.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The Architecture of Part 4&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;After Part 4, the handshake looks roughly like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Client
  |
  |  ClientHello
  |  ephemeral X25519 public key
  |
  v
Server
  |
  |  ServerHello
  |  ephemeral X25519 public key
  |  certificate chain
  |  handshake signature
  |
  v
Client
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then both sides derive the shared secret using X25519:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;client private key + server public key
server private key + client public key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both sides arrive at the same shared secret without sending that secret over the network.&lt;/p&gt;

&lt;p&gt;Then HKDF turns that shared secret into actual session keys.&lt;/p&gt;

&lt;p&gt;Then AES-GCM protects the application records.&lt;/p&gt;

&lt;p&gt;The important change is that the client no longer accepts the server’s ephemeral public key blindly. It checks whether the authenticated server signed the handshake.&lt;/p&gt;

&lt;p&gt;The complete shape now looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Certificate chain
  proves server identity

Handshake signature
  binds server identity to the ephemeral key exchange

X25519
  creates a fresh shared secret

HKDF
  derives directional session keys

AES-GCM
  protects application records
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is still a simplified protocol, but the main pieces now line up with the real TLS story much better.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What We Built in Code&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;For this part, I added a small certificate infrastructure to the project.&lt;/p&gt;

&lt;p&gt;The implementation generates a simple hierarchy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Root CA
Intermediate CA
Server Certificate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The server uses the server certificate and private key as its long-term identity. During the handshake, it still creates a fresh ephemeral X25519 key pair for the current connection.&lt;/p&gt;

&lt;p&gt;That distinction matters.&lt;/p&gt;

&lt;p&gt;The long-term certificate key is not used to encrypt application data. It is used to prove identity.&lt;/p&gt;

&lt;p&gt;The ephemeral X25519 key is not used as identity. It is used to create a fresh shared secret for this connection.&lt;/p&gt;

&lt;p&gt;This separation is one of the most important design ideas in modern TLS. Long-term keys authenticate. Ephemeral keys protect the session.&lt;/p&gt;

&lt;p&gt;The simplified Part 4 flow is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Client creates an ephemeral X25519 key pair.

2. Client sends its public key.

3. Server creates an ephemeral X25519 key pair.

4. Server sends:
   - its ephemeral public key
   - certificate chain
   - signature over handshake data

5. Client verifies the certificate chain.

6. Client verifies the handshake signature.

7. Both sides derive the shared secret with X25519.

8. Both sides derive session keys with HKDF.

9. Application data is protected with AES-GCM.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The full code is in the GitHub repository:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/DmytroHuzz/rebuilding_tls" rel="noopener noreferrer"&gt;https://github.com/DmytroHuzz/rebuilding_tls&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The full technical walkthrough is here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dmytrohuzz.github.io/rebuilding_tls/part_4/walkthrough/walkthrough.html" rel="noopener noreferrer"&gt;https://dmytrohuzz.github.io/rebuilding_tls/part_4/walkthrough/walkthrough.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I intentionally keep the article focused on the protocol idea rather than pasting the whole implementation here. Long code blocks inside an article often create an illusion of depth, but they usually make the article harder to read.&lt;/p&gt;

&lt;p&gt;The repository is the right place for the full implementation. The article is the right place for the explanation.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What This Still Is Not&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;This is not real TLS.&lt;/p&gt;

&lt;p&gt;That sentence is important.&lt;/p&gt;

&lt;p&gt;This project is an educational reconstruction of some core TLS ideas. It is not a library. It is not a production protocol. It is not something that should protect real traffic.&lt;/p&gt;

&lt;p&gt;Real TLS has many more details and much stronger guarantees around the handshake. For example, real TLS 1.3 binds the handshake with a transcript hash. It supports negotiation, extensions, certificate validation rules, revocation mechanisms, session resumption, alerts, many edge cases, and years of hardening against attacks that are easy to miss when building a small protocol.&lt;/p&gt;

&lt;p&gt;My version is intentionally smaller.&lt;/p&gt;

&lt;p&gt;It is useful because it makes the main ideas visible:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Why encryption alone is not enough.

Why integrity must be added.

Why fixed keys are weak.

Why key exchange gives fresh session keys.

Why key exchange is still not authentication.

Why certificates exist.

Why the server must sign the handshake.

Why TLS has the shape it has.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the real goal of this series.&lt;/p&gt;

&lt;p&gt;Not to replace TLS, but to make TLS less mysterious.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Try It Yourself&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The project is public and runnable.&lt;/p&gt;

&lt;p&gt;The main repository is here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/DmytroHuzz/rebuilding_tls" rel="noopener noreferrer"&gt;https://github.com/DmytroHuzz/rebuilding_tls&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Part 4 walkthrough is here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dmytrohuzz.github.io/rebuilding_tls/part_4/walkthrough/walkthrough.html" rel="noopener noreferrer"&gt;https://dmytrohuzz.github.io/rebuilding_tls/part_4/walkthrough/walkthrough.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The complete series landing page is here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.dmytrohuz.com/p/rebuilding-tls-from-scratch-my-complete" rel="noopener noreferrer"&gt;https://www.dmytrohuz.com/p/rebuilding-tls-from-scratch-my-complete&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you want to understand the path from the beginning, I recommend reading the series in order. Each part fixes one problem and reveals the next one.&lt;/p&gt;

&lt;p&gt;Part 1 starts with encryption. Part 2 adds integrity. Part 3 adds key exchange and session keys. Part 4 adds authentication through certificates and a handshake signature.&lt;/p&gt;

&lt;p&gt;That sequence matters because it shows why TLS is not just “encryption.” It is a stack of answers to different problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Summary&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Before Part 4, the protocol could create an encrypted channel, but it could not prove who was on the other side.&lt;/p&gt;

&lt;p&gt;That is a serious problem.&lt;/p&gt;

&lt;p&gt;Encryption protects data from being read. Integrity protects data from being modified. Key exchange creates fresh session keys. But authentication tells the client whether it is talking to the real server or to an attacker in the middle.&lt;/p&gt;

&lt;p&gt;In Part 4, I added that missing authentication layer.&lt;/p&gt;

&lt;p&gt;The client now verifies a certificate chain and checks a handshake signature. The server uses a long-term identity key to authenticate itself, while still using an ephemeral X25519 key for the actual key exchange. HKDF derives session keys, and AES-GCM protects application data.&lt;/p&gt;

&lt;p&gt;The result is still not real TLS, but it now has the core shape:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;authenticated handshake
fresh session keys
protected records
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that is the point of this whole series.&lt;/p&gt;

&lt;p&gt;TLS is hard to understand when you only look at the final protocol. There are too many details, too many names, too many moving parts.&lt;/p&gt;

&lt;p&gt;But when you rebuild it step by step, each piece starts to make sense.&lt;/p&gt;

&lt;p&gt;You first feel the problem.&lt;/p&gt;

&lt;p&gt;Then you add the missing mechanism.&lt;/p&gt;

&lt;p&gt;Then you see why the real protocol looks the way it does.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>learning</category>
    </item>
    <item>
      <title>Rebuilding TLS, Part 3 — Building Our First Handshake</title>
      <dc:creator>Dmytro Huz</dc:creator>
      <pubDate>Sun, 19 Apr 2026 17:09:17 +0000</pubDate>
      <link>https://dev.to/aws-builders/rebuilding-tls-part-3-building-our-first-handshake-4a2j</link>
      <guid>https://dev.to/aws-builders/rebuilding-tls-part-3-building-our-first-handshake-4a2j</guid>
      <description>&lt;h2&gt;
  
  
  &lt;strong&gt;Overview: Where we are and What Is Still Missing&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;In the previous part of this series, we made our fake secure channel much less fake.&lt;/p&gt;

&lt;p&gt;We started with the broken encrypted transport from &lt;a href="https://www.dmytrohuz.com/p/rebuilding-tls-part-1-why-encryption" rel="noopener noreferrer"&gt;Part 1&lt;/a&gt;, added integrity with HMAC, added sequence numbers to make the record layer less naive, &lt;a href="https://www.dmytrohuz.com/p/rebuilding-tls-part-2-adding-integrity" rel="noopener noreferrer"&gt;and then moved to AEAD&lt;/a&gt; — the approach modern systems usually use to protect records.&lt;/p&gt;

&lt;p&gt;At that point, our protocol could already do something meaningful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;encrypt application data&lt;/li&gt;
&lt;li&gt;detect tampering&lt;/li&gt;
&lt;li&gt;reject modified records&lt;/li&gt;
&lt;li&gt;keep some minimal record-layer state&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That was a real step forward.&lt;/p&gt;

&lt;p&gt;But it still relied on one very unrealistic assumption:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;both sides already shared the secret keys&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And that is exactly what we need to remove now.&lt;/p&gt;

&lt;p&gt;Because a real secure protocol cannot stop at protecting data after the keys already exist. It also has to answer one of the harder questions first:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;if client and server do not already share a secret, how can they create one over an insecure network in the first place?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That is the goal of this part.&lt;/p&gt;

&lt;p&gt;We are going to build the next missing layer of the protocol: the handshake.&lt;/p&gt;

&lt;p&gt;The architecture of this step is simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Client                           Server
------                           ------
Handshake messages  &amp;lt;---------&amp;gt;  Handshake messages
       |                               |
       v                               v
  shared secret                  shared secret
       |                               |
       +---------&amp;gt; HKDF &amp;lt;--------------+
                    |
                    v
              session keys
                    |
                    v
         protected application data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The idea is to let the connection create fresh key material dynamically instead of starting with a hardcoded application key.&lt;/p&gt;

&lt;p&gt;We will implement that in three steps.&lt;/p&gt;

&lt;p&gt;First, we will build a handshake with classic Diffie-Hellman, where the shared prime and base are still explicit and visible in the protocol. Then we will replace that version with X25519 to show how modern protocols simplify the same idea. After that, we will use HKDF to derive proper session keys from the raw shared secret.&lt;/p&gt;

&lt;p&gt;That will take us one big step closer to the shape of real TLS.&lt;/p&gt;

&lt;p&gt;But still not all the way.&lt;/p&gt;

&lt;p&gt;Because even if both sides manage to derive the same fresh session keys, one critical problem will remain: they still do not know who is on the other side.&lt;/p&gt;

&lt;p&gt;And that is where this part is heading.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;A Very Short Note on Public Key Exchange&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The basic idea of public key exchange is simple.&lt;/p&gt;

&lt;p&gt;Two sides communicate over an insecure network. They exchange some public information. And from that exchange, both sides derive the same shared secret — without ever sending that secret directly over the wire.&lt;/p&gt;

&lt;p&gt;That is the key point.&lt;/p&gt;

&lt;p&gt;The network can be fully visible.&lt;/p&gt;

&lt;p&gt;An observer can see all handshake messages.&lt;/p&gt;

&lt;p&gt;But the observer still should not be able to derive the same secret.&lt;/p&gt;

&lt;p&gt;That is exactly the kind of mechanism we need now.&lt;/p&gt;

&lt;p&gt;Until this point in the series, our protocol always started with a secret that already existed. Public key exchange changes that. It gives the connection a way to create fresh shared key material dynamically.&lt;/p&gt;

&lt;p&gt;In this article, I do not want to go deep into the mathematics behind it. I only want to use the core idea as the next building block of the protocol.&lt;/p&gt;

&lt;p&gt;If you want the deeper intuition behind why this works, I already wrote about it here:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The aha moment of public key encryption&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.dmytrohuz.com/p/the-aha-moment-of-public-key-encryption" rel="noopener noreferrer"&gt;https://www.dmytrohuz.com/p/the-aha-moment-of-public-key-encryption&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For now, the main idea we need is this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;each side contributes its own private value&lt;/li&gt;
&lt;li&gt;both sides exchange some public values&lt;/li&gt;
&lt;li&gt;both sides derive the same shared secret&lt;/li&gt;
&lt;li&gt;that secret can then become the basis for session keys&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So let’s build that first in the most explicit way, with classic Diffie-Hellman where the shared public parameters are still visible in the handshake.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Implementation Part 1 — Our First Handshake with Classic Diffie-Hellman&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Now let’s build the first real handshake in the series.&lt;/p&gt;

&lt;p&gt;I want to start with classic Diffie-Hellman, not because this is the final form we want to keep, but because it makes the mechanics of key exchange much more visible.&lt;/p&gt;

&lt;p&gt;In this version, both sides work with the same public parameters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a prime p&lt;/li&gt;
&lt;li&gt;a generator g&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These values are not secret. In our implementation, the client sends them in the handshake, which makes the whole mechanism more explicit on the wire. That is exactly what I want at this stage. Before we hide the details behind a cleaner modern primitive, I want to make the structure fully visible.&lt;/p&gt;

&lt;p&gt;The actual secret material comes from somewhere else:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the client chooses a private exponent a&lt;/li&gt;
&lt;li&gt;the server chooses a private exponent b&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;From those private values, both sides compute public values:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the client computes A = g^a mod p&lt;/li&gt;
&lt;li&gt;the server computes B = g^b mod p&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then they exchange A and B.&lt;/p&gt;

&lt;p&gt;And this is the key step:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the client computes s = B^a mod p&lt;/li&gt;
&lt;li&gt;the server computes s = A^b mod p&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both sides end up with the same shared secret, without ever sending that secret directly over the network.&lt;/p&gt;

&lt;p&gt;In diagram form, the handshake looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Client                                        Server
------                                        ------
choose private a
compute A = g^a mod p

ClientHello(p, g, A)        ---------&amp;gt;

                                              choose private b
                                              compute B = g^b mod p

                            &amp;lt;---------          ServerHello(B)

compute s = B^a mod p                           compute s = A^b mod p
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is our first real handshake.&lt;/p&gt;

&lt;p&gt;Until now, the protocol always started with a secret key that already existed.&lt;/p&gt;

&lt;p&gt;Now the connection itself creates the secret.&lt;/p&gt;

&lt;p&gt;That is a major shift.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;The raw Diffie-Hellman math&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;At the lowest level, the core operations are very small. That is one of the nice things about starting with classic Diffie-Hellman: the whole idea is still visible in a few functions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;
&lt;span class="c1"&gt;# RFC 3526 Group 14: 2048-bit MODP prime
&lt;/span&gt;&lt;span class="n"&gt;DH_PRIME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;29024E088A67CC74020BBEA63B139B22514A08798E3404DD&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;83655D23DCA3AD961C62F356208552BB9ED529077096966D&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DE2BCBF6955817183995497CEA956AE515D2261898FA0510&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;15728E5A8AACAA68FFFFFFFFFFFFFFFF&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;DH_GENERATOR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;generate_private_exponent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urandom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;big&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;compute_public_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;private&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;pow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;private&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;compute_shared_secret&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;peer_public&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;private&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;pow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;peer_public&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;private&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the whole core idea in code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;private exponent stays local&lt;/li&gt;
&lt;li&gt;public value goes on the wire&lt;/li&gt;
&lt;li&gt;shared secret is derived independently on both sides&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is the heart of Diffie-Hellman.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Client side&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;client_handshake&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Perform the client side of the classic DH handshake.

    The client picks the public parameters (p, g) and sends them to the
    server along with its own public DH value.  The server uses those
    parameters to compute its own public value and sends it back.

    Returns the shared secret as bytes.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="c1"&gt;# The client chooses p and g.  These are PUBLIC — not secret.
&lt;/span&gt;    &lt;span class="c1"&gt;# Anyone on the wire can see them, and that is perfectly fine.
&lt;/span&gt;    &lt;span class="c1"&gt;# The security of DH depends on the hardness of the discrete
&lt;/span&gt;    &lt;span class="c1"&gt;# logarithm problem, not on hiding p and g.
&lt;/span&gt;    &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DH_PRIME&lt;/span&gt;
    &lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DH_GENERATOR&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  Public parameters (chosen by client, sent to server):&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;    p = &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;... (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bit_length&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; bits)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;    g = &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Step 1: Generate client's private exponent and public value.
&lt;/span&gt;    &lt;span class="c1"&gt;# The private exponent is the ONE thing that stays secret.
&lt;/span&gt;    &lt;span class="n"&gt;client_private&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generate_private_exponent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;client_public&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;compute_public_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client_private&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;client_public_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int_to_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client_public&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Step 2: Send ClientHello with p, g, and our public value.
&lt;/span&gt;    &lt;span class="c1"&gt;# All three are public.  The private exponent is NOT included.
&lt;/span&gt;    &lt;span class="n"&gt;p_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int_to_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;g_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int_to_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;client_hello&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;encode_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TAG_DH_P&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p_bytes&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TAG_DH_G&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;g_bytes&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TAG_DH_PUBLIC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;client_public_bytes&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# Step 3: send p, g, and the client’s public value inside ClientHello
&lt;/span&gt;    &lt;span class="nf"&gt;send_record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;client_hello&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Step 4: Receive ServerHello with the server's public value.
&lt;/span&gt;    &lt;span class="n"&gt;server_hello_raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;recv_record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;decode_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;server_hello_raw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;server_public_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;TAG_DH_PUBLIC&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;server_public_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;server_public_bytes&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ServerHello missing DH public value&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;server_public&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;bytes_to_int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;server_public_bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  &amp;lt;- Received ServerHello&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  Server public value B:   &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;hex_preview&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;server_public_bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Step 5: Compute the shared secret.
&lt;/span&gt;    &lt;span class="c1"&gt;# shared = B^a mod p = (g^b)^a mod p = g^(ab) mod p
&lt;/span&gt;    &lt;span class="n"&gt;shared_int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;compute_shared_secret&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;server_public&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;client_private&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;shared_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int_to_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shared_int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;shared_bytes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the client side, the flow is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;choose a private exponent&lt;/li&gt;
&lt;li&gt;compute the public value&lt;/li&gt;
&lt;li&gt;send p, g, and the client’s public value inside ClientHello&lt;/li&gt;
&lt;li&gt;receive the server’s public value&lt;/li&gt;
&lt;li&gt;derive the shared secret&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That is the first point in the series where the client does not begin with the application key. It participates in creating it.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Server side&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;server_handshake&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Perform the server side of the classic DH handshake.

    The server receives p, g, and client_public from the ClientHello,
    uses those parameters to generate its own keypair, and sends its
    public value back.

    Returns the shared secret as bytes.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="c1"&gt;# Step 1: Receive ClientHello — parse p, g, and client's public value.
&lt;/span&gt;    &lt;span class="c1"&gt;# The server does NOT assume any particular p or g.  It uses whatever
&lt;/span&gt;    &lt;span class="c1"&gt;# the client proposes.  (In a production system, the server would
&lt;/span&gt;    &lt;span class="c1"&gt;# validate that p is a safe prime and g is a proper generator.
&lt;/span&gt;    &lt;span class="c1"&gt;# We skip that here for clarity.)
&lt;/span&gt;    &lt;span class="n"&gt;client_hello_raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;recv_record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;decode_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client_hello_raw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;p_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;g_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;client_public_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;TAG_DH_P&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;p_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;TAG_DH_G&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;g_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;TAG_DH_PUBLIC&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;client_public_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;p_bytes&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ClientHello missing DH prime (p)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;g_bytes&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ClientHello missing DH generator (g)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;client_public_bytes&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ClientHello missing DH public value (A)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Deserialize the parameters from bytes.
&lt;/span&gt;    &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;bytes_to_int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p_bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;bytes_to_int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;g_bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;client_public&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;bytes_to_int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client_public_bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Step 2: Generate server's private exponent and public value
&lt;/span&gt;    &lt;span class="c1"&gt;# using the p and g received from the client.
&lt;/span&gt;    &lt;span class="n"&gt;server_private&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generate_private_exponent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# Step 3: Compute server's public value
&lt;/span&gt;    &lt;span class="n"&gt;server_public&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;compute_public_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;server_private&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;server_public_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int_to_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;server_public&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Step 4: Send ServerHello with our public value.
&lt;/span&gt;    &lt;span class="c1"&gt;# Only B is sent — p and g are already known from the ClientHello.
&lt;/span&gt;    &lt;span class="n"&gt;server_hello&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;encode_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TAG_DH_PUBLIC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;server_public_bytes&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;send_record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;server_hello&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Step 5: Compute the shared secret.
&lt;/span&gt;    &lt;span class="c1"&gt;# shared = A^b mod p = (g^a)^b mod p = g^(ab) mod p
&lt;/span&gt;    &lt;span class="n"&gt;shared_int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;compute_shared_secret&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client_public&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;server_private&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;shared_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int_to_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shared_int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;shared_bytes&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The server does the mirror image:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;receive p, g, and the client’s public value&lt;/li&gt;
&lt;li&gt;choose its own private exponent&lt;/li&gt;
&lt;li&gt;compute its own public value&lt;/li&gt;
&lt;li&gt;send that value back in ServerHello&lt;/li&gt;
&lt;li&gt;derive the same shared secret from the client’s public value&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So at the end of the handshake, both sides have the same secret — but that secret was never transmitted directly.&lt;/p&gt;

&lt;p&gt;That is the big win.&lt;/p&gt;

&lt;p&gt;After this step, the connection can create fresh shared key material dynamically.&lt;/p&gt;

&lt;p&gt;That is a much more realistic foundation.&lt;/p&gt;

&lt;p&gt;But it is also still awkward.&lt;/p&gt;

&lt;p&gt;Not conceptually awkward — educationally this version is very useful — but operationally awkward. We now have explicit p and g in the handshake, which is nice for understanding the mechanism, but clunky for a modern protocol design.&lt;/p&gt;

&lt;p&gt;That is exactly why the next step will replace this version with X25519.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Implementation Part 2 — Simplifying the Handshake with X25519&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The classic Diffie-Hellman version was useful because it made the mechanics of the handshake fully visible.&lt;/p&gt;

&lt;p&gt;But it also makes something else visible:&lt;/p&gt;

&lt;p&gt;it is a bit clunky.&lt;/p&gt;

&lt;p&gt;Not conceptually clunky — educationally it is great — but operationally clunky. There are more moving parts in the handshake, more explicit protocol fields, and more visible math than modern protocols usually want to expose directly.&lt;/p&gt;

&lt;p&gt;So now we keep the same core idea and simplify the workflow.&lt;/p&gt;

&lt;p&gt;That is where &lt;strong&gt;X25519&lt;/strong&gt; comes in.&lt;/p&gt;

&lt;p&gt;The conceptual goal stays exactly the same:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;both sides generate ephemeral private/public key pairs&lt;/li&gt;
&lt;li&gt;both sides exchange public keys&lt;/li&gt;
&lt;li&gt;both sides derive the same shared secret&lt;/li&gt;
&lt;li&gt;that secret will later become the basis for session keys&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What changes is the &lt;em&gt;shape&lt;/em&gt; of the handshake.&lt;/p&gt;

&lt;p&gt;We no longer need to carry an explicit prime and generator through the protocol. We no longer manually perform modular exponentiation with visible p and g. X25519 gives us the same public-key exchange idea in a much cleaner modern form.&lt;/p&gt;

&lt;p&gt;That is why I wanted this section right after the classic DH version.&lt;/p&gt;

&lt;p&gt;Classic DH makes the mechanism visible.&lt;/p&gt;

&lt;p&gt;X25519 shows what the modern streamlined version looks like.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Client-side handshake structure&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Here is the current client handshake implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;client_handshake&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Perform the client side of the X25519 handshake.

    Returns the 32-byte shared secret.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;[handshake] Client: starting X25519 handshake&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Step 1: Generate an ephemeral X25519 keypair.
&lt;/span&gt;    &lt;span class="c1"&gt;# "Ephemeral" means we create a fresh keypair for this session only.
&lt;/span&gt;    &lt;span class="c1"&gt;# The private key never leaves this process and is discarded after use.
&lt;/span&gt;    &lt;span class="n"&gt;client_private&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;X25519PrivateKey&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;client_public&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client_private&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;public_key&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;client_public_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client_public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;public_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Encoding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Raw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PublicFormat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Raw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Step 2: Send ClientHello with our public key.
&lt;/span&gt;    &lt;span class="n"&gt;client_hello&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;encode_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TAG_X25519_PUBLIC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;client_public_bytes&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;send_record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;client_hello&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Step 3: Receive ServerHello with the server's public key.
&lt;/span&gt;    &lt;span class="n"&gt;server_hello_raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;recv_record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;decode_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;server_hello_raw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;server_public_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;TAG_X25519_PUBLIC&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;server_public_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;server_public_bytes&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ServerHello missing X25519 public key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Deserialize the server's public key from raw bytes.
&lt;/span&gt;    &lt;span class="n"&gt;server_public&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;X25519PublicKey&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_public_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;server_public_bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Step 4: Compute the shared secret.
&lt;/span&gt;    &lt;span class="c1"&gt;# X25519(client_private, server_public) = X25519(server_private, client_public)
&lt;/span&gt;    &lt;span class="c1"&gt;# This is the elliptic-curve equivalent of g^(ab) mod p from v1.
&lt;/span&gt;    &lt;span class="n"&gt;shared_secret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client_private&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;server_public&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;shared_secret&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I like this version because it makes the transition very clear.&lt;/p&gt;

&lt;p&gt;The client code no longer has to think about p and g at all. It just performs the handshake, gets the shared secret, and prints it. That is exactly the point of this stage in the series: the workflow becomes smaller, but the underlying purpose stays the same.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;What changed conceptually&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Compared to the classic DH version, the protocol has become simpler in three important ways.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;1. No explicit shared public parameters in the handshake&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;In the previous version, the client sent the prime and generator so the whole structure of classic Diffie-Hellman stayed visible.&lt;/p&gt;

&lt;p&gt;Now that goes away.&lt;/p&gt;

&lt;p&gt;X25519 already gives us a fixed, standard structure for the exchange, so the handshake only needs to carry the public key material.&lt;/p&gt;

&lt;p&gt;That makes the protocol smaller and cleaner.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;2. The public values are much more compact&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;In the classic DH version, the public values were tied to a large prime-field construction and looked much heavier in the protocol.&lt;/p&gt;

&lt;p&gt;In this version, the public keys are just 32 bytes.&lt;/p&gt;

&lt;p&gt;That is a huge practical simplification.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;3. The code starts to look more like real modern protocol code&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;This line from the comments says it well:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;generate(), exchange(), done.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is exactly the feeling this section should create.&lt;/p&gt;

&lt;p&gt;We are still doing public-key exchange.&lt;/p&gt;

&lt;p&gt;We are still deriving a shared secret.&lt;/p&gt;

&lt;p&gt;But the implementation shape is now much closer to what modern systems actually use.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;What this version still does not solve&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Even after switching to X25519, this version is still simplified:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;there is still &lt;strong&gt;no authentication&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;the shared secret is &lt;strong&gt;not yet turned into session keys&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;there is still &lt;strong&gt;no record-layer encryption using the new keys&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the next step, we will add &lt;strong&gt;HKDF&lt;/strong&gt; and derive proper working session keys from it.&lt;/p&gt;

&lt;p&gt;That is where the handshake starts to connect back to the record protection we built earlier.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Implementation Part 3 — Deriving Session Keys with HKDF&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;At this point, both the classic Diffie-Hellman version and the X25519 version give us the same kind of output:&lt;/p&gt;

&lt;p&gt;a shared secret that both sides can compute independently.&lt;/p&gt;

&lt;p&gt;That is already a big step forward compared to the pre-shared-key model from the previous parts. The connection can now create fresh key material dynamically instead of starting with one hardcoded application key.&lt;/p&gt;

&lt;p&gt;But there is still one important design question left:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;should we use that raw shared secret directly as the application key?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For a toy demo, we probably could.&lt;/p&gt;

&lt;p&gt;But even here, that would be the wrong direction.&lt;/p&gt;

&lt;p&gt;Because a cleaner protocol separates these two ideas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the handshake creates a shared secret&lt;/li&gt;
&lt;li&gt;the protocol derives working session keys from that secret&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is exactly where &lt;strong&gt;HKDF&lt;/strong&gt; comes in.&lt;/p&gt;

&lt;p&gt;HKDF is a key-derivation function. Its job is not to invent secrecy out of nowhere, but to take existing secret material and turn it into keys that are better structured and easier to use safely inside the protocol.&lt;/p&gt;

&lt;p&gt;So instead of treating the X25519 output as “the AES key,” we will use HKDF to derive proper session keys from it.&lt;/p&gt;

&lt;p&gt;That already makes the protocol feel much closer to real TLS.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;What changes conceptually&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The structure now becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;X25519 shared secret
        |
        v
      HKDF
        |
        v
  session key material
        |
        v
 protected application data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is an important shift.&lt;/p&gt;

&lt;p&gt;Before this step, the handshake produced something secret and we could have stopped there.&lt;/p&gt;

&lt;p&gt;After this step, the handshake produces an &lt;em&gt;input&lt;/em&gt; to a key schedule.&lt;/p&gt;

&lt;p&gt;That is a much better protocol design.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Why this matters&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;There are two main reasons to do this.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;1. The raw shared secret is handshake output, not final protocol state&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The shared secret is the result of key exchange. That does not automatically mean it should be used directly as the application-data key.&lt;/p&gt;

&lt;p&gt;Protocols usually want a cleaner boundary:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;handshake result first&lt;/li&gt;
&lt;li&gt;working keys second&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;2. We can derive keys for different purposes&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Once we introduce a key-derivation step, we are no longer forced into “one secret for everything.”&lt;/p&gt;

&lt;p&gt;Even in this toy protocol, that opens the door to a much more realistic design.&lt;/p&gt;

&lt;p&gt;For example, instead of one single AEAD key, we can derive:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;client → server key&lt;/li&gt;
&lt;li&gt;server → client key&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is already much closer to how real secure protocols think.&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;Deriving the keys&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;In the current implementation, HKDF takes the X25519 shared secret and stretches it into 64 bytes of key material.&lt;/p&gt;

&lt;p&gt;Then that material is split into two 32-byte keys:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one for traffic from client to server&lt;/li&gt;
&lt;li&gt;one for traffic from server to client&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That gives us directional keys instead of one shared application key for both directions.&lt;/p&gt;

&lt;p&gt;Here is the key schedule:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# key_schedule_x25519.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;cryptography.hazmat.primitives&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;hashes&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;cryptography.hazmat.primitives.kdf.hkdf&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;HKDF&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;derive_session_keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shared_secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;key_material&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;HKDF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;algorithm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;hashes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SHA256&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;salt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;toy-tls-part-3-x25519&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;derive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shared_secret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;client_to_server_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;key_material&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;server_to_client_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;key_material&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;client_to_server_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;server_to_client_key&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I like this step a lot because it is small in code, but it changes the protocol mindset in an important way.&lt;/p&gt;

&lt;p&gt;We are no longer thinking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;handshake gives us the key&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We are now thinking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;handshake gives us secret material, and the protocol derives the keys it actually wants to use&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is a much stronger model.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;A small but important detail&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Notice that the two sides must interpret the derived keys consistently.&lt;/p&gt;

&lt;p&gt;If the client treats the first 32 bytes as the client → server key, then the server must do the same. Otherwise the channel will immediately break.&lt;/p&gt;

&lt;p&gt;So now the handshake is not only producing shared secret material. It is also establishing a shared rule for how that material becomes working traffic keys.&lt;/p&gt;

&lt;p&gt;That is another reason protocols need structure, not just primitives.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Connecting HKDF back to the record layer&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Now we can finally connect this part back to what we built earlier.&lt;/p&gt;

&lt;p&gt;In Part 2, we already built an AEAD-protected record layer. But that record layer still depended on hardcoded keys.&lt;/p&gt;

&lt;p&gt;Now that changes.&lt;/p&gt;

&lt;p&gt;The AEAD layer no longer starts with a static key from configuration.&lt;/p&gt;

&lt;p&gt;It receives fresh traffic keys from the handshake.&lt;/p&gt;

&lt;p&gt;So the protocol shape becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Handshake -&amp;gt; X25519 shared secret -&amp;gt; HKDF -&amp;gt; directional session keys -&amp;gt; AEAD protected records
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is a major milestone in the series.&lt;/p&gt;

&lt;p&gt;At this point, the protocol no longer just looks secure because we wrapped some bytes in encryption. It now has a real high-level structure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;first establish shared key material&lt;/li&gt;
&lt;li&gt;then derive traffic keys&lt;/li&gt;
&lt;li&gt;then use those keys to protect application data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is already much closer to the shape of real TLS.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Using the new session keys&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Once the keys are derived, the record layer can use them directly.&lt;/p&gt;

&lt;p&gt;Conceptually, the flow now looks like this:&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Client&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AF_INET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SOCK_STREAM&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;HOST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Connected to &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;HOST&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;PORT&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# ==========================================
&lt;/span&gt;    &lt;span class="c1"&gt;# PHASE 1: HANDSHAKE
&lt;/span&gt;    &lt;span class="c1"&gt;# ==========================================
&lt;/span&gt;    &lt;span class="c1"&gt;# New in Part 3: the handshake dynamically establishes session keys.
&lt;/span&gt;    &lt;span class="c1"&gt;# No pre-shared secret needed.
&lt;/span&gt;    &lt;span class="n"&gt;client_write_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;server_write_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;client_handshake&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# ==========================================
&lt;/span&gt;    &lt;span class="c1"&gt;# PHASE 2: APPLICATION DATA
&lt;/span&gt;    &lt;span class="c1"&gt;# ==========================================
&lt;/span&gt;    &lt;span class="c1"&gt;# The record layer now uses HKDF-derived keys instead of hardcoded ones.
&lt;/span&gt;    &lt;span class="c1"&gt;# The record format is the same as Part 2 Stage 3 (AEAD).
&lt;/span&gt;
    &lt;span class="c1"&gt;# --- Send request (encrypted with client_write_key) ---
&lt;/span&gt;    &lt;span class="n"&gt;protected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;protect_record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client_write_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;send_seq&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;send_record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;protected&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;send_seq&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

    &lt;span class="c1"&gt;# --- Receive response (decrypted with server_write_key) ---
&lt;/span&gt;    &lt;span class="n"&gt;raw_response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;recv_record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;unprotect_record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;server_write_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;recv_seq&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;raw_response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;recv_seq&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;  Decrypted response:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;  &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;  *** REJECTED: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; ***&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Done.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;use client_write_key to protect outgoing application data&lt;/li&gt;
&lt;li&gt;use server_write_key to unprotect incoming application data&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Server&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AF_INET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SOCK_STREAM&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setsockopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SOL_SOCKET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SO_REUSEADDR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;HOST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Listening on &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;HOST&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;PORT&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;addr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;accept&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# ==========================================
&lt;/span&gt;        &lt;span class="c1"&gt;# PHASE 1: HANDSHAKE
&lt;/span&gt;        &lt;span class="c1"&gt;# ==========================================
&lt;/span&gt;        &lt;span class="n"&gt;client_write_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;server_write_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;server_handshake&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# ==========================================
&lt;/span&gt;        &lt;span class="c1"&gt;# PHASE 2: APPLICATION DATA
&lt;/span&gt;        &lt;span class="c1"&gt;# ==========================================
&lt;/span&gt;
        &lt;span class="c1"&gt;# --- Receive request (decrypted with client_write_key) ---
&lt;/span&gt;        &lt;span class="n"&gt;raw_request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;recv_record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;unprotect_record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client_write_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;recv_seq&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;raw_request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;recv_seq&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;  *** REJECTED: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; ***&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  Connection closed — refusing to process invalid data.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

            &lt;span class="c1"&gt;# --- Send response (encrypted with server_write_key) ---
&lt;/span&gt;            &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;HTTP/1.1 200 OK&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Type: text/plain&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Length: 13&lt;/span&gt;&lt;span class="se"&gt;\r\n\r\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hello, client&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="n"&gt;protected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;protect_record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;server_write_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;send_seq&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;send_record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;protected&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;send_seq&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Done.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;use client_write_key to unprotect incoming client traffic&lt;/li&gt;
&lt;li&gt;use server_write_key to protect outgoing server traffic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That means the two directions are now separated.&lt;/p&gt;

&lt;p&gt;This is cleaner than one symmetric application key shared blindly by both directions, and it makes the protocol feel more deliberate.&lt;/p&gt;

&lt;p&gt;Even in this simplified version, that is a meaningful step.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;What this step really gave us&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;By adding HKDF, we improved the protocol in a way that is easy to underestimate.&lt;/p&gt;

&lt;p&gt;We did not just “derive another key.”&lt;/p&gt;

&lt;p&gt;We made the protocol architecture cleaner.&lt;/p&gt;

&lt;p&gt;Now the handshake and the traffic layer are connected in a more principled way:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the handshake creates shared secret material&lt;/li&gt;
&lt;li&gt;the key schedule turns that material into working keys&lt;/li&gt;
&lt;li&gt;the record layer consumes those keys&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is a much better model than treating the raw X25519 result as the final answer.&lt;/p&gt;

&lt;p&gt;And it brings us one step closer to real TLS, where key derivation is not an optional detail, but one of the central pieces of the protocol design.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;But we are still not secure&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;And now we arrive at the uncomfortable but necessary part.&lt;/p&gt;

&lt;p&gt;Even with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a real handshake&lt;/li&gt;
&lt;li&gt;X25519&lt;/li&gt;
&lt;li&gt;HKDF&lt;/li&gt;
&lt;li&gt;fresh directional session keys&lt;/li&gt;
&lt;li&gt;AEAD-protected records&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;the protocol still cannot be considered secure enough.&lt;/p&gt;

&lt;p&gt;Why?&lt;/p&gt;

&lt;p&gt;Because all of this still says nothing about &lt;strong&gt;who&lt;/strong&gt; is on the other side.&lt;/p&gt;

&lt;p&gt;The handshake can successfully create shared secrets.&lt;/p&gt;

&lt;p&gt;HKDF can successfully derive traffic keys.&lt;/p&gt;

&lt;p&gt;The record layer can successfully protect application data.&lt;/p&gt;

&lt;p&gt;And an attacker can still sit in the middle and run two separate handshakes.&lt;/p&gt;

&lt;p&gt;That is the next lesson.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Still Not Secure — The Man-in-the-Middle Problem&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;At this point, our protocol already looks much more serious than the one we started with.&lt;/p&gt;

&lt;p&gt;We now have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a real handshake&lt;/li&gt;
&lt;li&gt;fresh shared secrets&lt;/li&gt;
&lt;li&gt;X25519 instead of a pre-shared application key&lt;/li&gt;
&lt;li&gt;HKDF-derived session keys&lt;/li&gt;
&lt;li&gt;AEAD-protected application records&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is a long way from the fake secure channel in Part 1.&lt;/p&gt;

&lt;p&gt;But it is still not enough.&lt;/p&gt;

&lt;p&gt;The missing piece is one of the most important ideas in this whole series:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;key exchange is not authentication&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That sentence is easy to read quickly and move on from. But it is worth stopping here, because this is exactly where many protocols fail.&lt;/p&gt;

&lt;p&gt;Our handshake proves that both sides can derive the same shared secret.&lt;/p&gt;

&lt;p&gt;What it does &lt;strong&gt;not&lt;/strong&gt; prove is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;who&lt;/strong&gt; is actually on the other side.&lt;/p&gt;

&lt;p&gt;And that difference is the whole problem.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;The attack&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Imagine an active attacker sitting between the client and the server.&lt;/p&gt;

&lt;p&gt;Let’s call her Mallory.&lt;/p&gt;

&lt;p&gt;The client thinks it is talking to the server.&lt;/p&gt;

&lt;p&gt;The server thinks it is talking to the client.&lt;/p&gt;

&lt;p&gt;But Mallory intercepts the handshake and replaces the exchanged public keys with her own.&lt;/p&gt;

&lt;p&gt;In simplified form, the flow looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiq3eu6p0rk0j2mynzq9z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiq3eu6p0rk0j2mynzq9z.png" alt="attack schema"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And now something very important happens.&lt;/p&gt;

&lt;p&gt;The handshake still “works.”&lt;/p&gt;

&lt;p&gt;But it works in the wrong way.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the &lt;strong&gt;client&lt;/strong&gt; ends up with a shared secret with &lt;strong&gt;Mallory&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;the &lt;strong&gt;server&lt;/strong&gt; ends up with a different shared secret with &lt;strong&gt;Mallory&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;and &lt;strong&gt;Mallory&lt;/strong&gt; now has one valid secure channel to each side&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;From the point of view of the client and the server, everything looks normal:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;key exchange succeeded&lt;/li&gt;
&lt;li&gt;keys were derived&lt;/li&gt;
&lt;li&gt;encrypted records verify correctly&lt;/li&gt;
&lt;li&gt;AEAD tags are valid&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And yet the protocol has already failed.&lt;/p&gt;

&lt;p&gt;Because Mallory can now:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;decrypt the client’s traffic&lt;/li&gt;
&lt;li&gt;read it or modify it&lt;/li&gt;
&lt;li&gt;re-encrypt it toward the server&lt;/li&gt;
&lt;li&gt;receive the server’s response&lt;/li&gt;
&lt;li&gt;read it or modify it&lt;/li&gt;
&lt;li&gt;re-encrypt it back toward the client&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Neither side can detect this.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;In The Next Part — Building the Certificate Infrastructure&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The handshake only proves one thing:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“I computed a shared secret with whoever sent me this public key.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It does &lt;strong&gt;not&lt;/strong&gt; prove:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“This public key came from the server I actually intended to talk to.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is the missing half.&lt;/p&gt;

&lt;p&gt;To fix this, the client needs a way to verify that the public key it receives during the handshake actually belongs to the server it wanted to talk to.&lt;/p&gt;

&lt;p&gt;That is where the next layer enters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;certificates&lt;/li&gt;
&lt;li&gt;signatures&lt;/li&gt;
&lt;li&gt;trust chains&lt;/li&gt;
&lt;li&gt;certificate authorities&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In other words, this is where the protocol must stop proving only that “someone” is there and start proving &lt;strong&gt;who&lt;/strong&gt; that someone is.&lt;/p&gt;

&lt;p&gt;That is exactly what the next article will build.&lt;/p&gt;

&lt;h3&gt;
  
  
  Summary
&lt;/h3&gt;

&lt;p&gt;Our protocol now has secrecy against passive observers.&lt;/p&gt;

&lt;p&gt;It has integrity for protected records.&lt;/p&gt;

&lt;p&gt;It has fresh session keys.&lt;/p&gt;

&lt;p&gt;But it still does not have &lt;strong&gt;identity&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;And without identity, a correct shared secret with the wrong party is still a protocol failure.&lt;/p&gt;

&lt;p&gt;That is the deeper lesson of Part 3.&lt;/p&gt;

&lt;p&gt;Part 1 taught us:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;confidentiality is not integrity&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Part 2 taught us:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;protecting records is not the same thing as establishing trust&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And now Part 3 adds the next lesson:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;key exchange is not authentication&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That we will solve in the next article!&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Final Code&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The full code for this part is available here:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/DmytroHuzz/rebuilding_tls/tree/main/part_3" rel="noopener noreferrer"&gt;https://github.com/DmytroHuzz/rebuilding_tls/tree/main/part_3&lt;/a&gt;&lt;/p&gt;

</description>
      <category>learning</category>
      <category>development</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>Rebuilding TLS, Part 2 — Adding Integrity to the Channel</title>
      <dc:creator>Dmytro Huz</dc:creator>
      <pubDate>Sun, 05 Apr 2026 21:42:10 +0000</pubDate>
      <link>https://dev.to/aws-builders/rebuilding-tls-part-2-adding-integrity-to-the-channel-4k78</link>
      <guid>https://dev.to/aws-builders/rebuilding-tls-part-2-adding-integrity-to-the-channel-4k78</guid>
      <description>&lt;p&gt;In the &lt;a href="https://www.dmytrohuz.com/p/rebuilding-tls-part-1-why-encryption" rel="noopener noreferrer"&gt;first part&lt;/a&gt; of this series, we built our first fake secure channel.&lt;/p&gt;

&lt;p&gt;We took a simple socket-based client and server, wrapped their communication in AES-CTR with a shared secret key, and got something that already looked much more serious than plain TCP. The traffic stopped being transparent. A passive observer could no longer read the request and response directly.&lt;/p&gt;

&lt;p&gt;That was real progress.&lt;/p&gt;

&lt;p&gt;But it still had a fatal flaw.&lt;/p&gt;

&lt;p&gt;The receiver had no way to know whether the encrypted message had been changed on the way.&lt;/p&gt;

&lt;p&gt;Encryption hid the bytes.&lt;/p&gt;

&lt;p&gt;It did not protect their meaning.&lt;/p&gt;

&lt;p&gt;So in this part, we will fix that.&lt;/p&gt;

&lt;p&gt;We will first add a &lt;strong&gt;MAC&lt;/strong&gt; so the receiver can detect tampering. Then we will make the record layer a little less naive by adding a sequence number. And after that, we will take one more step toward the real world and move to &lt;strong&gt;AEAD&lt;/strong&gt;, because that is how modern secure protocols usually protect records.&lt;/p&gt;

&lt;p&gt;We still will not have real TLS when we are done.&lt;/p&gt;

&lt;p&gt;But we will have a much more serious record layer than the one from Part 1.&lt;/p&gt;




&lt;h2&gt;
  
  
  What we will build in this part
&lt;/h2&gt;

&lt;p&gt;The plan for this article is simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;briefly introduce MACs&lt;/li&gt;
&lt;li&gt;add HMAC to our encrypted record format&lt;/li&gt;
&lt;li&gt;make tampering detectable&lt;/li&gt;
&lt;li&gt;add a sequence number to each record&lt;/li&gt;
&lt;li&gt;explain why sequence numbers matter&lt;/li&gt;
&lt;li&gt;then move from our hand-built “encrypt + MAC” construction to AEAD, because that is the approach real systems usually use&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Just like in Part 1, I want to keep the pattern simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;explain the idea&lt;/li&gt;
&lt;li&gt;show the code&lt;/li&gt;
&lt;li&gt;explain what changed&lt;/li&gt;
&lt;li&gt;explain what is still broken&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Why encryption still was not enough
&lt;/h2&gt;

&lt;p&gt;At the end of Part 1, our protocol already had one real property:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;confidentiality against passive observers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That mattered.&lt;/p&gt;

&lt;p&gt;But it still failed against active attackers.&lt;/p&gt;

&lt;p&gt;Because AES-CTR by itself does not provide integrity, an attacker could modify ciphertext and the receiver would still decrypt it and trust the result. That was the main lesson of the first article:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;confidentiality is not integrity&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So the next missing property is obvious.&lt;/p&gt;

&lt;p&gt;The receiver needs a way to verify that the message arrived unchanged.&lt;/p&gt;

&lt;p&gt;That is what a MAC gives us.&lt;/p&gt;




&lt;h2&gt;
  
  
  A very short note on MACs
&lt;/h2&gt;

&lt;p&gt;MAC stands for &lt;strong&gt;Message Authentication Code&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Very roughly, it is a cryptographic tag computed over a message using a secret key.&lt;/p&gt;

&lt;p&gt;The sender computes the tag and sends it together with the message.&lt;/p&gt;

&lt;p&gt;The receiver recomputes the tag and compares it with the one that was received.&lt;/p&gt;

&lt;p&gt;If the tags match, the receiver can trust that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the message was not modified&lt;/li&gt;
&lt;li&gt;and it was created by someone who knows the MAC key&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the tags do not match, the message must be rejected.&lt;/p&gt;

&lt;p&gt;In this article, we will use &lt;strong&gt;HMAC-SHA256&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I do not want to go too deep into HMAC itself here, because the goal of this series is to understand TLS as a protocol. But if you want a deeper explanation of MACs and HMAC, I already wrote about them in my cryptography series, and I’ll link that here: &lt;a href="https://www.dmytrohuz.com/p/building-own-mac-part-3-reinventing" rel="noopener noreferrer"&gt;https://www.dmytrohuz.com/p/building-own-mac-part-3-reinventing&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So for our purposes, the important idea is simple:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;encryption hides the message&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MAC protects the message from silent modification&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That is the missing half we need.&lt;/p&gt;




&lt;h2&gt;
  
  
  Adding HMAC to the channel
&lt;/h2&gt;

&lt;p&gt;Let’s start by upgrading the record format from Part 1.&lt;/p&gt;

&lt;p&gt;In Part 1, our protected payload was basically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nonce || ciphertext
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we will add a MAC tag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nonce || ciphertext || tag
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the sender will compute the HMAC over:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nonce || ciphertext
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the full logic becomes:&lt;/p&gt;

&lt;h3&gt;
  
  
  Sender
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;encrypt plaintext with AES-CTR&lt;/li&gt;
&lt;li&gt;compute HMAC over &lt;code&gt;nonce || ciphertext&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;send &lt;code&gt;nonce || ciphertext || tag&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Receiver
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;read &lt;code&gt;nonce || ciphertext || tag&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;recompute HMAC over &lt;code&gt;nonce || ciphertext&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;compare tags&lt;/li&gt;
&lt;li&gt;only if they match, decrypt the ciphertext&lt;/li&gt;
&lt;li&gt;otherwise reject the message&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There is one more small improvement I want to make here.&lt;/p&gt;

&lt;p&gt;Instead of using one key for everything, we will already separate them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one key for encryption&lt;/li&gt;
&lt;li&gt;one key for HMAC&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is still a toy setup, but it is better design than reusing the same bytes for every cryptographic job.&lt;/p&gt;

&lt;h3&gt;
  
  
  HMAC helpers
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;hmac&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;cryptography.hazmat.primitives.ciphers&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Cipher&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;algorithms&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;modes&lt;/span&gt;

&lt;span class="c1"&gt;# ---------------------------------------------------------------------------
# Keys — hardcoded for educational purposes.
# In a real protocol, these would be derived from a key exchange (e.g.,
# Diffie-Hellman), not embedded in source code.
# ---------------------------------------------------------------------------
&lt;/span&gt;
&lt;span class="c1"&gt;# 32-byte (256-bit) key for AES-256-CTR encryption.
&lt;/span&gt;&lt;span class="n"&gt;ENC_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0123456789ABCDEF0123456789ABCDEF&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;# 32-byte key for HMAC-SHA256.  Separate from the encryption key.
&lt;/span&gt;&lt;span class="n"&gt;MAC_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;HMAC_KEY_FOR_PART2_DEMO_1234567&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;# HMAC-SHA256 produces a 32-byte (256-bit) tag.
&lt;/span&gt;&lt;span class="n"&gt;TAG_LEN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;

&lt;span class="c1"&gt;# AES-CTR nonce is 16 bytes (128 bits).
&lt;/span&gt;&lt;span class="n"&gt;NONCE_LEN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;encrypt_then_mac&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;plaintext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Encrypt a plaintext and append an HMAC tag.

    Returns: nonce (16 B) || ciphertext (N B) || tag (32 B)
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="c1"&gt;# Step 1: Generate a fresh random nonce for AES-CTR.
&lt;/span&gt;    &lt;span class="c1"&gt;# A new nonce MUST be used for every record — reusing a nonce with
&lt;/span&gt;    &lt;span class="c1"&gt;# the same key completely breaks CTR-mode security.
&lt;/span&gt;    &lt;span class="n"&gt;nonce&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urandom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NONCE_LEN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Step 2: Encrypt the plaintext with AES-256-CTR.
&lt;/span&gt;    &lt;span class="n"&gt;cipher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Cipher&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;algorithms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AES&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ENC_KEY&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;modes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CTR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nonce&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;encryptor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cipher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encryptor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;ciphertext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;encryptor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;plaintext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;encryptor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;finalize&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# Step 3: Compute HMAC-SHA256 over (nonce || ciphertext).
&lt;/span&gt;    &lt;span class="c1"&gt;# New in Part 2: we authenticate the encrypted record before sending it.
&lt;/span&gt;    &lt;span class="c1"&gt;# The HMAC input includes the nonce so an attacker cannot swap nonces
&lt;/span&gt;    &lt;span class="c1"&gt;# between records without detection.
&lt;/span&gt;    &lt;span class="n"&gt;mac_input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nonce&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;ciphertext&lt;/span&gt;
    &lt;span class="n"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hmac&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MAC_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mac_input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  [crypto_hmac] encrypt_then_mac:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;    nonce    = &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;nonce&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hex&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;    ct_len   = &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ciphertext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; bytes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;    tag      = &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hex&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Step 4: Assemble the wire format.
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;nonce&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;ciphertext&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;verify_then_decrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Verify the HMAC tag, then decrypt if valid.

    Expects: nonce (16 B) || ciphertext (N B) || tag (32 B)
    Raises ValueError if the tag does not match.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="c1"&gt;# Step 1: Parse the record into its components.
&lt;/span&gt;    &lt;span class="c1"&gt;# The tag is always the last 32 bytes.  The nonce is the first 16.
&lt;/span&gt;    &lt;span class="c1"&gt;# Everything in between is ciphertext.
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;NONCE_LEN&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;TAG_LEN&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Record too short to contain nonce + tag&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;nonce&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="n"&gt;NONCE_LEN&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;ciphertext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;NONCE_LEN&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;TAG_LEN&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;received_tag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;TAG_LEN&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt;

    &lt;span class="c1"&gt;# Step 2: Recompute the HMAC over (nonce || ciphertext).
&lt;/span&gt;    &lt;span class="n"&gt;mac_input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nonce&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;ciphertext&lt;/span&gt;
    &lt;span class="n"&gt;expected_tag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hmac&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MAC_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mac_input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# Step 3: Compare tags using constant-time comparison.
&lt;/span&gt;    &lt;span class="c1"&gt;# hmac.compare_digest() prevents timing side-channel attacks.
&lt;/span&gt;    &lt;span class="c1"&gt;# A naive `==` comparison can leak information about which byte
&lt;/span&gt;    &lt;span class="c1"&gt;# position differs first, allowing an attacker to forge a valid
&lt;/span&gt;    &lt;span class="c1"&gt;# tag byte by byte.
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;hmac&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compare_digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;received_tag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expected_tag&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  [crypto_hmac] *** MAC VERIFICATION FAILED — record rejected ***&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;HMAC verification failed — record has been tampered with&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  [crypto_hmac] MAC verification: OK&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Step 4: Decrypt only after verification succeeds.
&lt;/span&gt;    &lt;span class="c1"&gt;# This is the key benefit of encrypt-then-MAC: we never process
&lt;/span&gt;    &lt;span class="c1"&gt;# unauthenticated ciphertext.
&lt;/span&gt;    &lt;span class="n"&gt;cipher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Cipher&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;algorithms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AES&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ENC_KEY&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;modes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CTR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nonce&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;decryptor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cipher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decryptor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;plaintext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;decryptor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ciphertext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;decryptor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;finalize&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;plaintext&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the first big improvement over Part 1.&lt;/p&gt;

&lt;p&gt;The important change is not just that we added a tag.&lt;/p&gt;

&lt;p&gt;It is that the receiver no longer blindly trusts ciphertext and only then discovers what it means. Now the receiver first checks whether the record is authentic and unchanged.&lt;/p&gt;

&lt;p&gt;That is a very different protocol posture.&lt;/p&gt;




&lt;h2&gt;
  
  
  Updating the client and server
&lt;/h2&gt;

&lt;p&gt;Now let’s plug this into the channel.&lt;/p&gt;

&lt;h3&gt;
  
  
  HMAC-based client
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;framing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;send_record&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;recv_record&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;crypto_hmac&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;encrypt_then_mac&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;verify_then_decrypt&lt;/span&gt;

&lt;span class="n"&gt;HOST&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;127.0.0.1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;9001&lt;/span&gt;

&lt;span class="c1"&gt;# A toy HTTP-like request — same spirit as Part 1.
&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;GET /transfer?to=bob&amp;amp;amount=100 HTTP/1.1&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s"&gt;Host: localhost&lt;/span&gt;&lt;span class="se"&gt;\r\n\r\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Part 2 — HMAC Client (encrypt-then-MAC)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AF_INET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SOCK_STREAM&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;HOST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Connected to &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;HOST&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;PORT&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# ----- SEND REQUEST -----
&lt;/span&gt;    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;--- Sending request ---&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;protected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;encrypt_then_mac&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;send_record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;protected&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  Record sent (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;protected&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; bytes on wire)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# ----- RECEIVE RESPONSE -----
&lt;/span&gt;    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;--- Receiving response ---&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;raw_response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;recv_record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;verify_then_decrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw_response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;  Decrypted response:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;  &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Done.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  HMAC-based server
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;framing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;send_record&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;recv_record&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;crypto_hmac&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;encrypt_then_mac&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;verify_then_decrypt&lt;/span&gt;

&lt;span class="n"&gt;HOST&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;127.0.0.1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;9001&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Part 2 — HMAC Server (encrypt-then-MAC)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AF_INET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SOCK_STREAM&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# SO_REUSEADDR lets us restart the server immediately without waiting
&lt;/span&gt;    &lt;span class="c1"&gt;# for the OS to release the port from TIME_WAIT state.
&lt;/span&gt;    &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setsockopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SOL_SOCKET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SO_REUSEADDR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;HOST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Listening on &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;HOST&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;PORT&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;addr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;accept&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Connected by &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;addr&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# ----- RECEIVE REQUEST -----
&lt;/span&gt;        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;--- Receiving request ---&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;raw_request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;recv_record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;verify_then_decrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw_request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;ValueError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# New in Part 2: if the MAC fails, we reject the record loudly.
&lt;/span&gt;            &lt;span class="c1"&gt;# In Part 1 we had no way to detect tampering at all.
&lt;/span&gt;            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;  *** REJECTED: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; ***&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  Connection closed — refusing to process tampered data.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;  Decrypted request:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;  &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="c1"&gt;# ----- SEND RESPONSE -----
&lt;/span&gt;            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--- Sending response ---&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;HTTP/1.1 200 OK&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Type: text/plain&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Length: 13&lt;/span&gt;&lt;span class="se"&gt;\r\n\r\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hello, client&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="n"&gt;protected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;encrypt_then_mac&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;send_record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;protected&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  Record sent (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;protected&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; bytes on wire)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Done.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The shape of the channel is still familiar.&lt;/p&gt;

&lt;p&gt;That matters.&lt;/p&gt;

&lt;p&gt;We did not replace the whole design.&lt;/p&gt;

&lt;p&gt;We strengthened one missing property.&lt;/p&gt;

&lt;p&gt;That is how protocol evolution should feel.&lt;/p&gt;




&lt;h4&gt;
  
  
  Let’s check it on the wire.
&lt;/h4&gt;

&lt;p&gt;We try to start the server and client, which we just created.&lt;/p&gt;

&lt;p&gt;Here is our client request’s data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="n"&gt;Sending&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;---&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;crypto_hmac&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;encrypt_then_mac&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="n"&gt;nonce&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;387&lt;/span&gt;&lt;span class="n"&gt;f0065f8915133473597d8cef15f34&lt;/span&gt;&lt;span class="bp"&gt;...&lt;/span&gt;
&lt;span class="n"&gt;ct_len&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;61&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;
&lt;span class="n"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;b7f0db369e5d2a221a18f2d167b5b3a8&lt;/span&gt;&lt;span class="bp"&gt;...&lt;/span&gt;
&lt;span class="n"&gt;Record&lt;/span&gt; &lt;span class="nf"&gt;sent &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;109&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt; &lt;span class="n"&gt;on&lt;/span&gt; &lt;span class="n"&gt;wire&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsbymray8ejip4sgnfvha.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsbymray8ejip4sgnfvha.png" alt="wireshark"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Detecting tampering
&lt;/h2&gt;

&lt;p&gt;Now let’s revisit the failure from Part 1.&lt;/p&gt;

&lt;p&gt;Previously, if someone modified the ciphertext, the receiver would still decrypt it and accept modified plaintext.&lt;/p&gt;

&lt;p&gt;Now that should no longer work.&lt;/p&gt;

&lt;p&gt;Here is a tiny tampering demo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# tampering_demo_hmac.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;crypto_hmac&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;encrypt_then_mac&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;verify_then_decrypt&lt;/span&gt;

&lt;span class="n"&gt;original&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;amount=100&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;protected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;encrypt_then_mac&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;original&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;tampered&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;bytearray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;protected&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;tampered&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;^=&lt;/span&gt; &lt;span class="mh"&gt;0x08&lt;/span&gt;  &lt;span class="c1"&gt;# flip one bit somewhere in the encrypted body
&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;verify_then_decrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tampered&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Unexpected success:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;ValueError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Tampering detected:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the result should be rejection, not silent acceptance.&lt;/p&gt;

&lt;p&gt;That is exactly what we wanted.&lt;/p&gt;

&lt;p&gt;This is the moment where our channel stops being merely “encrypted” and starts being “protected.”&lt;/p&gt;

&lt;p&gt;Because now the receiver does not just recover bytes. It verifies them first.&lt;/p&gt;

&lt;p&gt;That is a serious step.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why we also need a sequence number
&lt;/h2&gt;

&lt;p&gt;At this point, we fixed the big flaw from Part 1: silent tampering.&lt;/p&gt;

&lt;p&gt;But the record layer is still naive.&lt;/p&gt;

&lt;p&gt;Why?&lt;/p&gt;

&lt;p&gt;Because even with a valid HMAC, the receiver still has no sense of record position or freshness.&lt;/p&gt;

&lt;p&gt;Imagine an attacker records one valid protected message and sends it again later.&lt;/p&gt;

&lt;p&gt;The HMAC is still valid.&lt;/p&gt;

&lt;p&gt;The ciphertext is still valid.&lt;/p&gt;

&lt;p&gt;And unless the receiver keeps some state, it may accept the same record again.&lt;/p&gt;

&lt;p&gt;That means integrity alone is not the whole story.&lt;/p&gt;

&lt;p&gt;We also need some sense of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;order&lt;/li&gt;
&lt;li&gt;position&lt;/li&gt;
&lt;li&gt;repetition&lt;/li&gt;
&lt;li&gt;replay&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is where sequence numbers come in.&lt;/p&gt;

&lt;p&gt;A sequence number is just a counter that increases with every record:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;first record = 0&lt;/li&gt;
&lt;li&gt;next = 1&lt;/li&gt;
&lt;li&gt;next = 2&lt;/li&gt;
&lt;li&gt;and so on&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We then include that sequence number in the authenticated data, so the receiver does not just verify “these bytes were protected,” but also “these bytes belong in this position in the stream.”&lt;/p&gt;

&lt;p&gt;That makes the record layer much less naive.&lt;/p&gt;

&lt;p&gt;It still does not solve every replay problem in every possible system. But for our toy protocol, it is a very good next step.&lt;/p&gt;




&lt;h2&gt;
  
  
  Updating the record format
&lt;/h2&gt;

&lt;p&gt;Now our record becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;seq || nonce || ciphertext || tag
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And our HMAC input becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;seq || nonce || ciphertext
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the sender and receiver now both need a little bit of state:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the sender tracks the next sequence number to send&lt;/li&gt;
&lt;li&gt;the receiver tracks the next sequence number it expects&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is one of those moments where secure transport starts looking more like a real protocol and less like “some crypto around a socket.”&lt;/p&gt;

&lt;h3&gt;
  
  
  Sequence-aware HMAC helper
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# crypto_hmac_seq.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;hmac&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;struct&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;cryptography.hazmat.primitives.ciphers&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Cipher&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;algorithms&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;modes&lt;/span&gt;

&lt;span class="c1"&gt;# ---------------------------------------------------------------------------
# Keys — same as crypto_hmac.py, hardcoded for education.
# ---------------------------------------------------------------------------
&lt;/span&gt;&lt;span class="n"&gt;ENC_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0123456789ABCDEF0123456789ABCDEF&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;MAC_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;HMAC_KEY_FOR_PART2_DEMO_1234567&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;TAG_LEN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;  &lt;span class="c1"&gt;# HMAC-SHA256 output: 32 bytes (256 bits)
&lt;/span&gt;&lt;span class="n"&gt;NONCE_LEN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;  &lt;span class="c1"&gt;# AES-CTR nonce: 16 bytes (128 bits)
&lt;/span&gt;&lt;span class="n"&gt;SEQ_LEN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;  &lt;span class="c1"&gt;# Sequence number: 8 bytes (64-bit unsigned integer)
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;protect_record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seq&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;plaintext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Encrypt a plaintext record and attach a sequence-aware HMAC tag.

    Args:
        seq:       The current send-side sequence number (0, 1, 2, …).
        plaintext: The message to protect.

    Returns:
        seq (8 B) || nonce (16 B) || ciphertext (N B) || tag (32 B)
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="c1"&gt;# Pack the sequence number as an 8-byte big-endian unsigned integer.
&lt;/span&gt;    &lt;span class="c1"&gt;# "!Q" = network byte order, unsigned 64-bit.
&lt;/span&gt;    &lt;span class="n"&gt;seq_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;!Q&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;seq&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Generate a fresh AES-CTR nonce.
&lt;/span&gt;    &lt;span class="n"&gt;nonce&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urandom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NONCE_LEN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Encrypt the plaintext.
&lt;/span&gt;    &lt;span class="n"&gt;cipher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Cipher&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;algorithms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AES&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ENC_KEY&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;modes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CTR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nonce&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;encryptor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cipher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encryptor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;ciphertext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;encryptor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;plaintext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;encryptor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;finalize&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# Compute HMAC over (seq || nonce || ciphertext).
&lt;/span&gt;    &lt;span class="c1"&gt;# The sequence number is included in the MAC input so the integrity
&lt;/span&gt;    &lt;span class="c1"&gt;# check also covers record order/position in the stream.
&lt;/span&gt;    &lt;span class="n"&gt;mac_input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;seq_bytes&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;nonce&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;ciphertext&lt;/span&gt;
    &lt;span class="n"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hmac&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MAC_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mac_input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;seq_bytes&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;nonce&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;ciphertext&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;verify_and_unprotect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expected_seq&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Verify the HMAC and sequence number, then decrypt.

    Args:
        expected_seq: The sequence number the receiver expects next.
        payload:      The raw bytes received: seq || nonce || ct || tag.

    Returns:
        The decrypted plaintext.

    Raises:
        ValueError if the MAC is invalid or the sequence number is wrong.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="n"&gt;min_len&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SEQ_LEN&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;NONCE_LEN&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;TAG_LEN&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;min_len&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Record too short&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Step 1: Parse the record.
&lt;/span&gt;    &lt;span class="n"&gt;seq_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="n"&gt;SEQ_LEN&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;nonce&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;SEQ_LEN&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;SEQ_LEN&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;NONCE_LEN&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;ciphertext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;SEQ_LEN&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;NONCE_LEN&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;TAG_LEN&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;received_tag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;TAG_LEN&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt;

    &lt;span class="c1"&gt;# Step 2: Recompute HMAC over (seq || nonce || ciphertext).
&lt;/span&gt;    &lt;span class="n"&gt;mac_input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;seq_bytes&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;nonce&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;ciphertext&lt;/span&gt;
    &lt;span class="n"&gt;expected_tag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hmac&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MAC_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mac_input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# Step 3: Constant-time tag comparison.
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;hmac&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compare_digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;received_tag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expected_tag&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  [crypto_hmac_seq] *** MAC VERIFICATION FAILED ***&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;HMAC verification failed — record tampered or replayed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  [crypto_hmac_seq] MAC verification: OK&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Step 4: Check the sequence number matches what we expect.
&lt;/span&gt;    &lt;span class="c1"&gt;# Even though the MAC already covers the sequence number (so an
&lt;/span&gt;    &lt;span class="c1"&gt;# attacker cannot change it without invalidating the MAC), we still
&lt;/span&gt;    &lt;span class="c1"&gt;# explicitly verify that it matches our counter.  This catches
&lt;/span&gt;    &lt;span class="c1"&gt;# replayed or reordered records that carry a valid MAC but belong
&lt;/span&gt;    &lt;span class="c1"&gt;# to a different position in the stream.
&lt;/span&gt;    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;received_seq&lt;/span&gt;&lt;span class="p"&gt;,)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unpack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;!Q&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;seq_bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;received_seq&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;expected_seq&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  [crypto_hmac_seq] *** SEQUENCE MISMATCH: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;got &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;received_seq&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, expected &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;expected_seq&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; ***&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Sequence number mismatch: got &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;received_seq&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, expected &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;expected_seq&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  [crypto_hmac_seq] Sequence number: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;received_seq&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; (expected &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;expected_seq&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;) — OK&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Step 5: Decrypt.
&lt;/span&gt;    &lt;span class="n"&gt;cipher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Cipher&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;algorithms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AES&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ENC_KEY&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;modes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CTR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nonce&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;decryptor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cipher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decryptor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;plaintext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;decryptor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ciphertext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;decryptor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;finalize&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;plaintext&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And now the channel has a bit more memory.&lt;/p&gt;

&lt;p&gt;Not just “is this record authentic?”&lt;/p&gt;

&lt;p&gt;But also “is this the record I expected next?”&lt;/p&gt;

&lt;p&gt;That is a real protocol improvement.&lt;/p&gt;




&lt;h2&gt;
  
  
  Updating the client and server
&lt;/h2&gt;

&lt;p&gt;Now let’s plug this into the channel.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sequence-aware HMAC-based client
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# client_v2_hmac_seq.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;framing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;send_record&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;recv_record&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;crypto_hmac_seq&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;protect_record&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;verify_and_unprotect&lt;/span&gt;

&lt;span class="n"&gt;HOST&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;127.0.0.1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;9003&lt;/span&gt;

&lt;span class="c1"&gt;# Sequence counters — sender and receiver each maintain their own.
# The sender increments after each record sent.
# The receiver expects consecutive values starting from 0.
&lt;/span&gt;&lt;span class="n"&gt;send_seq&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;recv_seq&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

&lt;span class="c1"&gt;# A toy HTTP-like request — same spirit as Part 1.
&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;GET /transfer?to=bob&amp;amp;amount=100 HTTP/1.1&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s"&gt;Host: localhost&lt;/span&gt;&lt;span class="se"&gt;\r\n\r\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Part 2 — HMAC + Sequence Numbers Client (Stage 2)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AF_INET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SOCK_STREAM&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;HOST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Connected to &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;HOST&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;PORT&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# ----- SEND REQUEST -----
&lt;/span&gt;    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;--- Sending request (send_seq=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;send_seq&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;) ---&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;protected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;protect_record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;send_seq&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;send_record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;protected&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;send_seq&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  Record sent (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;protected&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; bytes on wire)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# ----- RECEIVE RESPONSE -----
&lt;/span&gt;    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;--- Receiving response (expecting recv_seq=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;recv_seq&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;) ---&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;raw_response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;recv_record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;verify_and_unprotect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;recv_seq&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;raw_response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;recv_seq&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;  Decrypted response:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;  &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;ValueError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;  *** REJECTED: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; ***&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Done.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Sequence-aware HMAC-based server
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# server_v2_hmac_seq.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;framing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;send_record&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;recv_record&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;crypto_hmac_seq&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;protect_record&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;verify_and_unprotect&lt;/span&gt;

&lt;span class="n"&gt;HOST&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;127.0.0.1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;9003&lt;/span&gt;

&lt;span class="c1"&gt;# Sequence counters.
# The server's recv_seq tracks the client's send_seq, and vice versa.
&lt;/span&gt;&lt;span class="n"&gt;send_seq&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;recv_seq&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Part 2 — HMAC + Sequence Numbers Server (Stage 2)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AF_INET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SOCK_STREAM&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setsockopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SOL_SOCKET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SO_REUSEADDR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;HOST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Listening on &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;HOST&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;PORT&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;addr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;accept&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Connected by &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;addr&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# ----- RECEIVE REQUEST -----
&lt;/span&gt;        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;--- Receiving request (expecting recv_seq=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;recv_seq&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;) ---&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;raw_request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;recv_record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;verify_and_unprotect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;recv_seq&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;raw_request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;recv_seq&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;ValueError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# Rejection: either the MAC is invalid, the sequence number
&lt;/span&gt;            &lt;span class="c1"&gt;# is wrong, or the data was tampered with / replayed.
&lt;/span&gt;            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;  *** REJECTED: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; ***&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  Connection closed — refusing to process invalid data.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;  Decrypted request:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;  &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="c1"&gt;# ----- SEND RESPONSE -----
&lt;/span&gt;            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--- Sending response (send_seq=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;send_seq&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;) ---&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;HTTP/1.1 200 OK&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Type: text/plain&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Length: 13&lt;/span&gt;&lt;span class="se"&gt;\r\n\r\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hello, client&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="n"&gt;protected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;protect_record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;send_seq&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;send_record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;protected&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;send_seq&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  Record sent (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;protected&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; bytes on wire)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Done.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Why real-world systems usually do not stop here
&lt;/h2&gt;

&lt;p&gt;At this point, we have something much stronger than Part 1.&lt;/p&gt;

&lt;p&gt;We have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;encryption&lt;/li&gt;
&lt;li&gt;integrity protection&lt;/li&gt;
&lt;li&gt;message authentication&lt;/li&gt;
&lt;li&gt;sequence-aware records&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is already a meaningful protocol.&lt;/p&gt;

&lt;p&gt;But if you look at how real systems are usually built, they do not normally stop at manually composing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AES-CTR&lt;/li&gt;
&lt;li&gt;HMAC-SHA256&lt;/li&gt;
&lt;li&gt;explicit sequence-aware record protection&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Why?&lt;/p&gt;

&lt;p&gt;Because modern systems usually prefer a single primitive that gives confidentiality and integrity together.&lt;/p&gt;

&lt;p&gt;That is where &lt;strong&gt;AEAD&lt;/strong&gt; comes in.&lt;/p&gt;

&lt;p&gt;We separated these properties on purpose because it makes the protocol easier to understand.&lt;/p&gt;

&lt;p&gt;But the real world usually packages them together.&lt;/p&gt;




&lt;h2&gt;
  
  
  A very short note on AEAD
&lt;/h2&gt;

&lt;p&gt;AEAD stands for &lt;strong&gt;Authenticated Encryption with Associated Data&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That sounds heavier than it really is.&lt;/p&gt;

&lt;p&gt;The practical idea is simple:&lt;/p&gt;

&lt;p&gt;An AEAD construction gives us:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;encryption&lt;/li&gt;
&lt;li&gt;integrity/authentication of the encrypted message&lt;/li&gt;
&lt;li&gt;and the ability to authenticate extra metadata that should not be encrypted&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Common examples are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AES-GCM&lt;/li&gt;
&lt;li&gt;ChaCha20-Poly1305&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is much closer to how modern secure protocols protect records.&lt;/p&gt;

&lt;p&gt;It is also why I wanted to include AEAD in this part. If we stopped only at “encrypt + HMAC,” we would understand the missing property better, but we would still be one step away from how modern systems actually package it.&lt;/p&gt;

&lt;p&gt;So now we take that final step.&lt;/p&gt;




&lt;h2&gt;
  
  
  Moving our channel to AEAD
&lt;/h2&gt;

&lt;p&gt;For the AEAD version, I will use &lt;strong&gt;AES-GCM&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The high-level idea is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the plaintext gets encrypted&lt;/li&gt;
&lt;li&gt;integrity/authentication is built in&lt;/li&gt;
&lt;li&gt;and we can include extra metadata as associated data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In our case, the sequence number is a good example of associated data.&lt;/p&gt;

&lt;p&gt;That means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;it does not need to be encrypted&lt;/li&gt;
&lt;li&gt;but it should still be authenticated&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  AEAD-based helper
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# crypto_aead.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;struct&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;cryptography.hazmat.primitives.ciphers.aead&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AESGCM&lt;/span&gt;

&lt;span class="c1"&gt;# ---------------------------------------------------------------------------
# Key — a single 256-bit key for AES-GCM.
# With AEAD, we do NOT need separate encryption and MAC keys — the
# algorithm handles both internally.
# ---------------------------------------------------------------------------
&lt;/span&gt;&lt;span class="n"&gt;AEAD_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AEAD_KEY_PART2_DEMO_FOR_AES_GCM!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;  &lt;span class="c1"&gt;# 32 bytes → AES-256-GCM
&lt;/span&gt;
&lt;span class="c1"&gt;# AES-GCM nonce length: 12 bytes is the recommended (and most efficient) size.
&lt;/span&gt;&lt;span class="n"&gt;NONCE_LEN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;

&lt;span class="c1"&gt;# Sequence number: 8 bytes (64-bit unsigned integer), same as Stage 2.
&lt;/span&gt;&lt;span class="n"&gt;SEQ_LEN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;protect_record_aead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seq&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;plaintext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Seal a plaintext record with AES-GCM.

    Args:
        seq:       The current send-side sequence number.
        plaintext: The message to protect.

    Returns:
        seq (8 B) || nonce (12 B) || ciphertext_and_tag (N+16 B)
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="c1"&gt;# Pack the sequence number as associated data.
&lt;/span&gt;    &lt;span class="c1"&gt;# The sequence number is authenticated but sent in the clear — the
&lt;/span&gt;    &lt;span class="c1"&gt;# receiver needs it to know which counter value to expect.
&lt;/span&gt;    &lt;span class="n"&gt;seq_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;!Q&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;seq&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Generate a random 12-byte nonce for AES-GCM.
&lt;/span&gt;    &lt;span class="n"&gt;nonce&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urandom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NONCE_LEN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Create an AESGCM instance with our key.
&lt;/span&gt;    &lt;span class="n"&gt;aesgcm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AESGCM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AEAD_KEY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Encrypt and authenticate in one call.
&lt;/span&gt;    &lt;span class="c1"&gt;# AESGCM.encrypt(nonce, data, associated_data) returns
&lt;/span&gt;    &lt;span class="c1"&gt;# ciphertext || 16-byte authentication tag as a single bytes object.
&lt;/span&gt;    &lt;span class="c1"&gt;# The associated_data (seq_bytes) is authenticated but NOT encrypted.
&lt;/span&gt;    &lt;span class="n"&gt;ciphertext_and_tag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;aesgcm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nonce&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;plaintext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;seq_bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  [crypto_aead] protect_record_aead:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;    seq      = &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;seq&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;    nonce    = &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;nonce&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hex&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;    sealed   = &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ciphertext_and_tag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; bytes &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;(plaintext &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;plaintext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; + tag 16)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;seq_bytes&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;nonce&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;ciphertext_and_tag&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;unprotect_record_aead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expected_seq&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Verify and decrypt an AES-GCM sealed record.

    Args:
        expected_seq: The sequence number the receiver expects next.
        payload:      seq (8 B) || nonce (12 B) || ciphertext_and_tag.

    Returns:
        The decrypted plaintext.

    Raises:
        ValueError if the sequence number is wrong.
        cryptography.exceptions.InvalidTag if decryption/auth fails.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="n"&gt;min_len&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SEQ_LEN&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;NONCE_LEN&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;  &lt;span class="c1"&gt;# at least seq + nonce + tag
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;min_len&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Record too short&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Step 1: Parse the record.
&lt;/span&gt;    &lt;span class="n"&gt;seq_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="n"&gt;SEQ_LEN&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;nonce&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;SEQ_LEN&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;SEQ_LEN&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;NONCE_LEN&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;ciphertext_and_tag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;SEQ_LEN&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;NONCE_LEN&lt;/span&gt; &lt;span class="p"&gt;:]&lt;/span&gt;

    &lt;span class="c1"&gt;# Step 2: Check the sequence number.
&lt;/span&gt;    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;received_seq&lt;/span&gt;&lt;span class="p"&gt;,)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unpack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;!Q&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;seq_bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;received_seq&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;expected_seq&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  [crypto_aead] *** SEQUENCE MISMATCH: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;got &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;received_seq&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, expected &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;expected_seq&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; ***&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Sequence number mismatch: got &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;received_seq&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, expected &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;expected_seq&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  [crypto_aead] Sequence number: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;received_seq&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;(expected &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;expected_seq&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;) — OK&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Step 3: Decrypt and verify in one call.
&lt;/span&gt;    &lt;span class="c1"&gt;# AESGCM.decrypt(nonce, data, associated_data) verifies the auth tag
&lt;/span&gt;    &lt;span class="c1"&gt;# and decrypts.  If anything was tampered with — the ciphertext, the
&lt;/span&gt;    &lt;span class="c1"&gt;# tag, or the associated data — it raises InvalidTag.
&lt;/span&gt;    &lt;span class="n"&gt;aesgcm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AESGCM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AEAD_KEY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;plaintext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;aesgcm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nonce&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ciphertext_and_tag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;seq_bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  [crypto_aead] AEAD decryption: OK (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;plaintext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; bytes)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;plaintext&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code is noticeably simpler.&lt;/p&gt;

&lt;p&gt;That is one of the big practical advantages of AEAD.&lt;/p&gt;

&lt;p&gt;Instead of manually:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;encrypting&lt;/li&gt;
&lt;li&gt;computing HMAC&lt;/li&gt;
&lt;li&gt;verifying HMAC&lt;/li&gt;
&lt;li&gt;then decrypting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;we use one primitive that already combines confidentiality and integrity.&lt;/p&gt;

&lt;p&gt;And the sequence number fits naturally as associated data.&lt;/p&gt;

&lt;h3&gt;
  
  
  AEAD-based client
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# client_v2_aead.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;framing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;send_record&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;recv_record&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;crypto_aead&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;protect_record_aead&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;unprotect_record_aead&lt;/span&gt;

&lt;span class="n"&gt;HOST&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;127.0.0.1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;9002&lt;/span&gt;

&lt;span class="c1"&gt;# Sequence counters — sender and receiver each maintain their own.
# The sender increments after each record sent.
# The receiver expects consecutive values starting from 0.
&lt;/span&gt;&lt;span class="n"&gt;send_seq&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;recv_seq&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

&lt;span class="c1"&gt;# A toy HTTP-like request.
&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;GET /transfer?to=bob&amp;amp;amount=100 HTTP/1.1&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s"&gt;Host: localhost&lt;/span&gt;&lt;span class="se"&gt;\r\n\r\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Part 2 — AEAD Client (AES-GCM)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AF_INET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SOCK_STREAM&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;HOST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Connected to &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;HOST&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;PORT&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# ----- SEND REQUEST -----
&lt;/span&gt;    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;--- Sending request (send_seq=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;send_seq&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;) ---&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;protected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;protect_record_aead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;send_seq&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;send_record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;protected&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;send_seq&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  Record sent (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;protected&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; bytes on wire)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# ----- RECEIVE RESPONSE -----
&lt;/span&gt;    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;--- Receiving response (expecting recv_seq=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;recv_seq&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;) ---&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;raw_response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;recv_record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;unprotect_record_aead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;recv_seq&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;raw_response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;recv_seq&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;  Decrypted response:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;  &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;  *** REJECTED: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; ***&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Done.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  AEAD-based server
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# server_v2_aead.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;framing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;send_record&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;recv_record&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;crypto_aead&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;protect_record_aead&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;unprotect_record_aead&lt;/span&gt;

&lt;span class="n"&gt;HOST&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;127.0.0.1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;9002&lt;/span&gt;

&lt;span class="c1"&gt;# Sequence counters.
# The server's recv_seq tracks the client's send_seq, and vice versa.
&lt;/span&gt;&lt;span class="n"&gt;send_seq&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;recv_seq&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Part 2 — AEAD Server (AES-GCM)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AF_INET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SOCK_STREAM&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setsockopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SOL_SOCKET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SO_REUSEADDR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;HOST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Listening on &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;HOST&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;PORT&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;addr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;accept&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Connected by &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;addr&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# ----- RECEIVE REQUEST -----
&lt;/span&gt;        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;--- Receiving request (expecting recv_seq=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;recv_seq&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;) ---&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;raw_request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;recv_record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;unprotect_record_aead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;recv_seq&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;raw_request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;recv_seq&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# AEAD rejection: either the auth tag is invalid, the sequence
&lt;/span&gt;            &lt;span class="c1"&gt;# number is wrong, or the data was tampered with.
&lt;/span&gt;            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;  *** REJECTED: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; ***&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  Connection closed — refusing to process invalid data.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;  Decrypted request:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;  &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="c1"&gt;# ----- SEND RESPONSE -----
&lt;/span&gt;            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--- Sending response (send_seq=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;send_seq&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;) ---&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;HTTP/1.1 200 OK&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Type: text/plain&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Length: 13&lt;/span&gt;&lt;span class="se"&gt;\r\n\r\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hello, client&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="n"&gt;protected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;protect_record_aead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;send_seq&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;send_record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;protected&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;send_seq&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  Record sent (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;protected&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; bytes on wire)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Done.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This version is already much closer to how modern secure transport actually protects records.&lt;/p&gt;

&lt;p&gt;Not identical to TLS, of course. But structurally much closer.&lt;/p&gt;




&lt;h2&gt;
  
  
  What we gained
&lt;/h2&gt;

&lt;p&gt;At this point, our channel is much stronger than the one from Part 1.&lt;/p&gt;

&lt;p&gt;We now have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;confidentiality&lt;/li&gt;
&lt;li&gt;integrity protection&lt;/li&gt;
&lt;li&gt;authenticated records&lt;/li&gt;
&lt;li&gt;sequence-aware message handling&lt;/li&gt;
&lt;li&gt;a much more realistic record protection design through AEAD&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is a big improvement.&lt;/p&gt;

&lt;p&gt;The receiver is no longer just decrypting whatever arrives and trusting the result. Now the receiver can reject modified or structurally unexpected records.&lt;/p&gt;

&lt;p&gt;That is a real protocol boundary.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is still broken
&lt;/h2&gt;

&lt;p&gt;And yet, even now, we are still very far from real TLS.&lt;/p&gt;

&lt;p&gt;Because the biggest assumption in our design is still untouched:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;both sides already share the necessary secret keys&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That means we still do not know how to solve the next real problem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;how do two strangers establish fresh secrets?&lt;/li&gt;
&lt;li&gt;how does the client know it is talking to the right server?&lt;/li&gt;
&lt;li&gt;how do we scale beyond hardcoded shared secrets?&lt;/li&gt;
&lt;li&gt;how do we build trust instead of assuming it?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We improved record protection a lot.&lt;/p&gt;

&lt;p&gt;But we still do not have a real way to establish trust.&lt;/p&gt;

&lt;p&gt;That is the next wall.&lt;/p&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;In this part, we took the encrypted but still incomplete channel from Part 1 and made it much more serious.&lt;/p&gt;

&lt;p&gt;First, we added &lt;strong&gt;HMAC&lt;/strong&gt;, which gave the receiver a way to detect tampering.&lt;/p&gt;

&lt;p&gt;Then, we added a &lt;strong&gt;sequence number&lt;/strong&gt;, which made the record layer less naive and bound records to their place in the stream.&lt;/p&gt;

&lt;p&gt;Finally, we moved to &lt;strong&gt;AEAD&lt;/strong&gt;, because in real-world systems confidentiality and integrity are usually protected together, not assembled manually from separate pieces.&lt;/p&gt;

&lt;p&gt;So this article had two goals:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;understand the missing property explicitly&lt;/li&gt;
&lt;li&gt;then move toward the real-world shape of the solution&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is why we did not stop at HMAC.&lt;/p&gt;

&lt;p&gt;But even after all of this, we still depend on one assumption that makes the whole thing unrealistic:&lt;/p&gt;

&lt;p&gt;we are still starting with pre-shared secret keys.&lt;/p&gt;

&lt;p&gt;And that is exactly what the next part will attack.&lt;/p&gt;




&lt;h2&gt;
  
  
  Next part — getting rid of the pre-shared key
&lt;/h2&gt;

&lt;p&gt;So we stop here.&lt;/p&gt;

&lt;p&gt;We now have a much better record layer than in Part 1. But we are still relying on a hardcoded shared secret, and that is not how real secure communication between strangers on the internet works.&lt;/p&gt;

&lt;p&gt;In the next part, we will stop assuming both sides already share a secret.&lt;/p&gt;

&lt;p&gt;We will start building a real handshake and establish fresh session keys instead.&lt;/p&gt;

&lt;p&gt;That still will not give us full TLS.&lt;/p&gt;

&lt;p&gt;But it will take us much closer to the real shape of the protocol.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final code
&lt;/h2&gt;

&lt;p&gt;I’ll put the full code for this part on GitHub here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/DmytroHuzz/rebuilding_tls/tree/main/part_2" rel="noopener noreferrer"&gt;&lt;strong&gt;[GitHub link to final code]&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>networking</category>
      <category>programming</category>
      <category>security</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Rebuilding TLS, Part 1 — Why Encryption Alone Is Not Enough</title>
      <dc:creator>Dmytro Huz</dc:creator>
      <pubDate>Sun, 29 Mar 2026 19:08:43 +0000</pubDate>
      <link>https://dev.to/aws-builders/rebuilding-tls-part-1-a-fake-secure-channel-p0n</link>
      <guid>https://dev.to/aws-builders/rebuilding-tls-part-1-a-fake-secure-channel-p0n</guid>
      <description>&lt;p&gt;A year ago I wrote a series about how a web server works.&lt;/p&gt;

&lt;p&gt;I started from a very primitive version and step by step moved toward the same core ideas modern production servers rely on. When I finished that series, I thought the next step would be small.&lt;/p&gt;

&lt;p&gt;Wrap it in TLS. Make the communication secure.&lt;/p&gt;

&lt;p&gt;It did not stay small for long.&lt;/p&gt;

&lt;p&gt;What looked like a thin security layer on top of an existing server turned into a much deeper journey into cryptography, authentication, trust, certificates, protocol design, and many details usually hidden behind one familiar phrase: &lt;strong&gt;secure connection&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So this series is my attempt to approach TLS the same way I approached the web server: not as a finished black box, but as something we can rebuild from simpler pieces until its shape starts to make sense.&lt;/p&gt;

&lt;p&gt;In this first part, we will start with the most naive version of the problem.&lt;/p&gt;

&lt;p&gt;We will build a very simple socket-based communication channel, see that it is fully transparent, wrap it in encryption with a shared secret key, and then see why that is still not enough.&lt;/p&gt;

&lt;p&gt;That will give us our first fake secure channel.&lt;/p&gt;

&lt;p&gt;And that is exactly where we should start.&lt;/p&gt;




&lt;h2&gt;
  
  
  What we will build in this part
&lt;/h2&gt;

&lt;p&gt;The plan for this article is simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;build a tiny socket-based client and server&lt;/li&gt;
&lt;li&gt;send plain text between them&lt;/li&gt;
&lt;li&gt;look at the traffic and see that everything is visible&lt;/li&gt;
&lt;li&gt;add shared-key encryption with AES-CTR&lt;/li&gt;
&lt;li&gt;make the traffic unreadable&lt;/li&gt;
&lt;li&gt;then show why encryption alone still does not give us a trustworthy secure protocol&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We are not trying to build real TLS yet.&lt;/p&gt;

&lt;p&gt;We are trying to make the first mistake on purpose.&lt;/p&gt;

&lt;p&gt;Because once that mistake becomes visible, the next piece of the protocol stops looking optional.&lt;/p&gt;




&lt;h2&gt;
  
  
  TLS is not SSL
&lt;/h2&gt;

&lt;p&gt;Before we start, one small clarification.&lt;/p&gt;

&lt;p&gt;People still often say “SSL” when they talk about secure communication on the web. But SSL is the older family of protocols. TLS is its successor.&lt;/p&gt;

&lt;p&gt;So when people say things like “SSL certificate” or “SSL connection,” in practice they usually mean TLS.&lt;/p&gt;

&lt;p&gt;For modern systems, the relevant protocols are TLS, especially TLS 1.2 and TLS 1.3. This series is about understanding the ideas behind TLS by rebuilding simpler versions of the problems it solves.&lt;/p&gt;

&lt;p&gt;And instead of starting from the finished protocol, we will begin one layer lower — with plain socket communication.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1 — A plain socket-based communication channel
&lt;/h2&gt;

&lt;p&gt;Let’s start with the smallest possible thing: a tiny TCP server and a tiny TCP client.&lt;/p&gt;

&lt;p&gt;The client will send an HTTP-like request.&lt;/p&gt;

&lt;p&gt;The server will read it and return an HTTP-like response.&lt;/p&gt;

&lt;p&gt;Nothing secure yet. Just raw bytes moving over a socket.&lt;/p&gt;

&lt;h3&gt;
  
  
  Plain server
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# server_plain.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;

&lt;span class="n"&gt;HOST&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;127.0.0.1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8081&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AF_INET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SOCK_STREAM&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;HOST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Listening on &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;HOST&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;PORT&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;addr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;accept&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Connected by &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;addr&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;recv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4096&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Received request:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;HTTP/1.1 200 OK&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Type: text/plain&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Length: 13&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hello, client&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Plain client
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# client_plain.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;

&lt;span class="n"&gt;HOST&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;127.0.0.1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8081&lt;/span&gt;

&lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;GET /transfer?to=bob&amp;amp;amount=100 HTTP/1.1&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Host: localhost&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AF_INET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SOCK_STREAM&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;HOST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;recv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4096&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Received response:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is intentionally tiny.&lt;/p&gt;

&lt;p&gt;The client sends a request like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="nf"&gt;GET&lt;/span&gt; &lt;span class="nn"&gt;/transfer?to=bob&amp;amp;amount=100&lt;/span&gt; &lt;span class="k"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt;
&lt;span class="na"&gt;Host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;localhost&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The server reads it and sends a response back.&lt;/p&gt;

&lt;p&gt;That is all.&lt;/p&gt;

&lt;p&gt;And because it is all plain TCP, anyone who can observe the traffic can read it directly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Looking at the traffic
&lt;/h3&gt;

&lt;p&gt;If you capture this communication in Wireshark, the request and response are fully visible in clear text.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff525x9z5e4p3qalg8mj4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff525x9z5e4p3qalg8mj4.png" alt="plain"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That is our baseline.&lt;/p&gt;

&lt;p&gt;The client can read it.&lt;/p&gt;

&lt;p&gt;The server can read it.&lt;/p&gt;

&lt;p&gt;And anyone on the wire can read it too.&lt;/p&gt;

&lt;p&gt;So the first obvious idea is also the first naive one:&lt;/p&gt;

&lt;p&gt;If the problem is that everyone can read the bytes, let’s encrypt the bytes.&lt;/p&gt;

&lt;p&gt;That sounds reasonable.&lt;/p&gt;

&lt;p&gt;And it is still not enough.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2 — Turning bytes into records
&lt;/h2&gt;

&lt;p&gt;Before we add encryption, we need one small but important thing: structure.&lt;/p&gt;

&lt;p&gt;TCP gives us a byte stream.&lt;/p&gt;

&lt;p&gt;It does not give us message boundaries.&lt;/p&gt;

&lt;p&gt;So once we stop sending plain text directly and start sending encrypted blobs, we need a way to tell the receiver how many bytes belong to one logical message.&lt;/p&gt;

&lt;p&gt;That means even before security, we need a little bit of protocol design.&lt;/p&gt;

&lt;p&gt;Let’s define the smallest possible record format:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;4 bytes: payload length&lt;/li&gt;
&lt;li&gt;N bytes: payload
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;
+----------+-----------+
|  length  |  payload  |
| (4 bytes)|  (varies) |
+----------+-----------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is enough for our first version.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# framing.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;struct&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;send_record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;header&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;!I&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;header&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;recv_exact&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;chunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="n"&gt;remaining&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;

    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;remaining&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;chunk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;recv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;remaining&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ConnectionError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Connection closed while reading data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;remaining&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;recv_record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;header&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;recv_exact&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="p"&gt;,)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unpack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;!I&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;recv_exact&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is not a crypto step.&lt;/p&gt;

&lt;p&gt;It is a protocol step.&lt;/p&gt;

&lt;p&gt;And that distinction matters more than it first appears. A secure channel is not just “call encrypt on a string.” It is a protocol with structure, state, and rules.&lt;/p&gt;

&lt;p&gt;Now that we have a way to send and receive well-defined records, we can finally wrap them in encryption.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3 — Wrapping the channel in shared-key encryption
&lt;/h2&gt;

&lt;p&gt;The most obvious first attempt at secure communication is usually this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;both sides already know the same secret key&lt;/li&gt;
&lt;li&gt;the sender encrypts the message before sending&lt;/li&gt;
&lt;li&gt;the receiver decrypts it after receiving&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is exactly what we will do.&lt;/p&gt;

&lt;p&gt;No handshake yet.&lt;/p&gt;

&lt;p&gt;No certificates yet.&lt;/p&gt;

&lt;p&gt;No integrity yet.&lt;/p&gt;

&lt;p&gt;No authentication yet.&lt;/p&gt;

&lt;p&gt;This version is intentionally naive.&lt;/p&gt;

&lt;p&gt;For encryption, I will use AES in CTR mode.&lt;/p&gt;

&lt;p&gt;Very briefly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AES is a symmetric block cipher&lt;/li&gt;
&lt;li&gt;CTR mode makes it convenient for encrypting a stream of bytes&lt;/li&gt;
&lt;li&gt;it gives us confidentiality&lt;/li&gt;
&lt;li&gt;but it does &lt;strong&gt;not&lt;/strong&gt; give us integrity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last point is the important one for this article.&lt;/p&gt;

&lt;p&gt;If you want a deeper explanation of AES itself, I already wrote about it in my cryptography series: &lt;a href="https://www.dmytrohuz.com/p/building-own-block-cipher-part-3" rel="noopener noreferrer"&gt;https://www.dmytrohuz.com/p/building-own-block-cipher-part-3&lt;/a&gt;, so I will not go into the internals here.&lt;/p&gt;

&lt;h3&gt;
  
  
  The nonce
&lt;/h3&gt;

&lt;p&gt;CTR mode also needs a nonce.&lt;/p&gt;

&lt;p&gt;For now, think of it as a fresh per-message value that must be different for each encryption under the same key.&lt;/p&gt;

&lt;p&gt;It is not secret.&lt;/p&gt;

&lt;p&gt;It just must not be reused.&lt;/p&gt;

&lt;p&gt;So our encrypted payload will look like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;nonce&lt;/li&gt;
&lt;li&gt;ciphertext&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Crypto helper
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# crypto.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;cryptography.hazmat.primitives.ciphers&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Cipher&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;algorithms&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;modes&lt;/span&gt;

&lt;span class="c1"&gt;# 32-byte shared key for AES-256
&lt;/span&gt;&lt;span class="n"&gt;SHARED_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0123456789ABCDEF0123456789ABCDEF&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;encrypt_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;plaintext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;nonce&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urandom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;cipher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Cipher&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;algorithms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AES&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SHARED_KEY&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;modes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CTR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nonce&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;encryptor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cipher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encryptor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;ciphertext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;encryptor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;plaintext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;encryptor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;finalize&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# Encrypted payload format:
&lt;/span&gt;    &lt;span class="c1"&gt;# nonce || ciphertext
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;nonce&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;ciphertext&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;decrypt_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;nonce&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;ciphertext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt;

    &lt;span class="n"&gt;cipher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Cipher&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;algorithms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AES&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SHARED_KEY&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;modes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CTR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nonce&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;decryptor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cipher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decryptor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;plaintext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;decryptor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ciphertext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;decryptor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;finalize&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;plaintext&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, our wire format becomes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;4-byte length&lt;/li&gt;
&lt;li&gt;16-byte nonce&lt;/li&gt;
&lt;li&gt;ciphertext
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+----------------+----------------+---------------------+
| length (4 B)   | nonce (16 B)   | ciphertext (N bytes)|
+----------------+----------------+---------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That already looks much more like a protocol.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4 — Encrypt the request and response
&lt;/h2&gt;

&lt;p&gt;Now let’s integrate this into the client and server.&lt;/p&gt;

&lt;h3&gt;
  
  
  Encrypted client
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# client_v1.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;framing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;send_record&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;recv_record&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;crypto&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;encrypt_message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;decrypt_message&lt;/span&gt;

&lt;span class="n"&gt;HOST&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;127.0.0.1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8081&lt;/span&gt;

&lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;GET /transfer?to=bob&amp;amp;amount=100 HTTP/1.1&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Host: localhost&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AF_INET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SOCK_STREAM&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;HOST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="n"&gt;encrypted_request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;encrypt_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;send_record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encrypted_request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;encrypted_response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;recv_record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;decrypt_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encrypted_response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Received decrypted response:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Encrypted server
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# server_v1.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;framing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;send_record&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;recv_record&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;crypto&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;encrypt_message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;decrypt_message&lt;/span&gt;

&lt;span class="n"&gt;HOST&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;127.0.0.1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8081&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AF_INET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SOCK_STREAM&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;HOST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Listening on &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;HOST&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;PORT&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;addr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;accept&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Connected by &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;addr&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;encrypted_request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;recv_record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;decrypt_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encrypted_request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Received decrypted request:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;HTTP/1.1 200 OK&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s"&gt;Content-Type: text/plain&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Length: 13&lt;/span&gt;&lt;span class="se"&gt;\r\n\r\n&lt;/span&gt;&lt;span class="s"&gt;hello, client&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;encrypted_response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;encrypt_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;send_record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encrypted_response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the communication flow changes in an important way.&lt;/p&gt;

&lt;p&gt;Instead of sending readable HTTP-like text directly, the client sends an encrypted record. The server reads the record, decrypts it, and sees the original request.&lt;/p&gt;

&lt;h3&gt;
  
  
  What changes on the wire
&lt;/h3&gt;

&lt;p&gt;If you capture this version in Wireshark, the traffic is no longer readable.&lt;/p&gt;

&lt;p&gt;Instead of clear request and response text, you now see opaque binary data.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk0kiccihimtlkuu1sdh2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk0kiccihimtlkuu1sdh2.png" alt="encr"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So yes, we gained something real.&lt;/p&gt;

&lt;p&gt;Let’s stop and say exactly what that is.&lt;/p&gt;




&lt;h2&gt;
  
  
  What encryption actually gave us
&lt;/h2&gt;

&lt;p&gt;This first version gives us one meaningful property:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;confidentiality against passive observers&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If someone can only observe the traffic, but cannot modify it, they no longer get the plaintext request and response for free.&lt;/p&gt;

&lt;p&gt;That is already better than raw TCP.&lt;/p&gt;

&lt;p&gt;And this is why “just add encryption” feels so convincing. It visibly solves a real problem.&lt;/p&gt;

&lt;p&gt;But that visible success can hide another, more dangerous failure.&lt;/p&gt;

&lt;p&gt;Because a secure channel needs more than secrecy.&lt;/p&gt;

&lt;p&gt;It also needs protection against tampering.&lt;/p&gt;

&lt;p&gt;And we still do not have that.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 5 — Why encryption alone is not enough
&lt;/h2&gt;

&lt;p&gt;This is the real point of Part 1.&lt;/p&gt;

&lt;p&gt;We encrypted the messages.&lt;/p&gt;

&lt;p&gt;We did &lt;strong&gt;not&lt;/strong&gt; make them trustworthy.&lt;/p&gt;

&lt;p&gt;AES-CTR protects confidentiality, but it does not protect integrity.&lt;/p&gt;

&lt;p&gt;That means an active attacker may be able to modify ciphertext, and those modifications will flow through into the decrypted plaintext.&lt;/p&gt;

&lt;p&gt;Very roughly, CTR mode behaves like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ciphertext = plaintext XOR keystream
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So if an attacker changes bits in the ciphertext, the corresponding bits change in the plaintext after decryption.&lt;/p&gt;

&lt;p&gt;That property is called &lt;strong&gt;malleability&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;And protocol messages are usually predictable enough that this becomes useful to an attacker.&lt;/p&gt;

&lt;p&gt;Our example request already has a very predictable structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="nf"&gt;GET&lt;/span&gt; &lt;span class="nn"&gt;/transfer?to=bob&amp;amp;amount=100&lt;/span&gt; &lt;span class="k"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt;
&lt;span class="na"&gt;Host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;localhost&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact bytes of &lt;code&gt;amount=100&lt;/code&gt; are not random.&lt;/p&gt;

&lt;p&gt;That predictability is enough to hurt us.&lt;/p&gt;

&lt;h3&gt;
  
  
  A tiny isolated demo
&lt;/h3&gt;

&lt;p&gt;We do not need a full man-in-the-middle proxy to show the problem. A small isolated example is enough.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ctr_malleability_demo.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;crypto&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;encrypt_message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;decrypt_message&lt;/span&gt;

&lt;span class="n"&gt;original&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;amount=100&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;encrypted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;encrypt_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;original&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;nonce&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;encrypted&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;ciphertext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;bytearray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encrypted&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;:])&lt;/span&gt;

&lt;span class="c1"&gt;# Change '1' -&amp;gt; '9'
# ASCII '1' = 0x31
# ASCII '9' = 0x39
# Difference = 0x08
&lt;/span&gt;
&lt;span class="n"&gt;index_of_digit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;amount=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ciphertext&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index_of_digit&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;^=&lt;/span&gt; &lt;span class="mh"&gt;0x08&lt;/span&gt;

&lt;span class="n"&gt;modified&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nonce&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ciphertext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;decrypted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;decrypt_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;modified&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Original :&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;original&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Modified :&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;decrypted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Original : b'amount=100'
Modified : b'amount=900'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that is the failure.&lt;/p&gt;

&lt;p&gt;The attacker did not need the key.&lt;/p&gt;

&lt;p&gt;They did not need to fully decrypt the message first.&lt;/p&gt;

&lt;p&gt;They only needed the ability to modify the encrypted bytes in transit.&lt;/p&gt;

&lt;p&gt;The receiver then decrypts the modified ciphertext and gets modified plaintext — without any built-in indication that anything went wrong.&lt;/p&gt;

&lt;p&gt;So even though the message is hidden from passive observers, it is still vulnerable to active tampering.&lt;/p&gt;

&lt;p&gt;That is not a secure protocol.&lt;/p&gt;

&lt;p&gt;That is only encrypted transport.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is still broken
&lt;/h2&gt;

&lt;p&gt;At this point, our fake secure channel still has many serious holes.&lt;/p&gt;

&lt;h3&gt;
  
  
  No integrity protection
&lt;/h3&gt;

&lt;p&gt;The receiver cannot detect that the ciphertext was modified.&lt;/p&gt;

&lt;h3&gt;
  
  
  No message authentication
&lt;/h3&gt;

&lt;p&gt;The receiver has no cryptographic proof that the message came from the expected sender and arrived unchanged.&lt;/p&gt;

&lt;h3&gt;
  
  
  No replay protection
&lt;/h3&gt;

&lt;p&gt;An attacker can capture an encrypted message and replay it later.&lt;/p&gt;

&lt;h3&gt;
  
  
  Static shared key
&lt;/h3&gt;

&lt;p&gt;Both sides use one long-term shared key for everything.&lt;/p&gt;

&lt;p&gt;That does not scale, and if it leaks, everything built on top of it collapses.&lt;/p&gt;

&lt;h3&gt;
  
  
  No handshake
&lt;/h3&gt;

&lt;p&gt;There is no fresh session establishment. The peers do not negotiate anything. They just start encrypting.&lt;/p&gt;

&lt;h3&gt;
  
  
  No peer identity
&lt;/h3&gt;

&lt;p&gt;The client does not really know who it is talking to beyond “someone who can decrypt with this key.”&lt;/p&gt;

&lt;p&gt;So yes, we improved something.&lt;/p&gt;

&lt;p&gt;But we are still very far from TLS.&lt;/p&gt;

&lt;p&gt;Good.&lt;/p&gt;

&lt;p&gt;That is exactly what Part 1 should make visible.&lt;/p&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;In this first part, we built a fake secure channel.&lt;/p&gt;

&lt;p&gt;We started with plain socket communication and saw that everything was fully transparent. Then we wrapped the communication in shared-key encryption with AES-CTR, which gave us confidentiality against passive observers.&lt;/p&gt;

&lt;p&gt;That was real progress.&lt;/p&gt;

&lt;p&gt;But it was not enough.&lt;/p&gt;

&lt;p&gt;Because encrypting a message is not the same thing as protecting the message from being changed. Our channel still accepts modified ciphertext, decrypts it, and trusts the result.&lt;/p&gt;

&lt;p&gt;So the first lesson of this series is simple:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;confidentiality is not integrity&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And if we want something that starts to deserve the name secure protocol, we need both.&lt;/p&gt;




&lt;h2&gt;
  
  
  Next part — adding integrity with a MAC
&lt;/h2&gt;

&lt;p&gt;So we stop here.&lt;/p&gt;

&lt;p&gt;We now have a channel that can hide bytes from passive observers, but cannot reliably detect tampering.&lt;/p&gt;

&lt;p&gt;In the next part, we will keep the same basic setup and fix the biggest hole we exposed here: we will add a &lt;strong&gt;MAC — a Message Authentication Code&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That will take us from:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“you probably can’t read this”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;to:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“you also can’t silently change this”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That still will not be real TLS.&lt;/p&gt;

&lt;p&gt;But it will make our fake secure channel one step less fake.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final code
&lt;/h2&gt;

&lt;p&gt;I’ll put the full code for this part on GitHub here:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/DmytroHuzz/rebuilding_tls/tree/main/part_1" rel="noopener noreferrer"&gt;https://github.com/DmytroHuzz/rebuilding_tls/tree/main/part_1&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>learning</category>
    </item>
    <item>
      <title>A Practical Guide to Time for Developers: Part 4 -The Linux Time Sync Cheat Sheet</title>
      <dc:creator>Dmytro Huz</dc:creator>
      <pubDate>Thu, 19 Mar 2026 16:42:58 +0000</pubDate>
      <link>https://dev.to/dmytro_huz/a-practical-guide-to-time-for-developers-part-4-the-linux-time-sync-cheat-sheet-5np</link>
      <guid>https://dev.to/dmytro_huz/a-practical-guide-to-time-for-developers-part-4-the-linux-time-sync-cheat-sheet-5np</guid>
      <description>&lt;p&gt;If you got here and made it through the previous articles, you have already done the hard part. You are basically a time guru now.&lt;/p&gt;

&lt;p&gt;You know what time means in computing, how a machine keeps it, why clocks drift, how synchronization works, and why timestamp location matters. That is already more than most people ever learn about this topic.&lt;/p&gt;

&lt;p&gt;Now let’s compress all of that into the Linux view of the world.&lt;/p&gt;

&lt;p&gt;This is the top of the iceberg: a small set of clocks, commands, and tools that represent most of the concepts we have been building up across the series.&lt;/p&gt;

&lt;p&gt;Think of this as the practical cheat sheet — the 90% version. The one you can use to inspect clocks, understand what is synchronized to what, and handle most everyday Linux time-sync tasks without drowning in documentation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;This is probably the part you will want to bookmark.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;The three main clock entities in Linux&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;When people say “Linux time,” they often mean one thing. In reality, Linux commonly deals with at least three different clock entities:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;system time&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RTC&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PHC&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They serve different purposes.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;1. System time&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;This is the normal wall-clock time used by most applications.&lt;/p&gt;

&lt;p&gt;It is what you usually see when you run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;date&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;timedatectl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Conceptually, this is the kernel’s main wall clock, commonly associated with CLOCK_REALTIME.&lt;/p&gt;

&lt;p&gt;This is the clock used by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;most user-space applications&lt;/li&gt;
&lt;li&gt;logs&lt;/li&gt;
&lt;li&gt;system services&lt;/li&gt;
&lt;li&gt;everyday time queries&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Quick check&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;date
&lt;/span&gt;timedatectl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;Mental model&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;System clock = the main OS wall clock
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  &lt;strong&gt;2. RTC (Real-Time Clock)&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The RTC is the battery-backed hardware clock on the motherboard.&lt;/p&gt;

&lt;p&gt;Its main job is simple: keep time while the machine is powered off.&lt;/p&gt;

&lt;p&gt;Linux often uses it during boot to initialize system time, and may update it again later from system time. But the RTC is usually &lt;strong&gt;not&lt;/strong&gt; the main precision synchronization clock during normal operation.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Quick check&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;hwclock &lt;span class="nt"&gt;--show&lt;/span&gt;
timedatectl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;Common operations&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Copy system time to RTC:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;hwclock &lt;span class="nt"&gt;--systohc&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy RTC to system time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;hwclock &lt;span class="nt"&gt;--hctosys&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;Mental model&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RTC = persistent clock for boot/shutdown
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  &lt;strong&gt;3. PHC (PTP Hardware Clock)&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;A PHC is a hardware clock exposed by a PTP-capable network interface.&lt;/p&gt;

&lt;p&gt;This is where Linux gets especially interesting.&lt;/p&gt;

&lt;p&gt;A PHC lives on the NIC, much closer to the real transmit/receive event than the normal system clock. That is why it matters for precise synchronization.&lt;/p&gt;

&lt;p&gt;PHCs usually appear as device files like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/dev/ptp0
/dev/ptp1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;Quick check&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;List PHC devices:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; /dev/ptp&lt;span class="k"&gt;*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check which PHC belongs to a NIC and whether hardware timestamping is supported:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ethtool &lt;span class="nt"&gt;-T&lt;/span&gt; eth0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;Mental model&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PHC = hardware clock on the NIC, used for precise packet timing
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  &lt;strong&gt;One picture: how they relate&lt;/strong&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RTC
  |
  | used mainly at boot / shutdown
  v
System clock (CLOCK_REALTIME)
  ^
  |
  | phc2sys can sync between them
  |
PHC (/dev/ptpX on NIC)
  ^
  |
  | ptp4l syncs PHC to PTP network
  |
PTP network / Grandmaster
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A rough summary:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;RTC&lt;/strong&gt; keeps time while power is off&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;system clock&lt;/strong&gt; is what most software reads&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PHC&lt;/strong&gt; is the precision clock on the NIC&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;How Linux syncs time with NTP&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;In the NTP world, Linux usually synchronizes the &lt;strong&gt;system clock&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Common tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;chronyd&lt;/li&gt;
&lt;li&gt;systemd-timesyncd&lt;/li&gt;
&lt;li&gt;older setups may use ntpd&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Check whether NTP is active&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;timedatectl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;If you use chrony&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Show synchronization state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;chronyc tracking
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Show time sources:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;chronyc sources &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;Mental model&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NTP servers
   |
   v
chronyd / timesyncd / ntpd
   |
   v
System clock
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In other words, NTP usually targets the &lt;strong&gt;system clock&lt;/strong&gt;, not the PHC.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;How Linux syncs time with PTP&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;In the PTP world, Linux often follows a two-step path:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;synchronize the &lt;strong&gt;PHC&lt;/strong&gt; to the PTP network&lt;/li&gt;
&lt;li&gt;synchronize the &lt;strong&gt;system clock&lt;/strong&gt; to that PHC&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The two main tools are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ptp4l&lt;/li&gt;
&lt;li&gt;phc2sys&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;ptp4l: syncing the NIC-side clock&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;ptp4l speaks PTP on the network and usually synchronizes the PHC associated with the interface.&lt;/p&gt;

&lt;p&gt;Typical example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;ptp4l &lt;span class="nt"&gt;-i&lt;/span&gt; eth0 &lt;span class="nt"&gt;-m&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Meaning:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;i eth0 → use interface eth0&lt;/li&gt;
&lt;li&gt;m → print log messages to stdout&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Mental model&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PTP Grandmaster / network
        |
        v
      ptp4l
        |
        v
PHC on eth0 (/dev/ptpX)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is usually where the NIC-side precision timing gets aligned to the network timing domain.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;phc2sys: syncing one local clock to another&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Once the PHC is synchronized, the rest of the machine may still be reading the system clock.&lt;/p&gt;

&lt;p&gt;That is why phc2sys exists.&lt;/p&gt;

&lt;p&gt;Its job is to synchronize one local clock to another.&lt;/p&gt;

&lt;p&gt;The most common use is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PHC → system clock&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;phc2sys &lt;span class="nt"&gt;-s&lt;/span&gt; eth0 &lt;span class="nt"&gt;-c&lt;/span&gt; CLOCK_REALTIME &lt;span class="nt"&gt;-m&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Meaning:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;s eth0 → source is the PHC associated with eth0&lt;/li&gt;
&lt;li&gt;c CLOCK_REALTIME → target is the system clock&lt;/li&gt;
&lt;li&gt;m → print status&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Mental model&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PHC on NIC
   |
   v
phc2sys
   |
   v
System clock
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the classic Linux PTP flow.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;The classic Linux PTP pipeline&lt;/strong&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PTP Grandmaster
      |
      v
  [ network ]
      |
      v
NIC hardware timestamping
      |
      v
    ptp4l
      |
      v
PHC (/dev/ptp0) synchronized to PTP domain
      |
    phc2sys
      |
      v
System clock (CLOCK_REALTIME)
      |
      v
Applications / logs / services
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is one of the most useful diagrams to keep in your head.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;PHC to system clock, or system clock to PHC?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;In precision setups, the common direction is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PHC → system clock
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;because the PHC is closer to the wire and usually the better timing source in a PTP environment.&lt;/p&gt;

&lt;p&gt;That is why this is a common command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;phc2sys &lt;span class="nt"&gt;-s&lt;/span&gt; eth0 &lt;span class="nt"&gt;-c&lt;/span&gt; CLOCK_REALTIME &lt;span class="nt"&gt;-m&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But phc2sys is more general than that. It can synchronize clocks in other directions too.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Syncing one PHC to another&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Linux can also synchronize different hardware clocks to each other.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PHC A → PHC B
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;phc2sys &lt;span class="nt"&gt;-s&lt;/span&gt; /dev/ptp0 &lt;span class="nt"&gt;-c&lt;/span&gt; /dev/ptp1 &lt;span class="nt"&gt;-m&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is less common than PHC-to-system-clock sync, but it is useful when one interface or subsystem should follow another local hardware clock.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Where software timestamping fits&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Not every NIC has hardware timestamping. Not every system needs that level of precision.&lt;/p&gt;

&lt;p&gt;Linux can still synchronize clocks using software timestamps, and for many use cases that is completely fine.&lt;/p&gt;

&lt;p&gt;But the practical tradeoff remains the same:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;hardware timestamping&lt;/strong&gt; gives measurements closer to the real wire event&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;software timestamping&lt;/strong&gt; includes more delay and variation from the OS path&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is why software timestamping usually belongs to a looser precision budget, while hardware timestamping is what unlocks much tighter synchronization.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;The most useful commands at a glance&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Check system time&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;date
&lt;/span&gt;timedatectl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;Check RTC&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;hwclock &lt;span class="nt"&gt;--show&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;List PHC devices&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; /dev/ptp&lt;span class="k"&gt;*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;Check NIC timestamping support&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ethtool &lt;span class="nt"&gt;-T&lt;/span&gt; eth0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;Run PTP on an interface&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;ptp4l &lt;span class="nt"&gt;-i&lt;/span&gt; eth0 &lt;span class="nt"&gt;-m&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;Sync system clock from PHC&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;phc2sys &lt;span class="nt"&gt;-s&lt;/span&gt; eth0 &lt;span class="nt"&gt;-c&lt;/span&gt; CLOCK_REALTIME &lt;span class="nt"&gt;-m&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;Check chrony state&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;chronyc tracking
chronyc sources &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  &lt;strong&gt;The one table worth remembering&lt;/strong&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RTC ------------------&amp;gt; system time at boot / shutdown
NTP daemon -----------&amp;gt; system clock
ptp4l ----------------&amp;gt; PHC
phc2sys --------------&amp;gt; PHC &amp;lt;-&amp;gt; system clock
hwclock --systohc ----&amp;gt; system clock -&amp;gt; RTC
hwclock --hctosys ----&amp;gt; RTC -&amp;gt; system clock
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  &lt;strong&gt;The biggest practical lesson&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;When somebody says:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“The machine is synchronized.”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That is usually too vague.&lt;/p&gt;

&lt;p&gt;The useful follow-up question is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Which clock is synchronized?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the RTC?&lt;/li&gt;
&lt;li&gt;the system clock?&lt;/li&gt;
&lt;li&gt;the PHC?&lt;/li&gt;
&lt;li&gt;and which one is the application actually using?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That one question prevents a lot of confusion.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;One-screen summary&lt;/strong&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RTC
- battery-backed motherboard clock
- keeps time while machine is off
- checked with: hwclock --show

System clock
- main OS wall clock
- used by most applications
- checked with: date, timedatectl
- synchronized by: NTP or by phc2sys from PHC

PHC
- hardware clock on a PTP-capable NIC
- represented as: /dev/ptpX
- checked with: ls /dev/ptp*, ethtool -T eth0
- synchronized by: ptp4l

Typical Linux PTP flow
PTP network -&amp;gt; ptp4l -&amp;gt; PHC -&amp;gt; phc2sys -&amp;gt; system clock
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  &lt;strong&gt;Final note&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;If you made it all the way here, you did not just read a few articles about clocks.&lt;/p&gt;

&lt;p&gt;You built a real mental model of time in computing — from first principles, to clocks inside a machine, to synchronization across networks, to the actual Linux entities and tools that make it work in practice.&lt;/p&gt;

&lt;p&gt;That already puts you far ahead of most engineers who touch these systems.&lt;/p&gt;

&lt;p&gt;You now know enough to stop treating time as a mysterious background feature and start seeing it for what it really is: infrastructure, measurement, coordination, and engineering.&lt;/p&gt;

&lt;p&gt;And it was the final piece: a practical cheat sheet you can actually use. Bookmark it. Return to it. Break things with it. Fix things with it.&lt;/p&gt;




</description>
      <category>cli</category>
      <category>devops</category>
      <category>linux</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>A Practical Guide to Time for Developers: Part 3 — How Computers Share Time</title>
      <dc:creator>Dmytro Huz</dc:creator>
      <pubDate>Mon, 16 Mar 2026 16:00:00 +0000</pubDate>
      <link>https://dev.to/dmytro_huz/a-practical-guide-to-time-for-developers-part-3-how-computers-share-time-16d4</link>
      <guid>https://dev.to/dmytro_huz/a-practical-guide-to-time-for-developers-part-3-how-computers-share-time-16d4</guid>
      <description>&lt;h2&gt;
  
  
  Intro
&lt;/h2&gt;

&lt;p&gt;Every action film has that scene just before the military operation begins.&lt;/p&gt;

&lt;p&gt;“Let’s sync our watches,” the captain says.&lt;/p&gt;

&lt;p&gt;The idea is simple: if every stage of the plan depends on precise coordination, everyone involved has to act in sync and according to the same timeline.&lt;/p&gt;

&lt;p&gt;A while ago, we started our journey with a practical goal: synchronizing the time of many computers. To get there, we first had to understand what time actually is and learn the basic glossary needed to speak the language of this problem and its solutions. In the first part (&lt;a href="https://www.dmytrohuz.com/p/a-practical-guide-to-time-for-developers" rel="noopener noreferrer"&gt;https://www.dmytrohuz.com/p/a-practical-guide-to-time-for-developers&lt;/a&gt;), we explored the foundations of time itself. In the second (&lt;a href="https://www.dmytrohuz.com/p/a-practical-guide-to-time-for-developers-2ec" rel="noopener noreferrer"&gt;https://www.dmytrohuz.com/p/a-practical-guide-to-time-for-developers-2ec&lt;/a&gt;), we looked at how time is kept and tracked inside a single computer. Now we are finally ready to move to the next step: how many computers share time with each other.&lt;/p&gt;

&lt;p&gt;Keeping precise time across many computers is not unusual or exotic. In fact, the opposite is true. Distributed systems, industrial networks, telecom infrastructure, financial systems, and measurement environments often involve hundreds or thousands of devices that must stay synchronized within a clearly defined precision budget.&lt;/p&gt;

&lt;p&gt;Let’s imagine a wind farm. Each turbine is around 120 meters tall and has a warning light at the top. To make the turbines visible to planes at night, the lights should blink every second. And to make the whole field clearly visible as one coordinated structure, those lights should blink simultaneously.&lt;/p&gt;

&lt;p&gt;How can we make that happen?&lt;/p&gt;

&lt;p&gt;The obvious answer is: the turbines need synchronized clocks.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fang0fw1quxulsmcj6t1h.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fang0fw1quxulsmcj6t1h.jpg" alt="Isync" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But how we can keep them in sync for hundreds and thousands devices with amazing accuracy?&lt;br&gt;
Let’s see! &lt;/p&gt;

&lt;h2&gt;
  
  
  Just sync the clocks once?
&lt;/h2&gt;

&lt;p&gt;Let’s start with the most obvious idea: set the same time on all clocks once, and the problem is solved.&lt;/p&gt;

&lt;p&gt;Unfortunately, it does not work that way.&lt;/p&gt;

&lt;p&gt;Every clock has physical behavior behind it. Its frequency is affected by things like oscillator quality, temperature, aging, and other environmental factors. As a result, every clock drifts in its own way. Some also exhibit short-term fluctuations, often described as wander. These effects cannot be fully eliminated, and in practice they mean that two clocks will slowly diverge even if they start perfectly aligned.&lt;/p&gt;

&lt;p&gt;That turns synchronization from a one-time setup task into a continuous process.&lt;/p&gt;

&lt;p&gt;Clocks do not just need to be set. They need to be kept aligned over time. In practice, that means measuring the difference between clocks again and again, then adjusting their time and, more importantly, their rate so that they do not immediately drift apart again.&lt;/p&gt;

&lt;p&gt;You can see how quickly clocks with different rates and wander fall out of sync, even when they start at exactly the same time with the interactive simulation I created for this exact scenario: &lt;a href="https://dmytrohuzz.github.io/interactive_demo/clock_sync/index.html" rel="noopener noreferrer"&gt;https://dmytrohuzz.github.io/interactive_demo/clock_sync/index.html&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  From setting time to synchronization
&lt;/h2&gt;

&lt;p&gt;Once we accept that clocks drift, a one-time setup stops looking like a real solution. Time is not something you assign once. It is something you keep aligned.&lt;/p&gt;

&lt;p&gt;In practice, synchronization is a feedback loop. A machine compares its local clock to some reference, estimates the difference, adjusts its own clock, and repeats the process again and again.&lt;/p&gt;

&lt;p&gt;The difficult part is that machines cannot read each other’s clocks directly. They can only communicate over a network, and the network adds delay and uncertainty. So synchronization protocols work indirectly: they exchange messages with timestamps and use those timestamps to estimate the relationship between clocks.&lt;/p&gt;

&lt;p&gt;At the center of that estimate are two questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;how far apart are the clocks?&lt;/li&gt;
&lt;li&gt;how much of the observed difference comes from network delay rather than clock error?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This sounds simple in theory, but the key idea only becomes clear once we walk through it step by step.&lt;/p&gt;

&lt;p&gt;A basic synchronization exchange gives us four timestamps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;t1 — the client sends a request&lt;/li&gt;
&lt;li&gt;t2 — the server receives that request&lt;/li&gt;
&lt;li&gt;t3 — the server sends a response&lt;/li&gt;
&lt;li&gt;t4 — the client receives the response&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F536uky2zlk4mfmsl7oq2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F536uky2zlk4mfmsl7oq2.png" alt="Isync" width="544" height="851"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These four timestamps are the heart of the whole mechanism. Once this pattern becomes intuitive, the rest of the synchronization topic becomes much easier to follow.&lt;/p&gt;

&lt;p&gt;Now imagine the exchange from the client’s point of view.&lt;/p&gt;

&lt;p&gt;The client sends a request at local time t1 = 00:00.&lt;/p&gt;

&lt;p&gt;Later, it receives the response at local time t4 = 00:04.&lt;/p&gt;

&lt;p&gt;Inside that response, the server includes its own timestamps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;it received the request at t2 = 00:06&lt;/li&gt;
&lt;li&gt;it sent the response at t3 = 00:06&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At first glance, this looks strange. How can the server receive the request at 00:06 if the client sent it at 00:00, and the whole round trip took only four seconds on the client side?&lt;/p&gt;

&lt;p&gt;The answer is simple: t1 and t2 do not belong to the same timeline.&lt;/p&gt;

&lt;p&gt;The client clock and the server clock are different local views of time. What synchronization tries to estimate is the relation between those two timelines. In other words, it tries to answer this question:&lt;/p&gt;

&lt;p&gt;If the client sees one moment as 00:00, what does the server call that same moment?&lt;/p&gt;

&lt;p&gt;That relationship is what we call offset.&lt;/p&gt;

&lt;p&gt;This is the most important insight in the whole topic: the difference t2 - t1 does not represent only network delay. It contains two things mixed together:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;packet travel time&lt;/li&gt;
&lt;li&gt;clock offset between client and server&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A useful way to think about it is with time zones.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Firkm47q5zur79sdjhqpz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Firkm47q5zur79sdjhqpz.png" alt="cars" width="800" height="533"&gt;&lt;/a&gt;&lt;br&gt;
Imagine you leave one city at local time 00:00, travel to another city, and arrive when the local clock there shows 14:00. Then you immediately turn around and come back, arriving home when your original city’s clock shows 20:00.&lt;/p&gt;

&lt;p&gt;Now suppose the travel time is the same in both directions.&lt;/p&gt;

&lt;p&gt;The first leg, from your city to the other one, includes:&lt;/p&gt;

&lt;p&gt;travel time + time-zone difference&lt;/p&gt;

&lt;p&gt;The return leg includes:&lt;/p&gt;

&lt;p&gt;travel time - time-zone difference&lt;/p&gt;

&lt;p&gt;So if the outward journey appears shorter or longer than the return journey, that difference tells you something about the offset between the two local clocks.&lt;/p&gt;

&lt;p&gt;This is exactly what synchronization protocols exploit.&lt;/p&gt;

&lt;p&gt;Under the usual symmetric-delay assumption, the offset can be estimated as:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;offset = ((t2 - t1) + (t3 - t4)) /2&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
and the round-trip delay as:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;delay = (t4 - t1) - (t3 - t2)&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
The first formula separates clock offset from the two directions of travel. The second removes the server’s processing time and leaves only the network round-trip time.&lt;/p&gt;

&lt;p&gt;So synchronization is not about directly copying time from one machine to another. It is about observing message exchanges, separating delay from clock difference, and then correcting the local clock based on that estimate.&lt;/p&gt;

&lt;p&gt;That is the core idea behind the whole topic.&lt;/p&gt;

&lt;p&gt;Feel free to play with the simulation here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dmytrohuzz.github.io/interactive_demo/clock_sync/clock_sync_explained" rel="noopener noreferrer"&gt;https://dmytrohuzz.github.io/interactive_demo/clock_sync/clock_sync_explained&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  NTP and PTP: two ways to synchronize clocks
&lt;/h2&gt;

&lt;p&gt;Over time, two major protocol families became the standard answers to the synchronization problem: NTP and PTP.&lt;/p&gt;

&lt;p&gt;Both solve the same core problem: a machine cannot read another machine’s clock directly, so it has to infer the difference by exchanging timestamped messages over a network. From those timestamps, it estimates clock offset and network delay, then adjusts the local clock toward a reference.&lt;/p&gt;

&lt;p&gt;The difference is not the basic idea, but the precision target and the environment they are designed for.&lt;/p&gt;

&lt;h3&gt;
  
  
  NTP: practical synchronization for general systems
&lt;/h3&gt;

&lt;p&gt;NTP — the Network Time Protocol — is the general-purpose approach. It is designed to keep clocks reasonably aligned across ordinary systems and ordinary networks.&lt;/p&gt;

&lt;p&gt;Its main principle is simple: a client exchanges request and response messages with a time server, records timestamps on both sides, estimates round-trip delay and clock offset, and then gradually disciplines its own clock. It repeats this process continuously, using multiple measurements to smooth out noise and avoid reacting too aggressively to one bad sample.&lt;/p&gt;

&lt;p&gt;That makes NTP a good fit for:&lt;br&gt;
    • logs and observability&lt;br&gt;
    • authentication and certificate validation&lt;br&gt;
    • scheduled jobs&lt;br&gt;
    • general wall-clock correctness across servers and infrastructure&lt;/p&gt;

&lt;p&gt;NTP does not assume a perfect network. It is built for real environments, where delays vary, paths are not perfectly symmetric, and hosts are under changing load. Its strength is robustness, not extreme precision.&lt;br&gt;
[&lt;a href="https://dmytrohuzz.github.io/interactive_demo/clock_sync/ntp_visualized.html" rel="noopener noreferrer"&gt;Interactive demo&lt;/a&gt;]&lt;/p&gt;

&lt;h3&gt;
  
  
  PTP: tighter synchronization for controlled environments
&lt;/h3&gt;

&lt;p&gt;PTP — the Precision Time Protocol — targets systems where much tighter agreement between clocks is required.&lt;/p&gt;

&lt;p&gt;Its principle is similar to NTP: devices exchange timing messages, estimate offset and delay, and adjust local clocks. But PTP is designed for local precision networks, where the entire timing path is treated more carefully. In practice, this often means hardware timestamping, PTP-aware switches, and a dedicated timing hierarchy built around a grandmaster clock distributing time to other devices.&lt;/p&gt;

&lt;p&gt;PTP is commonly used in:&lt;br&gt;
    • industrial and automation systems&lt;br&gt;
    • telecom networks&lt;br&gt;
    • audio and video systems&lt;br&gt;
    • measurement systems&lt;br&gt;
    • finance&lt;br&gt;
    • power and substation environments&lt;/p&gt;

&lt;p&gt;PTP is not just “a more accurate NTP.” It usually operates in a different class of environment, with tighter timing requirements and more deliberate infrastructure support.&lt;br&gt;
[&lt;a href="https://dmytrohuzz.github.io/interactive_demo/clock_sync/ptp_visualized.html" rel="noopener noreferrer"&gt;Interactive Demo&lt;/a&gt;]&lt;/p&gt;

&lt;h3&gt;
  
  
  Different tools for different timing budgets
&lt;/h3&gt;

&lt;p&gt;So NTP and PTP are not really rivals. They are different engineering choices.&lt;/p&gt;

&lt;p&gt;If the goal is to keep ordinary systems aligned to real time well enough for general infrastructure behavior, NTP is usually the right tool.&lt;/p&gt;

&lt;p&gt;If the goal is to keep clocks tightly aligned in a local timing domain where timing quality directly affects correctness, event ordering, or measurement precision, PTP is often the better fit.&lt;/p&gt;

&lt;p&gt;The key point is this: both protocols depend on timestamp exchange, but the quality of synchronization depends heavily on how those timestamps are produced.&lt;/p&gt;

&lt;p&gt;And that leads to the next question: where exactly was the timestamp taken?&lt;/p&gt;

&lt;p&gt;This is where timestamping location — in software or in hardware — starts to matter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why timestamp location changes everything
&lt;/h2&gt;

&lt;p&gt;At this point, NTP and PTP may still look like protocol problems: exchange messages, estimate offset, correct the clock.&lt;/p&gt;

&lt;p&gt;But in practice, a large part of synchronization quality depends on something more physical:&lt;/p&gt;

&lt;p&gt;where exactly is the timestamp taken?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5zcekuqv8cljfmie0lx0.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5zcekuqv8cljfmie0lx0.jpg" alt="timestamp" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That matters because a packet does not appear in software at the exact moment it hits the wire. Between the real network event and the moment the operating system records a timestamp, the packet may pass through the NIC, driver, kernel, interrupt handling, scheduling, and software processing. Every one of those layers can add delay and variation.&lt;/p&gt;

&lt;p&gt;So two timestamps may look equally precise as numbers while representing very different physical moments.&lt;/p&gt;

&lt;h3&gt;
  
  
  Software timestamping
&lt;/h3&gt;

&lt;p&gt;With software timestamping, the timestamp is recorded somewhere in the software stack after the packet has already passed through part of the system.&lt;/p&gt;

&lt;p&gt;That makes software timestamping widely available and easy to use, but it also means the measurement includes more uncertainty:&lt;br&gt;
    • interrupt latency&lt;br&gt;
    • kernel and driver delay&lt;br&gt;
    • scheduling effects&lt;br&gt;
    • queueing and system load&lt;/p&gt;

&lt;p&gt;As a result, a software timestamp often reflects when the system handled the packet, not the exact moment the packet crossed the network interface.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hardware timestamping
&lt;/h3&gt;

&lt;p&gt;With hardware timestamping, the timestamp is recorded much closer to the real transmit or receive event, typically inside the NIC itself.&lt;/p&gt;

&lt;p&gt;This removes a large part of the software-induced uncertainty and makes the measurement more stable and repeatable. The closer the timestamp is to the actual wire event, the more useful it becomes for precise synchronization.&lt;/p&gt;

&lt;p&gt;That is one of the main reasons PTP can achieve much better accuracy in the right environment: not only because of the protocol itself, but because it is often paired with hardware timestamping and a more carefully controlled timing path.&lt;/p&gt;

&lt;p&gt;So the practical precision limit is not defined by the protocol name alone. It depends on the full measurement path.&lt;/p&gt;

&lt;p&gt;A good rule of thumb is simple:&lt;/p&gt;

&lt;p&gt;the closer the timestamp is to the wire, the better the synchronization can be.&lt;/p&gt;

&lt;p&gt;⸻&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;A single computer can keep time locally. A distributed system has a harder task: many machines must keep time together.&lt;/p&gt;

&lt;p&gt;That is why simply setting clocks once is not enough. Real clocks drift, so synchronization has to be continuous. Protocols such as NTP and PTP address this by exchanging timestamped messages, estimating clock offset and network delay, and repeatedly steering local clocks toward a reference.&lt;/p&gt;

&lt;p&gt;But protocol choice is only part of the story. In practice, synchronization quality also depends heavily on where timestamps are taken. A timestamp captured deep in software carries more uncertainty than one captured close to the physical network event.&lt;/p&gt;

&lt;p&gt;So if this part was about the general idea of shared time — why it matters, why it is difficult, and how systems approach it — the next part will move from principle to implementation.&lt;/p&gt;

&lt;p&gt;We will look at how Linux actually does this in practice: NICs, software and hardware timestamping, PHCs, and the tools that connect them into a real synchronization stack.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>learning</category>
      <category>devops</category>
    </item>
    <item>
      <title>When you delegate code writing to AI, you can still lean on tests.
But when you delegate the tests to AI as well, what exactly are you relying on?
The code looks fine. The tests are green.
Does that mean the behavior you expect is truly protect?</title>
      <dc:creator>Dmytro Huz</dc:creator>
      <pubDate>Wed, 11 Mar 2026 09:22:32 +0000</pubDate>
      <link>https://dev.to/dmytro_huz/when-you-delegate-code-writing-to-ai-you-can-still-lean-on-tests-but-when-you-delegate-the-tests-30h6</link>
      <guid>https://dev.to/dmytro_huz/when-you-delegate-code-writing-to-ai-you-can-still-lean-on-tests-but-when-you-delegate-the-tests-30h6</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/dmytro_huz/i-built-ac-trace-to-check-what-tests-actually-protect-2pnc" class="crayons-story__hidden-navigation-link"&gt;I Built ac-trace to Check What Tests Actually Protect&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/dmytro_huz" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1824917%2F1549c3d8-4f61-495f-b96f-6ddd331a3c9f.PNG" alt="dmytro_huz profile" class="crayons-avatar__image" width="512" height="512"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/dmytro_huz" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Dmytro Huz
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Dmytro Huz
                
              
              &lt;div id="story-author-preview-content-3331161" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/dmytro_huz" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1824917%2F1549c3d8-4f61-495f-b96f-6ddd331a3c9f.PNG" class="crayons-avatar__image" alt="" width="512" height="512"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Dmytro Huz&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/dmytro_huz/i-built-ac-trace-to-check-what-tests-actually-protect-2pnc" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Mar 9&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/dmytro_huz/i-built-ac-trace-to-check-what-tests-actually-protect-2pnc" id="article-link-3331161"&gt;
          I Built ac-trace to Check What Tests Actually Protect
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/testing"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;testing&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/development"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;development&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/opensource"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;opensource&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/dmytro_huz/i-built-ac-trace-to-check-what-tests-actually-protect-2pnc" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;1&lt;span class="hidden s:inline"&gt;&amp;nbsp;reaction&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/dmytro_huz/i-built-ac-trace-to-check-what-tests-actually-protect-2pnc#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            4 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>testing</category>
      <category>ai</category>
      <category>development</category>
      <category>opensource</category>
    </item>
    <item>
      <title>I Built ac-trace to Check What Tests Actually Protect</title>
      <dc:creator>Dmytro Huz</dc:creator>
      <pubDate>Mon, 09 Mar 2026 14:45:13 +0000</pubDate>
      <link>https://dev.to/dmytro_huz/i-built-ac-trace-to-check-what-tests-actually-protect-2pnc</link>
      <guid>https://dev.to/dmytro_huz/i-built-ac-trace-to-check-what-tests-actually-protect-2pnc</guid>
      <description>&lt;p&gt;AI-assisted coding is making one part of software development much faster than another.&lt;/p&gt;

&lt;p&gt;It is now easier than ever to generate implementation code, unit tests, fixtures, mocks, and even test structure. But while output is getting faster, confidence is not automatically getting deeper. In fact, the opposite can happen: the more quickly code and tests are produced, the easier it becomes to confuse visible testing activity with real protection.&lt;/p&gt;

&lt;p&gt;That gap is exactly why I built &lt;strong&gt;&lt;a href="https://github.com/DmytroHuzz/ac-trace" rel="noopener noreferrer"&gt;ac-trace&lt;/a&gt;&lt;/strong&gt;, a new open-source tool.&lt;/p&gt;

&lt;p&gt;The core problem is simple: passing tests are often a weaker signal than teams think. Coverage is not enough either. A test suite can be green, a code path can be exercised, and the intended behavior can still be only weakly defended.&lt;/p&gt;

&lt;p&gt;What I care about is not just whether code ran, or whether assertions passed. The harder question is this:&lt;/p&gt;

&lt;p&gt;Are the acceptance criteria actually protected?&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem: green tests do not prove much by themselves
&lt;/h2&gt;

&lt;p&gt;In many teams, these ideas get blended together:&lt;br&gt;
    • tests are passing&lt;br&gt;
    • code is covered&lt;br&gt;
    • therefore the requirement is safe&lt;/p&gt;

&lt;p&gt;But those are different signals.&lt;/p&gt;

&lt;p&gt;A passing test tells you that some expectation held in one scenario. Coverage tells you that code executed. Neither one, by itself, proves that the important business behavior is strongly defended against breakage.&lt;/p&gt;

&lt;p&gt;This becomes more important with AI-assisted coding.&lt;/p&gt;

&lt;p&gt;AI is good at producing plausible implementations and plausible tests very quickly. That is useful. But it also lowers the cost of producing code that looks well tested. You get more test files, more green checks, more visible structure — and sometimes only shallow confidence underneath.&lt;/p&gt;

&lt;h2&gt;
  
  
  A concrete example
&lt;/h2&gt;

&lt;p&gt;Imagine a billing service with this acceptance criterion:&lt;/p&gt;

&lt;p&gt;Premium users must never be charged above their contractual monthly cap.&lt;/p&gt;

&lt;p&gt;Now imagine the code has tests for invoice creation. It has tests for premium-user billing flow. It has good coverage around the billing function. The relevant lines all execute. The pipeline is green.&lt;/p&gt;

&lt;p&gt;Looks fine.&lt;/p&gt;

&lt;p&gt;But now remove the cap check. Or flip the comparison. Or mutate the mapped billing logic in a way that breaks the intended behavior.&lt;/p&gt;

&lt;p&gt;Do the tests fail?&lt;/p&gt;

&lt;p&gt;If they do not, then the acceptance criterion was never really protected. The system had tests. The code was covered. But the thing that mattered was still weakly defended.&lt;/p&gt;

&lt;p&gt;That is the gap I wanted to make more visible.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3mrdyj9j1754j74laft5.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3mrdyj9j1754j74laft5.jpg" alt="schema" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  That is why I built ac-trace
&lt;/h2&gt;

&lt;p&gt;ac-trace (Repo: &lt;a href="https://github.com/DmytroHuzz/ac-trace" rel="noopener noreferrer"&gt;https://github.com/DmytroHuzz/ac-trace&lt;/a&gt;) is an open-source tool that maps acceptance criteria to code and tests, then mutates the mapped code to verify whether the tests actually catch the breakage.&lt;/p&gt;

&lt;p&gt;In plain terms:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;it tries to answer whether the tests defend the behavior they are supposed to defend.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is not just traceability for documentation. The point is not only to show links between requirements, code, and tests. The point is to test whether those links have teeth.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;The current workflow is intentionally simple:&lt;br&gt;
    1.  Define acceptance criteria&lt;br&gt;
    2.  Map them to relevant source code and tests&lt;br&gt;
    3.  Infer some links from annotated tests&lt;br&gt;
    4.  Mutate the mapped implementation&lt;br&gt;
    5.  Run the relevant tests&lt;br&gt;
    6.  Generate a report showing what failed and what survived&lt;/p&gt;

&lt;p&gt;So the flow is roughly:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;acceptance criteria → code → tests → mutation → report&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If the mapped code is changed and the linked tests &lt;strong&gt;&lt;em&gt;fail&lt;/em&gt;&lt;/strong&gt;, that is a useful sign.&lt;/p&gt;

&lt;p&gt;If the mapped code is changed and the linked tests still pass, that is also useful — because it shows a confidence gap that might otherwise stay hidden behind a green suite.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why this matters more now
&lt;/h2&gt;

&lt;p&gt;I do not think AI-assisted coding is the problem by itself.&lt;/p&gt;

&lt;p&gt;The problem is that AI increases output faster than it increases justified confidence.&lt;/p&gt;

&lt;p&gt;When implementation and tests both become cheap to generate, teams need better ways to distinguish between:&lt;br&gt;
    • code that looks tested&lt;br&gt;
    • code that is covered&lt;br&gt;
    • code whose important behavior is actually defended&lt;/p&gt;

&lt;p&gt;Without that distinction, it becomes very easy to over-trust green pipelines.&lt;/p&gt;

&lt;p&gt;That is the broader reason for ac-trace. I wanted something practical that pushes on this exact point.&lt;/p&gt;

&lt;h2&gt;
  
  
  Current scope
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/DmytroHuzz/ac-trace" rel="noopener noreferrer"&gt;ac-trace&lt;/a&gt; is still early and intentionally narrow.&lt;/p&gt;

&lt;p&gt;Right now it focuses on:&lt;br&gt;
    • Python&lt;br&gt;
    • pytest&lt;br&gt;
    • YAML manifests&lt;br&gt;
    • inferred links from annotated tests&lt;br&gt;
    • generated reports&lt;/p&gt;

&lt;p&gt;I kept the scope small on purpose. I would rather build a narrow tool around one precise question than make broad claims too early.&lt;/p&gt;

&lt;p&gt;This is an experiment in making one software-quality problem more concrete.&lt;/p&gt;

&lt;h2&gt;
  
  
  Launch note
&lt;/h2&gt;

&lt;p&gt;So this post is also the announcement: ac-trace is now open source: &lt;a href="https://github.com/DmytroHuzz/ac-trace" rel="noopener noreferrer"&gt;https://github.com/DmytroHuzz/ac-trace&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you work on backend systems, care about software quality, or are thinking seriously about how AI changes testing and confidence, I think this problem is worth exploring.&lt;/p&gt;

&lt;p&gt;I built ac-trace because I kept coming back to the same thought:&lt;/p&gt;

&lt;p&gt;Passing tests are useful, but they do not necessarily mean the &lt;strong&gt;&lt;em&gt;acceptance criteria are protected&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I want a more direct way to inspect that gap.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/DmytroHuzz/ac-trace" rel="noopener noreferrer"&gt;ac-trace&lt;/a&gt; is my open-source attempt to make the gap between green tests and justified confidence more visible.&lt;/p&gt;

&lt;h2&gt;
  
  
  CTA
&lt;/h2&gt;

&lt;p&gt;Check out the &lt;a href="https://github.com/DmytroHuzz/ac-trace" rel="noopener noreferrer"&gt;repo&lt;/a&gt;, try it on a small Python project, and tell me where the idea is useful, naive, or worth pushing further.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>ai</category>
      <category>development</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
