Skip to content
academia.sh

Lesson 01 / 21

Hashing, Encryption, and Encoding

Separating three operations in terms of reversibility: the encoded, encrypted, and hashed form of the same meter reading, the number of secret items needed for reversal being 0, 2, and none respectively, only four of the twelve matches between four fields and three operations coming out appropriate, and a wrong match destroying the reversal of 24,800 records.

Published Updated

In this lesson

  • Separates hashing, encryption, and encoding by the number of secret items needed for reversal
  • Measures the output length and determinism of the three operations on the same input
  • Counts the cost of using an operation outside its purpose in terms of reversal items and lost records
Contents

The previous course counted the claims on the network and arrived at the same place across twenty lessons: a claim is accepted only to the extent it can be proven. The question left open at the close concerned the proof itself — what does that proof rest on, who holds the key, where exactly does trust begin? This course names that starting point and counts what falls when it breaks.

Before naming it, a distinction is needed. In the fictional regional measurement network, a meter reading passes through three separate operations on its way from the field to the center, and all three render the output unreadable. To someone looking at a record dump, all three look the same: a meaningless string of letters and digits. Yet the security value of the three is independent of one another, and the difference between them is measured by a single question — how many secret items does one need on hand to go from this output back to the input?

Three Operations, Three Separate Questions

CF1: encoding is a change of representation; it writes the data’s bytes in a different alphabet, and reversal needs no secret item. Its purpose is portability: a byte passing through the transport path without corruption.

CF2: encryption is a reversible transformation; reversal depends on a countable number of secret items. Its purpose is confidentiality: a party without the item being unable to learn the content.

CF3: hashing is a one-way digest; no item suffices to go from the output back to the input, because the inverse is not a defined operation. Its purpose is integrity and comparison: testing whether two pieces of data are the same without holding the data itself.

What the three sentences share is that none of them uses the word “strong” or “secure.” All three say something countable: the number of items needed for reversal is, respectively, zero, more than one, and none.

The Same Input Passes Through Three Operations

The unit below passes a fictional meter reading through three operations. The key is derived from a fixed seed so the output stays the same from run to run; it is not a real key, and in a real setup a key is not produced this way.

// crypto/triad.mjs — passes the same input through three operations and measures
// reversibility: encoding (base64), encryption (AES-256-GCM), hashing (SHA-256). The input is
// a meter reading from the fictional regional measurement network. There is no real key; the key
// is derived from a fixed seed so the output stays the same from run to run. No randomness.
import { createHash, createCipheriv, createDecipheriv, scryptSync } from "node:crypto";

// CF1: encoding is a change of representation; reversal needs no secret item.
// CF2: encryption is a reversible transformation; reversal depends on exactly a
//      countable number of secret items.
// CF3: hashing is a one-way digest; no item gives back the reversal.

export const READING = "SAY-4417|2026-03-01T06:00Z|41982";
export const LONG    = READING + "|SAY-4418|2026-03-01T06:00Z|8813304";
const KEY = scryptSync("kurgu-tohum", "kurgu-tuz", 32, { N: 2 ** 12, r: 8, p: 1 });
const NONCE = Buffer.alloc(12, 7);                    // fictional, fixed

export const encode = (m) => Buffer.from(m).toString("base64");
export const hash   = (m) => createHash("sha256").update(m).digest("base64");
export function encrypt(m) {
  const s = createCipheriv("aes-256-gcm", KEY, NONCE);
  const g = Buffer.concat([s.update(m, "utf8"), s.final()]);
  return Buffer.concat([g, s.getAuthTag()]).toString("base64");
}
export function decrypt(g64, key = KEY) {
  const t = Buffer.from(g64, "base64");
  const c = createDecipheriv("aes-256-gcm", key, NONCE);
  c.setAuthTag(t.subarray(t.length - 16));
  return Buffer.concat([c.update(t.subarray(0, t.length - 16)), c.final()]).toString();
}
export const attempt = (f) => { try { return f() === READING ? "yes" : "no"; }
                                catch { return "no"; } };
export const WRONG = scryptSync("baska-tohum", "kurgu-tuz", 32, { N: 2 ** 12, r: 8, p: 1 });
// crypto/measure.mjs — the output form, determinism, and reversal of the three operations.
import { READING, LONG, encode, hash, encrypt, decrypt, attempt, WRONG } from "./triad.mjs";

const printRow = (g, ...s) => console.log(s.map((v, i) =>
  (g[i] < 0 ? String(v).padEnd(-g[i]) : String(v).padStart(g[i]))).join(""));
const A = [-11, 7, 7, 15, 14];
printRow(A, "operation", "short", "long", "deterministic", "secret item");
for (const [name, f, item] of [["encoding", encode, 0], ["encryption", encrypt, 2],
                               ["hashing", hash, "-"]])
  printRow(A, name, f(READING).length, f(LONG).length,
      f(READING) === f(READING) ? "yes" : "no", item);

console.log("\nreversal actually attempted (did it give back the exact input):");
const B = [-34, 6];
for (const [name, f] of [
  ["encoding, no item on hand", () => Buffer.from(encode(READING), "base64").toString()],
  ["encryption, with the correct key", () => decrypt(encrypt(READING))],
  ["encryption, with the wrong key", () => decrypt(encrypt(READING), WRONG)],
  ["encryption, with one byte changed", () => {
    const b = Buffer.from(encrypt(READING), "base64"); b[3] ^= 1;
    return decrypt(b.toString("base64")); }],
]) printRow(B, name, attempt(f));
printRow(B, "hashing, no inverse function", "no");

console.log("\nthe three output forms of the same input:");
console.log("  encoding  :", encode(READING));
console.log("  encryption:", encrypt(READING));
console.log("  hashing   :", hash(READING));
operation    short   long  deterministic   secret item
encoding        44     92            yes             0
encryption      64    112            yes             2
hashing         44     44            yes             -

reversal actually attempted (did it give back the exact input):
encoding, no item on hand            yes
encryption, with the correct key     yes
encryption, with the wrong key        no
encryption, with one byte changed     no
hashing, no inverse function          no

the three output forms of the same input:
  encoding  : U0FZLTQ0MTd8MjAyNi0wMy0wMVQwNjowMFp8NDE5ODI=
  encryption: KtcWlbMqgsctrQ9mH5TfZXklUgBZe3RQghkBaCASMIF/zJIXfxBHDQ8B0sIX7nzA
  hashing   : 6MaKWYn3eYX57Bdf2685XV76MBSSJCOE1SLYc6Rz7zg=

What Is Measured: The Items Needed for Reversal

The last three lines are the lesson’s starting point. All three outputs look unreadable, and all three are roughly the same length. Appearance says nothing; the tables do.

The length columns give the first distinction. As the input grows, encoding’s output goes from 44 to 92, and encryption’s output goes from 64 to 112 — both grow with the input, because both carry the entire input. Hashing’s output is 44 characters for both inputs. That fixed length is direct proof that hashing does not carry the input: a 64-character input and a 6-kilobyte file both come down to the same 32 bytes. Information that does not fit inside cannot be extracted back out.

In the determinism column, all three give yes; the same input always produces the same output. This property is what makes verification possible, and it is also the source of a constraint the second lesson measures: if two records are equal, their outputs are equal too.

The reversal table is the real measurement, and all four rows were actually attempted. The encoded value was opened with zero items on hand — reversal is an operation anyone who sees the output can perform. The encrypted value opened with the correct key and did not open with the wrong key. The third row shows the second secret item: when a single byte of the ciphertext changed, decryption failed, because the mode of operation in use appends an authentication tag to the output and checks it before decrypting. Encryption’s reversal therefore depends on not one but two items: the key and an uncorrupted ciphertext. The hashing row has nothing to even attempt; there is no such thing as an inverse function.

Where Encoding Fits and Telling It Apart from the Output

Encoding not having a security function does not mean it is unnecessary. In the fictional measurement network, the packet transferred between branches passes through a text-carrying channel, and part of the raw bytes gets interpreted on that channel as a delimiter or control character. Encoding solves exactly that: it writes the bytes into an alphabet the channel can carry safely. That is why encoding comes out appropriate in the table’s transfer packet row — what is wanted in that field is not confidentiality, but arriving without corruption.

The confusion comes from an encoded value looking unreadable. Telling the three apart by looking at the output is nevertheless possible, and the distinguishing marks are right there in the output above. An encoded value’s length is proportional to the input’s length, and decoding it yields meaningful text; the trailing = padding is also a mark of the alphabet in use. Hashing’s output is always the same length regardless of the input — 32 bytes, 44 characters in this alphabet. An encrypted value grows with the input but decrypting it yields meaningless bytes and it usually carries a fixed overhead: the 16-byte authentication tag here is that overhead.

If a record dump shows fixed-length 44-character fields, that is a hashing column; fields whose length varies from row to row are encoding or encryption. The distinction is not a guess — it is a measurable property, and it is the first column to check during an audit.

The Countable Result of Mixing Them Up

Separating the three operations is not a terminology debate. The fictional measurement network has four fields, and each field has two known properties: whether the value needs to be retrieved later, and whether the value needs to stay secret. These two properties determine the correct operation; a wrong choice costs two separate numbers.

// crypto/match.mjs — matching the four fields' purpose against the three operations in
// the fictional measurement network (model): how many items each wrong match results in.
// CF4: the cost of using an operation outside its purpose is measured not in appearance but
//      in item count: the items needed for reversal and the records whose reversal is lost.
const FIELD = [                      // record counts are fictional
  { name: "meter reading value",  records: 12400, reversed: true,  secret: true,  correct: "encryption" },
  { name: "field crew password",  records:   340, reversed: false, secret: true,  correct: "hashing" },
  { name: "device serial digest", records: 12400, reversed: false, secret: false, correct: "hashing" },
  { name: "transfer packet",      records: 12400, reversed: true,  secret: false, correct: "encoding" },
];
const REVERSAL = { encoding: 0, encryption: 1, hashing: null };   // items needed for reversal

function decide(a, op) {
  if (op === a.correct)                  return ["fits", REVERSAL[op], 0];
  if (op === "hashing")                  return ["no reversal", null, a.records];
  if (op === "encoding" && a.secret)     return ["reads with 0 items", 0, 0];
  if (op === "encoding")                 return ["raw value stays", 0, 0];
  if (!a.reversed)                       return ["single root opens everything", 1, 0];
  return ["unnecessary root", 1, 0];
}
const printRow = (g, ...s) => console.log(s.map((v, i) =>
  (g[i] < 0 ? String(v).padEnd(-g[i]) : String(v).padStart(g[i]))).join(""));
const G = [-22, -11, 9, 8, 10, 8, -32];
printRow(G, "field", "operation", "reversed", "secret", "reversal", "lost", "  decision");
let fitCount = 0, zeroCount = 0, lostTotal = 0;
for (const a of FIELD) for (const op of ["encoding", "encryption", "hashing"]) {
  const [d, rv, lost] = decide(a, op);
  if (d === "fits") fitCount++; else if (rv === 0) zeroCount++;
  lostTotal += lost;
  printRow(G, a.name, op, a.reversed ? "yes" : "no", a.secret ? "yes" : "no",
      rv === null ? "-" : rv, lost || "-", "  " + d);
}
console.log(`\n${FIELD.length * 3} matches, ${fitCount} fit; ${zeroCount} matches bring the ` +
  `raw value back with 0 items, and a wrong hash destroys the reversal of ${lostTotal.toLocaleString("en-US")} records`);
field                 operation   reversed  secret  reversal    lost  decision
meter reading value   encoding         yes     yes         0       -  reads with 0 items
meter reading value   encryption       yes     yes         1       -  fits
meter reading value   hashing          yes     yes         -   12400  no reversal
field crew password   encoding          no     yes         0       -  reads with 0 items
field crew password   encryption        no     yes         1       -  single root opens everything
field crew password   hashing           no     yes         -       -  fits
device serial digest  encoding          no      no         0       -  raw value stays
device serial digest  encryption        no      no         1       -  single root opens everything
device serial digest  hashing           no      no         -       -  fits
transfer packet       encoding         yes      no         0       -  fits
transfer packet       encryption       yes      no         1       -  unnecessary root
transfer packet       hashing          yes      no         -   12400  no reversal

12 matches, 4 fit; 3 matches bring the raw value back with 0 items, and a wrong hash destroys the reversal of 24,800 records

Four of the twelve matches fit; eight carry a cost, and the costs are not of the same kind.

The quietest flaw is in the first row. If the meter reading is encoded, the record looks unreadable and whoever is looking assumes it is protected; yet the reversal item count is zero. Because encoding has no secret item at all, treating an encoded value as protected is the same thing as recording an unprotected value as protected. The same flaw repeats in the password row, where the record count is 340.

The second flaw runs the other way. If the field crew password is encrypted, reversal becomes possible — and that is exactly what is unwanted. Encryption ties all 340 records to a single item, the key. When that item breaks, the number of records that fall is 340; in hashing, there is no shared item to break. This row is the course’s first concrete example of its measurement axis: a wrong choice of operation creates a root, and the cost of creating a root is the number of items that fall if that root breaks.

The third flaw is the irreversible one. If the meter reading or the transfer packet is hashed, the value is not protected — it is lost; across the two fields, 24,800 records total become unreadable again. Unlike the other two, this flaw surfaces not at the moment of a leak but at the moment of first writing, and it cannot be undone.

The Course’s Three Columns

This lesson sets up the three questions that will be asked of every mechanism throughout the course, and answers the first one.

Trust root. What does the mechanism assume to be true from the start? Encoding has no root — it assumes nothing and promises nothing. Encryption’s root is the key staying secret. Hashing’s root is that two different inputs do not produce the same output, and that root is the subject of the next lesson.

How many items must be true at once. From root to use, how many components need to hold together? In encryption, the number measured is two: the key must stay secret and the ciphertext must be uncorrupted. In encoding, it is zero. In hashing, it is one: the function’s collision resistance.

How many items fall if the root breaks. If the key leaks, every record encrypted with that key falls; in the table’s password row, that is 340. In encoding, there is no root to break, because nothing is closed. In hashing, if the root breaks, what falls is not the records’ secrecy but the meaning of the comparison.

Every lesson throughout the course fills in these three columns. “That algorithm is strong” carries no decision. The root’s name, the chain’s length, and the number of items that fall at the moment of breaking do. A fourth question always accompanies the third column: what does renewal cost once the root breaks? When a key leaks, producing a new key is a single operation, but re-encrypting the 12,400 records that key protected and getting the key to every endpoint that needs it are separately counted items. The cost measured in this course is not the break itself, but the number of items touched by hand after the break.

Summary

  • The three operations are separated by the number of secret items needed for reversal: encoding 0, encryption 2 (the key and an uncorrupted ciphertext), and for hashing, no item suffices.
  • Encoding’s and encryption’s output grow with the input (44→92 and 64→112 characters); hashing’s output is 44 characters for every input, and that fixed length is proof that the input is not carried.
  • All three operations are deterministic: the same input always gives the same output.
  • Of the twelve matches between four fields and three operations, only four fit; in three matches, the raw value comes back with 0 items, and in two matches, the reversal of 24,800 records is destroyed.
  • Every mechanism is measured with three columns: the trust root’s name, the number of items that must be true at once from root to use, and the number of items that fall when the root breaks.

Next Step

In this lesson, hashing’s root was passed over in one sentence: two different inputs do not give the same output. But that sentence, as it stands, cannot be true. Hashing’s output has a fixed length and the number of possible inputs is unlimited; therefore, different inputs that give the same output certainly exist. The root is the assumption not that a collision does not exist, but that it cannot be found. The next lesson turns that assumption into a number: at how many bits of output is the first collision found in how many attempts, how closely does the measured number match the theoretical bound, and what happens to the number of attempts needed when the output length doubles. The same measure also explains why deprecated hashing algorithms are retired step by step.

To keep your progress and take notes, Log in

My notes

Log in to take notes.

Start typing to search.

↑↓ ↵ Esc navigate · open · close