14 · Cryptography and secure engineering
Depth: developed lesson · Prerequisites: foundations, code and byte interpretation · Time: two 90-minute sessions.
A cryptographic primitive answers a narrow question about its inputs. A secure system must also decide who controls the key, which bytes are authenticated, whether a request is fresh and what the recipient is allowed to do. Confusing these responsibilities creates convincing-looking checks that protect the wrong property.
This chapter uses a published HMAC test vector and original synthetic messages. The test key is public. Nothing here encrypts data, stores a password or implements a production authentication protocol.
Outcomes
- Distinguish integrity, authenticity, confidentiality and freshness.
- Explain why a replaced file and replaced reference hash can agree.
- Verify a standard HMAC vector and challenge tampered inputs.
- Demonstrate that checking the same valid tag twice does not reject replay.
- Turn a security claim into explicit positive and negative tests.
Name the property before choosing the mechanism
| Property | Question | What this lesson measures |
|---|---|---|
| Byte integrity | Are these the bytes associated with a trusted reference? | Compare SHA-256 values for specific messages |
| Message authentication | Does this message match a tag under the supplied key? | Full HMAC-SHA-256 verification |
| Confidentiality | Can an observer recover the message? | Not provided; all messages remain readable |
| Freshness | Is this an acceptable new event rather than an old replay? | Not enforced; replay succeeds |
| Authorization | May this actor perform this action? | Not provided by the primitive |
A hash is useful for identifying retained evidence, as in reporting. But a reference hash needs its own trust story. If someone can replace both an artifact and the adjacent digest, checking that pair against itself does not authenticate the publisher.
An HMAC uses a secret key with a hash function to authenticate a message. Python's hmac module provides the primitive; compare_digest is the recommended comparison method for checking supplied tags while reducing content-dependent timing differences. Python hmac documentation.
In a real symmetric-key arrangement, any holder of the key can create a valid tag. A matching tag therefore does not identify one particular human among all key holders, and it does not provide public signature verification. Our public teaching key cannot authenticate a real sender at all.
Session 1: verify the primitive against a known answer
The fixture uses RFC 4231 case 1 for HMAC-SHA-256: twenty bytes of hexadecimal 0b, the ASCII message Hi There, and this full 32-byte tag:
b0344c61d8db38535ca8afceaf0bf12b
881dc200c9833da726e9376c2e32cff7
The line break is for display; the implementation joins the hexadecimal text into one tag. The value is a published interoperability test, not a generated Deadwire benchmark. RFC 4231, section 4.2.
Run the standard-library exercise from the repository root:
python3 -m labs.integrity
Expect rfc4231_case1_passed: true. Inspect labs/integrity/__main__.py: the expected bytes are fixed independently of the library's newly calculated result. A test that calculates both expected and actual output using the same expression can reproduce the same mistake twice.
Passing this vector establishes agreement for one published input. It does not certify an implementation, measure resistance to every attack or validate a protocol around it. The fixture tests also reject a truncated tag and a changed message under the original tag.
Predict the original teaching cases
The original message is ASCII read:record:101; the changed message is read:record:202. These are inert bytes, not requests sent to the web lab. The evaluator retains the original hash and HMAC, then compares changed inputs.
| Output field | Expected | Interpretation |
|---|---|---|
original_hash_matches |
true | Same bytes match their original digest |
changed_bytes_match_original_hash |
false | This changed message differs from the retained reference |
changed_bytes_match_attacker_replaced_hash |
true | An untrusted replacement digest can match replacement bytes |
original_hmac_accepted |
true | The original tag matches under the supplied key |
changed_bytes_original_hmac_accepted |
false | Keeping the tag while changing these bytes fails |
wrong_key_accepted |
false | This different key does not validate the retained tag |
same_message_and_tag_replayed_accepted |
true | The stateless check does not track prior acceptance |
Write down which outcomes concern integrity, authentication and freshness. Do not describe the HMAC result as “the message was encrypted”: the plaintext is present throughout the exercise.
Session 2: demonstrate the key-management limitation
Because the fixture key is public, anyone can create a tag for the changed message. Run this bounded experiment from the repository root:
import hmac
from labs.integrity.__main__ import TEST_KEY, authenticates
changed = b'read:record:202'
replacement_tag = hmac.digest(TEST_KEY, changed, 'sha256')
print(authenticates(changed, replacement_tag, TEST_KEY))
Expect True. This is not a break of HMAC. The actor was given the key. A system that leaks a key has changed the assumptions under which the message-authentication claim was meaningful.
Now draw a receiver that needs both authentication and freshness. Add a place to check a unique request identifier or sequence against durable receiver state. Explain how that identifier becomes part of the authenticated bytes, what happens on duplicate delivery, and how a crash could affect the decision. This is a design exercise, not an invitation to deploy an improvised protocol. Reviewed application protocols and libraries remain the implementation baseline.
Replay reasoning to compare with your diagram
The existing function has only message, tag and key inputs. Two identical calls have no history that could make the second result different. Rejecting a replay requires additional authenticated context and a policy or state that distinguishes acceptable events. A timestamp alone also needs a clock/window policy and cannot automatically prevent repeated use inside the accepted window.
Transfer the lesson into code review
Ask a reviewer to locate the trusted reference, the key source, the exact authenticated byte representation and the place where an accepted result causes a side effect. These are distinct boundaries. Parsing JSON into an object and reserializing it differently can change bytes even when a human sees similar content; the actual protocol must define what is signed or authenticated.
For a small component, useful tests include a known answer, an unchanged valid message, changed data with the original tag, the wrong key, a malformed tag and a replay case. The expected replay outcome depends on the component's contract. Our primitive wrapper correctly accepts the same valid input again; an application promising one-time requests would need to reject the second operation elsewhere.
Encryption modes, signatures, password hashing, TLS, key rotation and production key custody remain deeper topics. Do not substitute this public-keyed teaching function for any of them.
Evidence and acceptance
Save the run output, your pre-run predictions, the public-test-key counterexample and the receiver diagram. Explain the exact trust assumption behind each true result. Acceptance requires identifying replay as unhandled and distinguishing successful primitive verification from proof of a sender, fresh event or authorized operation.
Safety and scope
Only fixed public test material is used. Do not insert real credentials or application keys. The exercise performs no network or filesystem operations and leaves no persistent state. Never reuse the fixture key in an application.
Teach-back
- Why can a matching hash fail to authenticate a downloaded artifact?
- Why is producing a tag with the public fixture key not a cryptographic attack?
- Which additional responsibility makes replay handling different from HMAC verification?
- What would you need to establish before trusting a production protocol's use of this primitive?
Continue: revisit the reporting exercise and annotate which integrity claims its evidence hash can actually support.