We close the fundamentals module with the single most important lesson for your professional practice: the one that defines how to think in cryptography. In 1883, the cryptographer Auguste Kerckhoffs formulated a principle that still governs the discipline: a cryptographic system must be secure even if the adversary knows everything about it except the key. From this principle follow very practical consequences: why you should never invent your own cipher, why "security through obscurity" fails as a sole defense (with illustrious casualties: DVD CSS, GSM A5, Mifare cards), why even a string comparison can leak a secret (side-channel attacks), and which libraries deserve your trust. The lesson ends with the golden rules we'll keep reinforcing throughout the course.

Contents

  1. Kerckhoffs's principle: statement and implications
  2. Security through obscurity: why it fails as a sole defense
  3. "Don't invent your own cryptography": three historical disasters
  4. Side-channel attacks: when the implementation betrays you
  5. MediNube case: comparing secrets with secrets.compare_digest
  6. Crypto-agility: designing so you can change
  7. Serious libraries versus hand-rolled primitives
  8. The course's golden rules

Kerckhoffs's principle: statement and implications

The modern statement of Kerckhoffs's principle:

A cryptographic system must be secure even if everything about the system — except the key — is public knowledge.

Claude Shannon, the father of information theory, restated it decades later even more bluntly: "the enemy knows the system" (Shannon's maxim). Always design under that assumption. Why is it a good idea to concentrate all the secrecy in the key, instead of spreading it across the algorithm too?

  • Keys are easy to protect and replace; algorithms are not. A key is 32 bytes: it's stored in a secrets manager and rotated in minutes if it leaks. An algorithm lives in every deployed binary, in every copy of the code, in the memory of every developer who has worked on it. When "the algorithm leaks" (and it always eventually does: reverse engineering, employees who leave, exposed repositories), you can't "rotate it" without rebuilding the entire system.
  • Only public scrutiny builds confidence. A secret algorithm has, at best, been reviewed by five people at your company. AES has been under attack since 1998 by the best cryptanalysts on the planet, with reputations and careers on the line for finding cracks in it — and it's still standing. An algorithm's security isn't proven: it's survived, and you only survive the attacks that are actually attempted.
  • It separates security from deployment. If security requires the attacker not to know "how it works," then every employee, contractor, repository backup, and memory dump is a catastrophic, unrecoverable point of failure. If only the key is secret, the surface you need to protect is minimal and well defined.

Notice that the principle doesn't say you must publish your design, nor that extra secrecy gets in the way. It says security cannot depend on that secret. It's a mental test you can apply to any system: "If the attacker reads all of our code and documentation tomorrow, can they still not access the data?" If the answer is no, the system is broken today — you just don't know it yet.

Security through obscurity: why it fails as a sole defense

Security through obscurity is relying on the attacker not discovering how something works: the homegrown algorithm, the undocumented endpoint, the secret parameter, the non-standard port. It fails as a primary defense for three structural reasons:

  1. Obscurity can't be measured. For a 128-bit key we know exactly how much it costs to break it. How much does it "cost" to discover your homegrown algorithm? Maybe an afternoon with a decompiler. You can't reason about what you can't quantify.
  2. Obscurity can't be recovered. A leaked key gets rotated. A leaked design secret is leaked forever.
  3. Obscurity erodes on its own. Every new employee, every vendor, every traffic analysis, every distributed binary chips away at the secret. Time works against it.

Important professional nuance: obscurity as an extra layer on top of a solid design isn't bad (not publishing your network's internal topology adds real friction for the attacker and costs nothing). What's indefensible is for it to be the only barrier, or to replace a real one. In the code review from lesson 01-02 you already saw textbook security through obscurity: Base64 as "protection" works exactly until someone looks.

"Don't invent your own cryptography": three historical disasters

The number-one practical consequence of Kerckhoffs's principle for a developer: don't design or implement your own cryptographic algorithms for production. It isn't a matter of humility but of statistics: secret, proprietary algorithms have a nearly perfect track record of getting broken. Three famous cases, all following the same pattern:

DVD CSS (Content Scramble System)

The video DVD encryption system (1996) used a secret algorithm with 40-bit keys, protected by non-disclosure agreements. In 1999 it was reverse-engineered (from a software player) and DeCSS was published. Analysis revealed the design was so weak that the practical attack cost ~2^25 operations — seconds on a PC of that era. It stayed broken forever: you can't "update the algorithm" on millions of discs and players already manufactured.

GSM A5/1 and A5/2

The encryption algorithms for GSM calls (1980s-90s) were designed in secret by industry committees. They leaked and were reverse-engineered in the 90s, and public cryptanalysis demolished them: A5/2 (the export variant, deliberately weakened) is broken in real time; for A5/1, precomputed tables were published that let anyone decrypt calls with home hardware. Billions of phones stayed exposed for decades because of deployment inertia.

Mifare Classic

The best-selling contactless card in the world (public transit, building access) used CRYPTO1, a proprietary, secret cipher with 48-bit keys. In 2008, researchers reconstructed the algorithm by analyzing the chip under a microscope and found structural weaknesses: the key can be recovered in seconds with a pocket reader. Entire cities' transit and access-control systems had to be migrated.

Case Design secrecy How it was discovered Outcome Cost of the mistake
DVD CSS Algorithm + 40-bit keys Reverse engineering a player (1999) Broken in seconds Irreparable: hardware already sold
GSM A5/1-A5/2 Secret committee-designed algorithms Leaks + reverse engineering (1990s) Practical call decryption Decades of exposed communications
Mifare Classic CRYPTO1, proprietary, 48 bits Analysis of the chip's silicon (2008) Key recovered in seconds Migration of entire cities

The pattern repeats, step by step: (1) the design is kept secret → (2) it gets reverse-engineered, always → (3) once exposed, public cryptanalysis finds in months the weaknesses that the closed design incubated for years → (4) the cost of replacement is brutal because the algorithm is baked into the deployment. Compare that with AES: published design, international competition (NIST, 1997-2000), fifteen finalists tearing each other apart, and twenty-five years of worldwide scrutiny since. That asymmetry in review is the entire difference.

And the everyday version of the mistake isn't designing a brand-new cipher, but things like: "XOR with a repeating password," "AES but hand-implemented by reading the spec," "a homegrown hash that mixes MD5 twice." All of that is inventing your own cryptography. The rule includes implementing known primitives: AES's specification fits on a few pages, but implementing it without side-channel leaks (next section) is specialist work.

Side-channel attacks: when the implementation betrays you

So far we've talked about breaking the math. But there's a family of attacks that ignores the math entirely and observes the system's physical execution: how long it takes, how much power it draws, what it emits electromagnetically, how the CPU cache behaves. These are side-channel attacks. Here you only need the concept and its most accessible example: the timing attack against a non-constant-time comparison.

Think about how Python's == operator compares strings: it walks through the characters and stops at the first difference. That means comparing "AAAA" == "BXXX" takes a tiny bit less time than "AAAA" == "ABXX", because the first comparison fails at character 1 and the second at character 2. The difference is nanoseconds... but it's statistically measurable by repeating the request thousands of times, especially over a local network or between containers.

The resulting attack against an endpoint that compares a secret token with ==:

  1. The attacker tries A000..., B000..., C000... and measures timing. The variant whose first character matches the secret consistently takes a hair longer (the comparison advances one more character before failing).
  2. Once the first character is nailed down, repeat for the second. And so on.
  3. Result: instead of guessing the whole token at once (impossible: 2^128), it's reconstructed character by character — going from exponential cost to linear cost. The 128 bits of entropy from the previous lesson are nullified by the implementation.

This is the deep takeaway of this section: theoretical security can evaporate in the implementation. It's another face of "don't invent your own cryptography": serious libraries implement their operations in constant time (the time doesn't depend on the secret data), an art full of pitfalls (compilers "optimize away" constant time, caches leak accesses...). Other side channels — power consumption, electromagnetic emissions, cache usage — follow the same logic and mostly affect hardware (smart cards, HSMs); for you, the channel to watch day-to-day is timing.

MediNube case: comparing secrets with secrets.compare_digest

Clinics integrate their systems with MediNube through an API that authenticates every request with an API key. In the legacy code you find this:

# ─── VULNERABLE to a timing attack ───
API_KEYS = {"clinica-sol": "tCq81vX2mZk4Nw9rLpAeYhSdF0uGjB3o"}

def authenticate_BAD(clinic: str, received_key: str) -> bool:
    correct_key = API_KEYS.get(clinic, "")
    return received_key == correct_key        # == stops at the first mismatch

== compares character by character and cuts off at the first mismatch: the endpoint's response time depends on how many leading characters the attacker got right. With patience and statistics, Clínica Sol's key can be extracted character by character, exactly as we just saw. The fix is one line:

# ─── CORRECT: constant-time comparison ───
import secrets

def authenticate(clinic: str, received_key: str) -> bool:
    correct_key = API_KEYS.get(clinic, "")
    return secrets.compare_digest(received_key, correct_key)

Breakdown:

  • secrets.compare_digest(a, b) always compares the full strings, no matter how long it takes: the timing never reveals where they differ. (It accepts two ASCII str or two bytes.)
  • The rest of lesson 01-03's reasoning still applies: the key should have been born from secrets.token_urlsafe(32), and the endpoint should also rate-limit attempts and log failures — defense in depth.
  • Use compare_digest whenever you compare a secret value received from the outside: API keys, recovery tokens, webhook signatures (we'll reuse this with HMAC in module 3). For comparisons that don't involve secrets (an if status == "active"), plain == is perfectly fine.

Note: storing API keys in a dictionary in the code, as in the example, is another piece of security debt at MediNube — secrets management is covered in depth in module 6. And, as always: a real healthcare deployment requires review by security and compliance professionals (GDPR).

Crypto-agility: designing so you can change

Cryptographic algorithms age: MD5 and SHA-1 fell, DES became too small, and quantum computing will force massive migrations (module 6). The question isn't whether you'll have to change algorithms during your system's lifetime, but how much it will cost you. Crypto-agility means designing so that change is cheap:

  • Tag data with its algorithm and version. Every encrypted or hashed value should carry metadata about what produced it (v1 = AES-256-GCM, v2 = whatever comes next). That way formats can coexist during a migration and the code knows how to treat each record. Serious formats already do this: you'll see it in password hashes ($argon2id$..., module 3) and JWT headers (module 6).
  • Centralize the cryptography. A single internal module/service (medinube.crypto) that the rest of the application consumes. Changing algorithms = changing one place, not forty.
  • Don't overengineer out of fear, but leave slack where it's free: database fields that accept longer hashes, protocols with a version field from day one.
  • The Mifare/GSM/CSS cases are also a lesson in the absence of crypto-agility: their biggest cost wasn't the cryptanalysis itself, but that the broken algorithm was cast into hardware that couldn't be updated.

Serious libraries versus hand-rolled primitives

If you shouldn't implement primitives by hand, what should you use? Libraries maintained by specialists, audited, and with APIs that are hard to misuse:

Ecosystem Recommended library Notes
Python cryptography (pyca project) The de facto standard; we'll use it from module 2 on. Exposes both high-level recipes (Fernet) and primitives (hazmat, "hazardous materials" — the name says it all)
Python (alternative) PyNaCl (libsodium) A minimalist, opinionated API: few options, all of them good
Cross-language / C libsodium The modern reference for "cryptography that's hard to misuse"
JavaScript/Node WebCrypto (crypto.subtle), libsodium.js Native to the browser and to Node
Java/JVM JCA/JCE with modern providers, Tink (Google) Tink is explicitly designed to be misuse-resistant
Go crypto/* in the standard library Maintained by the Go team itself
.NET System.Security.Cryptography Native to the platform

Criteria for trusting a cryptographic library (apply them to any language):

  • Active maintenance and an identifiable team; a public track record of handling vulnerabilities (CVEs fixed quickly).
  • Published external audits, or massive adoption under real scrutiny.
  • A high-level API that chooses secure parameters for you (the best API is one that doesn't let you choose wrong). Be wary of libraries that make you decide everything.
  • Documented constant-time implementations.

And the opposite anti-pattern: the Stack Overflow snippet with homegrown cryptography, the crypto-utils-fast package with 200 downloads, or "I'll just do it myself with XOR, it's faster." At MediNube, the architectural decision we make today is: Python + cryptography (pyca) for the whole course, with secrets/os.urandom for randomness.

The course's golden rules

The whole of module 1 condenses into these rules. We'll keep reinforcing them (and adding nuance) throughout the rest of the course; think of them as the applied cryptographer's contract:

  1. Don't invent or implement your own cryptography. Use standard algorithms through serious libraries (cryptography, libsodium). This includes not creatively "improving" or combining primitives.
  2. Assume the enemy knows the system (Kerckhoffs/Shannon). All the security must reside in the keys; obscurity, at most, is an extra layer — never the defense.
  3. All security-related randomness comes from the CSPRNG. In Python: secrets or os.urandom. Never random, never timestamps, never homegrown ingredients.
  4. Encoding is not encrypting. Base64, hex, and company are transport. If there's no secret key, there's no confidentiality.
  5. Entropy rules. A key is only worth the real bits of entropy behind it, not its apparent length. Today's serious minimum: 128 bits; our standard: 256.
  6. Compare secrets in constant time (secrets.compare_digest). The implementation can betray the mathematics.
  7. Identify the goal before the tool. Confidentiality, integrity, authenticity, non-repudiation? The answer picks the mechanism, not the other way around.
  8. Design for crypto-agility. Version your formats, centralize the cryptography, be ready to migrate algorithms.
  9. Cryptography doesn't replace the rest of security. Access control, attempt limits, auditing, secrets management, and professional review (even more so with health data and the GDPR) remain essential.

Common Mistakes and Tips

  • Misquoting Kerckhoffs: "you have to publish the algorithm." The principle doesn't require publishing anything; it requires that security not depend on the design's secrecy. You can keep your architecture confidential — but your system must hold up as if it were public.
  • The opposite extreme: dismissing all obscurity. Not exposing software versions or internal routes is reasonable hygiene. The sin is counting it as a security control instead of as extra friction.
  • "I use AES, so I satisfy rule 1." Using a standard algorithm badly (wrong mode, reused IV, a key derived from a weak password) also breaks the rule. That's why modules 2 and 6 spend so much space on the how.
  • Sprinkle-crypto: cryptographic calls scattered all over the codebase, each with its own parameters. When it's time to migrate (rule 8), it'll be archaeology. Centralize from day one.
  • Swapping == for compare_digest everywhere "just in case." It only makes sense for secret values compared against external input; using it for ordinary logic just obscures the code. Understand the why behind each rule, not cargo-cult it.
  • Tip: turn the golden rules into your team's code-review checklist. Most real cryptographic vulnerabilities (you'll see this in lesson 06-04) are direct violations of one of these nine lines.

Exercises

Exercise 1. A vendor offers MediNube a "military-grade record encryption" module with this sales pitch: "Our algorithm is proprietary and confidential, which adds a layer of security: since no one knows it, no hacker can attack it. It has passed our internal quality tests for 5 years without being broken." Write a technical response (4-6 sentences) pointing out the flaws in the argument, citing Kerckhoffs's principle and at least one historical case.

Exercise 2. This MediNube endpoint verifies the token an external webhook sends in a header. Identify the two golden-rule violations and fix them:

import random, string

WEBHOOK_TOKEN = "".join(random.choice(string.ascii_letters) for _ in range(32))

def verify_webhook(received_token: str) -> bool:
    return received_token == WEBHOOK_TOKEN

Exercise 3. For each decision, say whether it's an acceptable use of obscurity as an extra layer or a violation of Kerckhoffs's principle, and why: (a) MediNube doesn't publicly document the internal routes of its admin API; (b) medical records are "protected" with an XOR whose mask lives in the source code, relying on no one decompiling the app; (c) the team decides not to publish on the company blog which encryption algorithm they use, even though it's AES-256-GCM with well-managed keys; (d) the session token is named X-Custom-Data instead of Authorization "to throw people off," and is otherwise a well-verified, random 256-bit token.

Solutions

Solution 1. Model answer: "This argument inverts Kerckhoffs's principle: a system must be secure assuming the attacker knows the algorithm, because a design secret always eventually gets lost (reverse engineering, leaks) and, unlike a key, can't be rotated. Not knowing it doesn't stop it from being attacked: DVD CSS, GSM A5, and Mifare Classic were all proprietary, secret algorithms, and all three were reverse-engineered and broken as soon as they received public analysis. 'Five years unbroken' in internal testing only means no one qualified has tried yet — cryptographic confidence comes from massive public scrutiny, like what AES has received since 1998. We're requesting that the product use standard algorithms (e.g. AES-256-GCM) through audited libraries; a confidential design is, in itself, grounds for rejection."

Solution 2. Violations: (1) rule 3 — the token is generated with random, a predictable PRNG (on top of that, letters-only: log2(52) ≈ 5.7 bits/character, though the serious problem is the Mersenne Twister's predictability); (2) rule 6 — the == comparison isn't constant-time and lets an attacker extract the token character by character with a timing attack. Fix:

import secrets

WEBHOOK_TOKEN = secrets.token_urlsafe(32)   # 256 bits from the CSPRNG

def verify_webhook(received_token: str) -> bool:
    return secrets.compare_digest(received_token, WEBHOOK_TOKEN)

(Bonus: in production the token wouldn't live in a code variable but in a secrets manager — module 6 — and the robust mechanism for webhooks is signing the body with HMAC, which we'll build in module 3.)

Solution 3. (a) Acceptable: extra friction at no cost; the real security comes from the endpoint's authentication and authorization, not the secrecy of the route. (b) Clear violation: the only barrier is that no one looks at the code — there's no secret key managed as such, and XOR with a fixed mask isn't even a serious cipher; it breaks rules 1, 2, and 4. (c) Acceptable: not publishing details is legitimate; the system holds up even if they're known (AES used correctly doesn't depend on the secrecy of "what algorithm"). This is exactly the right stance toward Kerckhoffs: it could be made public with no risk. (d) Acceptable but useless and costly: the security comes from the well-verified 256-bit token, not the header's name; the "misdirection" barely slows anyone down and complicates integration and maintenance. It doesn't violate Kerckhoffs (security doesn't depend on the trick), but it's obscurity of almost zero value.

Conclusion

This lesson closes the fundamentals module, and you now have the complete mindset of an applied cryptographer. You know what cryptography pursues (confidentiality, integrity, authenticity, and non-repudiation — lesson 01-01), you can rigorously tell apart encoding, obfuscating, and encrypting (01-02), you know where the unpredictability that underpins everything comes from and how to generate it correctly in Python (01-03), and now you have the principle that governs design: security lives in the key, never in the algorithm's secrecy, with its corollaries — don't invent your own cryptography, distrust obscurity as a defense, watch for side channels with constant-time comparisons, design for crypto-agility, and rely on serious libraries. The nine golden rules condense the entire module; come back to them whenever you're unsure, because the rest of the course will do nothing but add depth to them.

At MediNube, this module's balance sheet is a good starting point: we've banished Base64-as-protection, recovery tokens are now born from secrets.token_urlsafe(32), and API keys are compared in constant time. But the central need is still unsolved: patient records like Ana Pérez's are still unencrypted on disk. In Module 2: Symmetric Cryptography we start building the real solution — the cryptography library takes the stage, and with it, the algorithms that protect most of the world's data: AES and ChaCha20, their modes of operation, authenticated encryption (AEAD), and key derivation. See you in lesson 02-01.

© Copyright 2026. All rights reserved