◇ OpenCyber Academy
Bachelor's · Chapter 3 · English

Chapter 3 — 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 3 — Programming I

Lesson map

Lesson 03 published. Map, example, EX005.

flowchart TD
  N0["Learning outcomes"]
  N1["3.1 Programs as precise statements"]
  N2["3.2 Text versus bytes"]
  N3["3.3 Randomness and hashing in the standard library"]
  N4["3.5 Practical activity"]
  N5["Synthesis"]
  N0 --> N1 --> N2 --> N3 --> N4 --> N5
  N5 --> EX["Worked example"]
  EX --> Q["Exercise EX005"]

Logical drawing

flowchart LR
  N0["Learning outcomes"]
  N1["3.1 Programs as precise statements"]
  N2["3.2 Text versus bytes"]
  N3["3.3 Randomness and hashing in the standard library"]
  N4["3.5 Practical activity"]
  N5["Synthesis"]
  N0 --> N1 --> N2 --> N3 --> N4 --> N5 --> Q["EX005"]
[Learning outcomes]
    |
    v
[3.1 Programs as precise statements]
    |
    v
[3.2 Text versus bytes]
    |
    v
[3.3 Randomness and hashing in the standard library]
    |
    v
[3.5 Practical activity]
    |
    v
[Synthesis]
    |
    v
[Worked example] --> [Exercise EX005]

Learning outcomes

You will (1) write small Python programs with functions, loops and conditionals; (2) handle text and bytes correctly; (3) use the standard library's hashlib and secrets modules; and (4) test a function with assertions.

3.1 Programs as precise statements

A program is an argument a machine can check by running it. Security work uses programs to reproduce claims: if a report says "this configuration offers only classical key exchange", a short script that parses the configuration and prints the groups makes the claim repeatable. Throughout this book labs are short Python programs using only the standard library, so that every reader can run them offline and compare output.

Start with the core constructs: variables hold values; functions name a reusable computation; for loops repeat; if statements choose. Readable programs use descriptive names, small functions and comments that explain why, not what.

3.2 Text versus bytes

Cryptographic functions operate on bytes, not text. In Python, "abc" is text (a sequence of Unicode characters) and b"abc" is bytes. Converting text to bytes requires an encoding, normally UTF-8: "abc".encode("utf-8"). A classic bug is hashing str(b"abc"), which is the six-character text b'abc', not the three bytes. The digest is silently wrong, and two systems that disagree on encoding will fail to verify each other's signatures. Interoperability problems in post-quantum deployments are often of this kind: wrong byte order, wrong encoding, wrong length.

3.3 Randomness and hashing in the standard library

secrets.token_bytes(32) returns 32 bytes from the operating system's cryptographically secure generator; the random module is not suitable for keys because its output is predictable from its seed. hashlib.sha256(data).hexdigest() computes a SHA-256 digest; hashlib.sha3_256 and hashlib.shake_128 compute members of the SHA-3 family used inside ML-KEM and ML-DSA. To compare secret values use hmac.compare_digest, which takes time independent of where the inputs differ.

Known-answer tests use published inputs and outputs, so a wrong implementation fails at once. The SHA-3 family is useful in this book because ML-KEM and ML-DSA use it internally. Never print secret bytes while debugging; print their length or a digest instead.

3.4 Worked example

An HCU intern writes a function to fingerprint certificates:

import hashlib
def fingerprint(der: bytes) -> str:
    return hashlib.sha256(der).hexdigest()
assert fingerprint(b"") == "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
print("fingerprint test passed")

The assertion uses the published digest of the empty string as a known answer. If someone later changes the function to hash text, the test fails immediately.

The test checks the function against an outside fact, not against its own output, which is what makes it able to fail. A second test with a real certificate's bytes and its published fingerprint would catch a different mistake, such as hashing a text encoding of the certificate instead of its raw bytes. The intern adds both tests to HCU's repository so that every later change is checked automatically.

3.5 Practical activity

Write is_valid_length(alg, data) that returns True only if data has the length expected for that algorithm, using a dictionary such as {"ML-KEM-768-ek": 1184} (the encapsulation-key size in FIPS 203). Test one correct and two incorrect inputs.

A length check is not a security check; it only catches gross errors such as truncated or concatenated values. It is still worth having, because many integration bugs in post-quantum migrations come from buffers sized for classical keys. Take the expected sizes from the standard itself rather than from a blog post, and note the document and table you used, so a reviewer can verify each number.

3.6 Common errors and limits

Each mistake produces code that seems to work. A key from the random module looks random but can be predicted by anyone who learns the seed. A digest of the wrong bytes looks like a digest and fails only when compared with another system. A test that compares a value with itself always passes, so it gives false confidence. Logging secrets leaves them in files that more people can read than the program itself.

Exercises

Synthesis

Small, tested programs make security claims reproducible. Bytes, encodings and secure randomness are where beginners' cryptographic code most often goes wrong.

Continue studying — chapter 03 of 40

← Chapter 02 (draft) · Course chapter index · Chapter 04 (draft) →