Skip to content
AI-grafen
EUniversityComputer science· about 60 min· evolving, reviewed regularly· verified 2026-09-20· EN

Cryptography — hashes, signatures, keys

Be able to explain hash functions, signatures and why API keys should never live in the code.

Prerequisites

Intuition

Three building blocks that cover most things:

A hash (SHA-256): a one-way function from arbitrary data to 256 bits. The same input always gives the same hash; the smallest change gives an entirely different one. It cannot be reversed. Used for fingerprints (has the file changed?) and for passwords — then with a slow hash (bcrypt, argon2) plus a salt, never plain SHA-256.

A signature (HMAC or asymmetric): proves that the data comes from someone with the key and has not been changed. AI-grafen's certificates are signed with HMAC — which is why they can be verified without being forgeable.

Encryption: symmetric (the same key, fast, AES) or asymmetric (a public/private key pair, slow, used to exchange symmetric keys — which is what TLS does).

Formal

Why API keys should never live in the code:

  1. Git does not forget. A key that has been committed remains in the history even after it has been removed from the file. The only remedy is to revoke the key.
  2. The repo spreads — forks, backups, CI logs, a laptop that goes missing.
  3. Automatic scanners find keys in public repos within minutes. There are documented cases where a leaked cloud key ran up tens of thousands of kronor in costs overnight.

Where they should live instead, in ascending order:

PlaceSuitable for
An environment variablelocal development
A .env outside git (in .gitignore)local development
A secrets manager (Vault, a cloud KMS)production
Short-lived tokens with automatic rotationthe best — the leak has an expiry date

AI-grafen fetches the LLM key out of Vault at bootstrap and writes it to an environment file that is never in the repo; only the first twelve characters are ever logged.

If a key has leaked: revoke first, investigate afterwards. The order is not negotiable.

Code

import hashlib, hmac, os, secrets

# A fingerprint — the same input, the same hash
print(hashlib.sha256(b"hello").hexdigest()[:16])        # 2cf2...

# Passwords: NEVER raw SHA-256. A slow hash plus a salt.
salt = secrets.token_bytes(16)
hashed = hashlib.scrypt(b"password", salt=salt, n=2**14, r=8, p=1)

# A signature (the same principle as the platform's certificates)
KEY = os.environ["SIGNING_KEY"].encode()                # from the environment, not the code
def sign(data: bytes) -> str:
    return hmac.new(KEY, data, hashlib.sha256).hexdigest()
def verify(data: bytes, sig: str) -> bool:
    return hmac.compare_digest(sign(data), sig)         # a constant-time comparison

# The key from the environment, with a clear error if it is missing
API_KEY = os.environ.get("LLM_API_KEY")
if not API_KEY:
    raise RuntimeError("LLM_API_KEY is missing — set it in the environment, not in the code")
print("key:", API_KEY[:12] + "…")                       # never log the whole thing

hmac.compare_digest instead of == is not pedantry: an ordinary comparison stops at the first wrong character, which leaks information about the signature through the response time.

Mastery means

  • Explains hash functions, signatures and keys
  • Knows why API keys should never live in the code
  • Handles secrets correctly

Sign in to do the exercises and build your mastery up.

Sources

All the sources and licences