◇ OpenCyber Academy
Engineering · Chapter 9 · English

Chapter 9 — Programming II

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 9 — Programming II

Lesson map

Lesson 09 published. Map, example, EX017.

flowchart TD
  N0["Learning outcomes"]
  N1["9.1 Interfaces over implementations"]
  N2["9.2 Input validation"]
  N3["9.3 Testing beyond examples"]
  N4["9.4 Dependencies"]
  N5["Engineering note"]
  N6["Synthesis"]
  N0 --> N1 --> N2 --> N3 --> N4 --> N5 --> N6
  N6 --> EX["Worked example"]
  EX --> Q["Exercise EX017"]

Logical drawing

flowchart LR
  N0["Learning outcomes"]
  N1["9.1 Interfaces over implementations"]
  N2["9.2 Input validation"]
  N3["9.3 Testing beyond examples"]
  N4["9.4 Dependencies"]
  N5["Engineering note"]
  N6["Synthesis"]
  N0 --> N1 --> N2 --> N3 --> N4 --> N5 --> N6 --> Q["EX017"]
[Learning outcomes]
    |
    v
[9.1 Interfaces over implementations]
    |
    v
[9.2 Input validation]
    |
    v
[9.3 Testing beyond examples]
    |
    v
[9.4 Dependencies]
    |
    v
[Engineering note]
    |
    v
[Synthesis]
    |
    v
[Worked example] --> [Exercise EX017]

Learning outcomes

You will (1) structure code so that algorithms are replaceable; (2) parse and validate untrusted inputs such as certificates and key shares; (3) write property-based and known-answer tests; and (4) manage dependencies on cryptographic libraries. It builds on Chapter 4 and prepares agility (Chapter 39).

9.1 Interfaces over implementations

Write code against interfaces — kem.encapsulate(public_key), signer.sign(message) — not against algorithm-specific functions. An algorithm identifier, chosen by policy, selects the implementation. This is the programming basis of crypto-agility (CSWP 39): migration becomes a configuration change plus testing, not a rewrite.

The policy file names an algorithm per use, such as ML-DSA-65 for e-document signatures, and the code never mentions it. When BPA later moves from a hybrid to a pure post-quantum setting, the change is one line in policy plus the test run. Code review checks that no module bypasses the interface by importing an algorithm library directly.

9.2 Input validation

Post-quantum keys and ciphertexts have fixed lengths per parameter set. FIPS 203 requires checks on encapsulation keys (a modulus check that encoded coefficients are below q) and decapsulation keys before use. Code should reject wrong lengths before calling a library and never "pad" or "truncate" to make inputs fit.

An ML-KEM-768 encapsulation key must be exactly 1,184 bytes and a ciphertext exactly 1,088. Checking these lengths first gives a clear error message and avoids passing malformed data into code that might behave unpredictably. The modulus check catches keys whose coefficients were encoded with values of 3329 or higher, which a malicious peer might send to probe for implementation weaknesses.

9.3 Testing beyond examples

Known-answer tests confirm exact outputs. Property-based tests check invariants over many random inputs: decapsulate(encapsulate(pk)) yields the same key; a signature verifies for the signed message and fails for any one-bit change. Negative tests matter as much as positive ones.

A property test for BPA's signer runs a thousand random messages, signs each, verifies each, then flips one random bit in the message and in the signature and expects both checks to fail. A test that only checks successful verification would pass even if verify always returned true, which is precisely the bug negative tests exist to catch.

9.4 Dependencies

Pin versions of cryptographic libraries, track their advisories and record which algorithms and parameter sets each exposes. Draft-era names (e.g. "Kyber768") and final names (ML-KEM-768) are not interchangeable: the final standard changed details, so draft implementations do not interoperate with FIPS 203.

BPA's dependency file records the library name, the exact version, and the algorithms that version provides under final names. When a library release renames or removes a draft algorithm, the build fails at upgrade time rather than at a partner's handshake. Advisory tracking matters because post-quantum libraries are newer and have had more security fixes per year than mature RSA code.

9.5 Worked example

BPA's e-document service calls rsa_sign() in 41 places. Refactoring introduces Signer with implementations RSA-PSS-3072 and ML-DSA-65, selected by a policy file. A test suite runs every call path with both. The diff is large once; the next migration will be small.

The 41 call sites are first found by searching for rsa_sign, then each is checked for assumptions such as a fixed 384-byte signature length in a database column. ML-DSA-65 signatures are 3,309 bytes, so the schema change is part of the same work item. The policy file starts with RSA-PSS-3072 so nothing changes for users until the switch.

9.6 Common errors and limits

Hard-coded names turn each future migration into a code change at every call site, which is exactly what the Signer refactoring removed. Draft and final versions differ in details, so a library still offering Kyber768 will not interoperate with ML-KEM-768. Testing only success paths leaves validation and error handling unchecked, and attackers target precisely those paths with malformed keys and ciphertexts.

Engineering note

Interfaces also need versioned algorithm identifiers that travel with data. A signature stored today must carry an identifier naming its algorithm and parameter set, so that a verifier in ten years selects the right implementation and can refuse retired ones. Without identifiers, systems guess from lengths, which breaks as soon as two algorithms share a size. Treat the identifier as part of the signed or authenticated content wherever the format allows, so an attacker cannot relabel a weak signature as a strong one. This small design habit is what later makes policy-driven migration in Chapter 39 safe rather than merely convenient.

Exercises

Synthesis

Replaceable algorithms start in code: interfaces, policy-chosen identifiers, strict validation, property and negative tests, and dependency tracking that knows draft from final.

Continue studying — chapter 09 of 50

← Chapter 08 (draft) · Course chapter index · Chapter 10 (draft) →