Chapter 8 — Programming II
Lesson map
Lesson 08 published. Map, example, EX015.
flowchart TD
N0["Learning outcomes"]
N1["8.1 Structure and data"]
N2["8.2 Negative testing"]
N3["8.3 Handling secrets in code"]
N4["8.5 Practical activity"]
N5["Synthesis"]
N0 --> N1 --> N2 --> N3 --> N4 --> N5
N5 --> EX["Worked example"]
EX --> Q["Exercise EX015"]
Logical drawing
flowchart LR
N0["Learning outcomes"]
N1["8.1 Structure and data"]
N2["8.2 Negative testing"]
N3["8.3 Handling secrets in code"]
N4["8.5 Practical activity"]
N5["Synthesis"]
N0 --> N1 --> N2 --> N3 --> N4 --> N5 --> Q["EX015"]
[Learning outcomes]
|
v
[8.1 Structure and data]
|
v
[8.2 Negative testing]
|
v
[8.3 Handling secrets in code]
|
v
[8.5 Practical activity]
|
v
[Synthesis]
|
v
[Worked example] --> [Exercise EX015]
Learning outcomes
You will (1) structure larger programs with modules, data classes and error handling; (2) parse structured inputs such as CSV and JSON; (3) write tests for both valid and invalid inputs; and (4) avoid common security mistakes in code that handles keys.
8.1 Structure and data
As programs grow, structure keeps them correct. Group related functions in modules, represent records with dataclasses so fields are named, and raise exceptions for invalid states instead of returning silently wrong values. The standard library's csv and json modules parse common formats; validating every field as you read it — type, range, allowed values — stops bad data from propagating. In migration work, inventories arrive as CSV exports from scanners and configuration databases, and most analysis errors begin with an unvalidated column.
8.2 Negative testing
A positive test checks that valid input works; a negative test checks that invalid input is rejected. Cryptographic code needs both. A decoder for ML-KEM encapsulation keys must accept exactly 800, 1,184 or 1,568 bytes for the three parameter sets in FIPS 203 and reject anything else, and FIPS 203 also specifies input checks on the key's coefficients. Libraries that skipped such checks have historically accepted malformed inputs that leaked information or crashed services. Write the negative tests before you write the decoder.
HCU's tests include keys one byte too short and one byte too long.
8.3 Handling secrets in code
Keep secrets out of logs, exception messages and version control. Prefer passing key handles (references held by an HSM or OS keystore) instead of raw key bytes. Compare secrets with constant-time functions. Never write your own production implementation of a post-quantum algorithm; use a vetted library, and treat even small wrapper code with suspicion, because side channels and parsing bugs live in the glue as often as in the core.
For HCU, this means calling the approved library through a small interface and reviewing that interface as carefully as anything else.
8.4 Worked example
An HCU analyst receives inventory.csv with columns host, protocol, group. A careless script counts rows where group == "x25519" and reports "most hosts classical". A careful version normalises case, rejects rows with empty groups as unknown rather than classical, and prints three numbers: classical, hybrid and unknown. The careful script reveals that 18 % of synthetic rows are unknown — a fact the first script hid.
Treating unknown as classical would have made the inventory look more complete and more uniformly classical than it was, hiding the hosts whose cryptography nobody knew. Unknowns become a work list of their own: someone must check those hosts directly before the inventory can be trusted. The careful script also records the input file's date, so later readers know how fresh the counts are.
8.5 Practical activity
Write a function that reads lines host,group, validates that group belongs to an allowed set, and returns counts of hybrid, classical and unknown. Include one negative test with an unknown group string.
Normalise case and spaces before comparing, and decide in advance what counts as hybrid. A group name you have never seen should fall into unknown, never into classical by default. Your negative test should show that a misspelt or invented group name is counted as unknown and that the function does not crash on an empty line. Print the three counts in a fixed order so outputs from different runs can be compared.
8.6 Common errors and limits
- Treating missing data as a negative finding.
- Catching all exceptions and continuing silently.
- Writing custom cryptography instead of calling a vetted implementation.
Missing data is a finding about the inventory, not about the systems. Swallowing exceptions hides the rows that failed, so the totals look complete when they are not; log each failure with its line number instead. Custom cryptography fails in ways that tests rarely catch, such as timing differences, and vetted libraries have had years of review that a new implementation cannot match.
Exercises
- EX015 — Negative tests. List four negative tests for a function that loads an ML-DSA-65 public key (1,952 bytes in FIPS 204). Rubric: 3 = four distinct, meaningful failure cases.
- EX016 — Unknown is not no. Explain in two sentences why counting empty fields as "classical" biases an inventory. Rubric: 3 = identifies bias direction and decision consequence.
Synthesis
Structured code, validated inputs and negative tests make analysis trustworthy; in cryptographic code, rejecting bad input is as important as handling good input.
Continue studying — chapter 08 of 40
← Chapter 07 (draft) · Course chapter index · Chapter 09 (draft) →