◇ OpenCyber Academy
Engineering · Chapter 4 · English

Chapter 4 — Programming I

Open, free learning for anyone who wants to protect people and technology. No account is required to read this text or its worked answers.

Chapter 4 — Programming I

Lesson map

Lesson 04 published. Map, example, EX007.

flowchart TD
  N0["Learning outcomes"]
  N1["4.1 Bytes, not strings"]
  N2["4.2 Randomness"]
  N3["4.3 Hashes and MACs"]
  N4["4.4 Testing"]
  N5["4.6 Practical activity"]
  N6["Synthesis"]
  N0 --> N1 --> N2 --> N3 --> N4 --> N5 --> N6
  N6 --> EX["Worked example"]
  EX --> Q["Exercise EX007"]

Logical drawing

flowchart LR
  N0["Learning outcomes"]
  N1["4.1 Bytes, not strings"]
  N2["4.2 Randomness"]
  N3["4.3 Hashes and MACs"]
  N4["4.4 Testing"]
  N5["4.6 Practical activity"]
  N6["Synthesis"]
  N0 --> N1 --> N2 --> N3 --> N4 --> N5 --> N6 --> Q["EX007"]
[Learning outcomes]
    |
    v
[4.1 Bytes, not strings]
    |
    v
[4.2 Randomness]
    |
    v
[4.3 Hashes and MACs]
    |
    v
[4.4 Testing]
    |
    v
[4.6 Practical activity]
    |
    v
[Synthesis]
    |
    v
[Worked example] --> [Exercise EX007]

Learning outcomes

You will (1) write small, testable Python programs with the standard library; (2) handle bytes, encodings and integers correctly; (3) use hashlib, hmac and secrets safely; and (4) recognize why you must not implement production cryptography yourself. It prepares the labs used throughout the book.

4.1 Bytes, not strings

Cryptographic functions operate on bytes. Python's str is text; convert with an explicit encoding ("text".encode("utf-8")). Mixing them causes subtle bugs, such as hashing the string representation of a byte object ("b'...'") instead of the bytes themselves. Integers convert with int.from_bytes(b, "big") and n.to_bytes(length, "big"); always state the byte order and length, because protocol specifications such as FIPS 203 define exact encodings.

A common symptom of mixing types is two systems computing different hashes for the same message because one encoded text as UTF-8 and the other as Latin-1. Fixing the encoding at the boundary, and passing bytes everywhere inside, removes that class of bug. Length and byte order matter too: FIPS 203 encodes coefficients in a precise bit layout, and a test against the standard's vectors is the only reliable check.

4.2 Randomness

Use secrets.token_bytes() for keys and nonces; never random, which is a predictable pseudo-random generator designed for simulations. The labs in this book use random only to generate synthetic data and toy parameters, and say so.

Python's random is seeded from a state that can be recovered after observing enough outputs, so any key it produces can be predicted. secrets draws from the operating system's cryptographic generator. Keeping the two visibly separate in code, for example by never importing random in production modules, makes reviews easier and stops toy code from slipping into real systems.

4.3 Hashes and MACs

hashlib.sha256, hashlib.sha3_256 and hashlib.shake_256 are available; SHA-3 and SHAKE are used inside ML-KEM and ML-DSA. hmac.new(key, msg, hashlib.sha256) computes a message authentication code, and hmac.compare_digest compares tags in constant time — a normal == can leak timing information about where two values first differ.

SHAKE is an extendable-output function: it can produce as many output bytes as needed, which ML-KEM uses to expand seeds into matrices. A MAC differs from a hash because it needs a secret key; without one, anyone can recompute a hash over a modified message. The constant-time comparison matters most when an attacker can submit many guesses and measure response times.

4.4 Testing

Every function gets a test with a known answer. For cryptography, known-answer tests (KATs) published with standards let you confirm that an implementation produces exactly the expected bytes. NIST's Cryptographic Algorithm Validation Program publishes test vectors for approved algorithms.

A KAT fixes the inputs, including what would normally be random, so the expected output is a single exact byte string. Passing KATs proves the implementation matches the standard on those inputs; it does not prove resistance to side channels or misuse. Engineers run KATs in continuous integration so that a library upgrade that changes outputs is caught before deployment.

4.5 Worked example: a hash-chain audit log

BPA wants tamper-evident logs. Each entry stores h_i = SHA-256(h_{i-1} || entry_i). A short program computes the chain, and a test verifies that changing any entry changes every later hash. The program uses bytes throughout, fixed encodings and compare_digest. This is a sound use of standard primitives; designing a new signature scheme would not be.

The chain detects modification, deletion and reordering, because each change alters every later hash. It does not stop someone with write access from rebuilding the whole chain, so BPA publishes the latest hash daily to a separate system. The program is short enough to review completely, and every primitive it uses is already standardised.

4.6 Practical activity

Write the hash-chain function with a test, then add a deliberate bug (hashing str(entry) instead of entry) and see the test catch it.

The deliberate bug shows why tests must compare exact bytes: hashing str(entry) hashes the text "b'...'" with quotes and a prefix, so every later hash changes even though the log content looks identical. Write the failing test first, confirm it fails with the bug, then fix the code and confirm it passes. Keep the test in the suite so the mistake cannot return.

4.7 Common errors and limits

Each error has caused real failures. Predictable random numbers have exposed keys, plain comparisons have leaked authentication tags through timing, and home-made implementations of standard algorithms have passed correctness tests while leaking secrets through timing or power use. BPA uses vetted libraries for ML-KEM and ML-DSA and reserves its own code for the glue around them, which it tests with KATs.

Exercises

Synthesis

Cryptographic code is byte-exact, uses secure randomness, compares secrets in constant time, and is tested against known answers; engineers compose vetted primitives rather than inventing them.

Continue studying — chapter 04 of 50

← Chapter 03 (draft) · Course chapter index · Chapter 05 (draft) →