Chapter 3 — Programming foundations
Lesson map
Lesson 03 published. Map, example, EX005.
flowchart TD
N0["Learning outcomes"]
N1["3.1 Scripts for implementers"]
N2["3.2 Bytes and lengths"]
N3["3.3 Standard-library cryptography"]
N4["3.5 Practical activity"]
N5["3.7 Reading library documentation"]
N6["Synthesis"]
N0 --> N1 --> N2 --> N3 --> N4 --> N5 --> N6
N6 --> EX["Worked example"]
EX --> Q["Exercise EX005"]
Logical drawing
flowchart LR
N0["Learning outcomes"]
N1["3.1 Scripts for implementers"]
N2["3.2 Bytes and lengths"]
N3["3.3 Standard-library cryptography"]
N4["3.5 Practical activity"]
N5["3.7 Reading library documentation"]
N6["Synthesis"]
N0 --> N1 --> N2 --> N3 --> N4 --> N5 --> N6 --> Q["EX005"]
[Learning outcomes]
|
v
[3.1 Scripts for implementers]
|
v
[3.2 Bytes and lengths]
|
v
[3.3 Standard-library cryptography]
|
v
[3.5 Practical activity]
|
v
[3.7 Reading library documentation]
|
v
[Synthesis]
|
v
[Worked example] --> [Exercise EX005]
Learning outcomes
You will (1) write small Python scripts with functions, dictionaries and loops; (2) treat bytes, encodings and lengths precisely; (3) use hashlib, hmac and secrets correctly; and (4) write tests that can fail.
3.1 Scripts for implementers
Implementers script to inventory, verify and automate. A thirty-line script that parses configuration exports and flags classical-only services is worth more than a manual checklist because it can run every night. This book's labs use only the Python standard library so they run anywhere.
KLS's first such script reads the exported ingress and load-balancer configurations, lists the configured groups per listener and prints any tier-1 listener without X25519MLKEM768. Run nightly, it found the customer portal's misordered list long before the monitoring alert in Chapter 19 would have. Keeping the script under 50 lines kept it reviewable.
3.2 Bytes and lengths
Keys, ciphertexts and signatures are bytes with exact lengths: ML-KEM-768 encapsulation keys are 1,184 bytes and ciphertexts 1,088 bytes (FIPS 203); ML-DSA-65 public keys 1,952 bytes and signatures 3,309 bytes (FIPS 204). Code that stores these in fixed-size database columns, base64 fields or protocol buffers sized for ECDSA will truncate or reject them. Always check lengths explicitly and fail closed.
Lengths are also the cheapest validation an implementer can add. A function that rejects a 1,183-byte ML-KEM-768 key, as Lab V does in Chapter 15, catches truncation, wrong parameter sets and encoding mix-ups before any library call. KLS stores the parameter set beside each key so the expected length can be looked up rather than guessed.
3.3 Standard-library cryptography
secrets.token_bytes(n) gives secure random bytes; random does not. hashlib.sha256, hashlib.sha3_256 and hashlib.shake_256 compute hashes; hmac.new(key, msg, hashlib.sha256) computes MACs; hmac.compare_digest compares secrets in constant time. Python's standard library does not implement ML-KEM or ML-DSA; production code calls a vetted library or the platform provider.
hashlib.shake_256 is worth knowing because ML-KEM and ML-DSA use SHAKE internally; implementers meet it in known-answer tests even though they never implement the schemes. The secrets module draws from the operating system's generator, which is what KLS scripts use for lab keys and test vectors.
3.4 Worked example
A KLS developer stores public keys in a column VARCHAR(512) holding base64 text. Base64 expands bytes by 4/3, so a 1,952-byte ML-DSA-65 key needs 2,604 characters and is silently truncated by a permissive database mode. A test that round-trips a 1,952-byte random value through the storage layer would have caught it:
import base64, secrets
key = secrets.token_bytes(1952)
enc = base64.b64encode(key).decode()
assert len(enc) == 2604 and base64.b64decode(enc) == key
print("round trip ok,", len(enc), "chars")
The fix was twofold: the column became a binary type sized from the largest key KLS plans to store, ML-DSA-87's 2,592 bytes, and the database was switched to strict mode so an oversize value raises an error instead of being cut. The round-trip test now runs for every key type in the inventory's length table.
3.5 Practical activity
Write check_len(kind, data) using a dictionary of expected lengths for ML-KEM-512/768/1024 encapsulation keys, and test valid and invalid inputs.
A good test file has at least six cases: the three correct lengths (800, 1,184 and 1,568 bytes), a value one byte short, a value one byte long, and a correct length passed with the wrong kind label, such as 1,184 bytes labelled ML-KEM-512. The function should raise an error naming the kind, expected length and actual length. Run the tests once with a deliberate bug, a wrong number in the dictionary, to show they can fail.
3.6 Common errors and limits
- Fixed-size fields sized for classical keys.
randomfor secrets.- Tests that only exercise success paths.
At KLS each error had a cost. The VARCHAR(512) column silently truncated ML-DSA keys in a permissive mode. A load-test script that built session tokens with random produced predictable values that a reviewer could reproduce from the seed. A storage test that only wrote valid keys passed for months while the truncation bug sat behind it. Scripts are small, but each of these errors survives precisely because nobody thinks a small script needs failure tests.
3.7 Reading library documentation
Before calling any cryptographic API, read what it promises: which parameter set a function uses by default, whether it returns errors or raises exceptions, whether keys are expanded or stored as seeds, and which encodings it accepts. FIPS 203 and FIPS 204 allow private keys to be stored as short seeds that expand deterministically; two libraries may disagree on which form they export. Write a short note in your repository for each library you use, with version, defaults and encodings, and link it from the inventory so the next implementer does not have to rediscover it.
Exercises
- EX005 — Base64 sizing. How many base64 characters does a 3,309-byte ML-DSA-65 signature need? Rubric: 3 = 4,412 with method (ceil(3309/3) × 4).
- EX006 — Constant time. Explain why
a == bis risky for comparing MAC tags and what to use instead. Rubric: 3 = early-exit timing leak,hmac.compare_digest.
Synthesis
Implementation scripts must treat bytes and lengths exactly, use secure randomness and constant-time comparison, and be tested on failure as well as success.
Continue studying — chapter 03 of 30
← Chapter 02 (draft) · Course chapter index · Chapter 04 (draft) →