Aufwärmen · Aufgabe 1 von 7
Aufwärmen aus der vorigen Lektion: json kann nur einfache Daten bauen. Was gibt das aus?
import json
print(json.loads('[1, 2]') == [1, 2], type(json.loads('true')).__name__)// A5.5 · ca. 45 Min. · Vertiefung
Nach dieser Lektion erzeugen Sie Tokens mit secrets, hashen und signieren Daten mit hashlib und hmac, speichern Passwörter mit einem langsamen Hash mit Salt, prüfen eine pyproject.toml vor python3 -m build und schließen das Modul mit einem sicheren t-String-Berichtsgenerator ab.
Lektion 5 von 5 in A5 Modernes Python und sicherer Code
Danach können Sie
Aufwärmen · Aufgabe 1 von 7
import json
print(json.loads('[1, 2]') == [1, 2], type(json.loads('true')).__name__)Vorhersagen · Aufgabe 2 von 7
import hashlib
short = hashlib.sha256(b"a").hexdigest()
long = hashlib.sha256(b"a" * 100_000).hexdigest()
print(len(short), len(long))Üben · Aufgabe 3 von 7
import secrets
import string
token = secrets.____(16)
allowed = set(string.ascii_letters + string.digits + "-_")
print(len(token), set(token) <= allowed)Üben · Aufgabe 4 von 7
import hmac
key = b"server-key"
tag = hmac.new(key, b"user=ada", "sha256").hexdigest()
again = hmac.new(key, b"user=ada", "sha256").hexdigest()
forged = hmac.new(key, b"user=admin", "sha256").hexdigest()
print(hmac.compare_digest(tag, again), hmac.compare_digest(tag, forged))Üben · Aufgabe 5 von 7
Denksport · Aufgabe 6 von 7
import hmac
for a, b in [("abc", "abc"), (b"abc", b"abc"), ("abc", b"abc")]:
try:
print(hmac.compare_digest(a, b), end=" ")
except TypeError:
print("TypeError", end=" ")Anwenden · Aufgabe 7 von 7
Prüfen Sie Ihr Ergebnis anhand dieser Liste
Lesen Sie das ausgearbeitete Beispiel und lösen Sie dann die Übungen. Ihr Code läuft in Ihrem Browser oder auf Ihrem Computer und wird nie hochgeladen.
Ausgearbeitetes Beispiel
Das Programm erzeugt ein Token mit secrets und gibt nur seine Länge aus, denn der Wert ändert sich bei jedem Lauf. Es hasht einen kleinen CSV-Bericht mit SHA-256, signiert ihn dann mit hmac und prüft zwei Kopien: eine unverändert und eine mit einer zusätzlichen Zeile. Nur die unveränderte Kopie passt zum Tag.
main.py
import hashlib
import hmac
import secrets
# 1. A token for a reset link: secrets, never random.
token = secrets.token_urlsafe(32)
print("token length:", len(token))
# 2. A checksum: the same bytes always give the same SHA-256 digest.
data = b"report,2026\nada,3\n"
print("sha256:", hashlib.sha256(data).hexdigest()[:16])
# 3. A signature: only someone with the key can make a matching tag.
key = b"keep-this-secret"
tag = hmac.new(key, data, "sha256").hexdigest()
received = hmac.new(key, data, "sha256").hexdigest()
tampered = hmac.new(key, data + b"bob,9\n", "sha256").hexdigest()
print("genuine:", hmac.compare_digest(tag, received))
print("tampered:", hmac.compare_digest(tag, tampered))
Ausführen mit
python main.pyAusgabe
token length: 43
sha256: 24ba945e8f82a042
genuine: True
tampered: FalseTab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.
Beim ersten Ausführen lädt Ihr Browser Python herunter (bis zu 6.5 MB) und speichert es im Cache. Ihr Code bleibt auf Ihrem Gerät.
Übung 1 von 4
Reparieren Sie drei Funktionen. make_token nutzt random und nur 8 Bytes; liefern Sie secrets.token_urlsafe(32). sign hasht key + text, und das ist kein HMAC; liefern Sie das Hex-HMAC-SHA256-Tag von text.encode() unter key. verify vergleicht mit ==; nutzen Sie hmac.compare_digest.
Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.
Beim ersten Ausführen lädt Ihr Browser Python herunter (bis zu 6.5 MB) und speichert es im Cache. Ihr Code bleibt auf Ihrem Gerät.
Importieren Sie secrets und hmac; random und hashlib brauchen Sie nicht mehr.
hmac.new(key, text.encode(), "sha256").hexdigest() ist das Tag.
verify liefert hmac.compare_digest(sign(key, text), tag).
Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.
import hmac
import secrets
def make_token() -> str:
"""Return a URL-safe token with 32 bytes of randomness."""
return secrets.token_urlsafe(32)
def sign(key: bytes, text: str) -> str:
"""Return the hex HMAC-SHA256 tag of text under key."""
return hmac.new(key, text.encode(), "sha256").hexdigest()
def verify(key: bytes, text: str, tag: str) -> bool:
"""Return True if tag is the right tag for text under key."""
return hmac.compare_digest(sign(key, text), tag)
Installieren Sie Python 3.14 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.
main.py
import hashlib
import random
def make_token() -> str:
"""Return a URL-safe token with 32 bytes of randomness."""
return "%016x" % random.getrandbits(64)
def sign(key: bytes, text: str) -> str:
"""Return the hex HMAC-SHA256 tag of text under key."""
return hashlib.sha256(key + text.encode()).hexdigest()
def verify(key: bytes, text: str, tag: str) -> bool:
"""Return True if tag is the right tag for text under key."""
return sign(key, text) == tag
test_main.py
import string
from main import make_token, sign, verify
URLSAFE = set(string.ascii_letters + string.digits + "-_")
def test_token_shape():
"""Ein Token hat 43 URL-sichere Zeichen (32 Zufallsbytes)"""
token = make_token()
assert len(token) == 43 and set(token) <= URLSAFE, f"make_token lieferte {token!r}; nutzen Sie secrets.token_urlsafe(32)"
def test_tokens_differ():
"""Zwei Tokens sind verschieden"""
assert make_token() != make_token(), "make_token lieferte zweimal dasselbe Token"
def test_sign_is_hmac():
"""sign liefert das HMAC-SHA256-Tag"""
got = sign(b"key", "msg")
want = "2d93cbc1be167bcb1637a4a23cbff01a7878f0c50ee833954ea5221bb1b8c628"
assert got == want, f"sign(b'key', 'msg') lieferte {got!r}; nutzen Sie hmac.new(key, text.encode(), 'sha256')"
def test_verify():
"""verify nimmt das richtige Tag an und lehnt geänderten Text oder einen anderen Schlüssel ab"""
tag = sign(b"key", "user=ada")
assert verify(b"key", "user=ada", tag), "das richtige Tag wurde abgelehnt"
assert not verify(b"key", "user=admin", tag), "ein Tag für anderen Text wurde angenommen"
assert not verify(b"other", "user=ada", tag), "ein Tag mit einem anderen Schlüssel wurde angenommen"
Unter macOS und Linux tippen Sie python3, wo in diesen Befehlen python steht, wie in der ersten Lektion.
Programm ausführen:
python main.pyPrüfungen ausführen (learnrun.py muss im selben Ordner liegen):
python learnrun.py testlearnrun.py herunterladenÜbung 2 von 4
hash_password speichert ein einfaches SHA-256 des Passworts: ohne Salt und schnell zu erraten. Speichern Sie stattdessen "salt$hash": 16 Zufallsbytes aus secrets als Salt und hashlib.pbkdf2_hmac("sha256", password.encode(), salt, ITERATIONS) als Hash, beides hexkodiert. check_password rechnet mit dem gespeicherten Salt neu und vergleicht mit hmac.compare_digest. Führen Sie die Tests auf Ihrem eigenen Rechner aus: Der Browser hat kein pbkdf2_hmac.
Diese Übung braucht Python auf Ihrem Computer (die Browser-Version kann sie nicht ausführen). Dateien und Befehle stehen unten.
salt = secrets.token_bytes(16), dann return salt.hex() + "$" + digest.hex().
In check_password trennen Sie an "$" und machen das Salt mit bytes.fromhex wieder zu bytes.
Vergleichen Sie digest.hex() mit dem gespeicherten Hex-Wert per hmac.compare_digest.
Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.
import hashlib
import hmac
import secrets
ITERATIONS = 100_000
def hash_password(password: str) -> str:
"""Return "salt$hash" in hex, with a new random salt each time."""
salt = secrets.token_bytes(16)
digest = hashlib.pbkdf2_hmac("sha256", password.encode(), salt, ITERATIONS)
return salt.hex() + "$" + digest.hex()
def check_password(password: str, stored: str) -> bool:
"""Return True if password matches what hash_password stored."""
salt_hex, digest_hex = stored.split("$")
digest = hashlib.pbkdf2_hmac("sha256", password.encode(), bytes.fromhex(salt_hex), ITERATIONS)
return hmac.compare_digest(digest.hex(), digest_hex)
Installieren Sie Python 3.14 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.
main.py
import hashlib
ITERATIONS = 100_000
def hash_password(password: str) -> str:
"""Return "salt$hash" in hex, with a new random salt each time."""
# sha256 of the bare password: no salt, and fast to brute-force.
return "$" + hashlib.sha256(password.encode()).hexdigest()
def check_password(password: str, stored: str) -> bool:
"""Return True if password matches what hash_password stored."""
return hash_password(password) == stored
test_main.py
import hashlib
from main import check_password, hash_password
def test_round_trip():
"""Das richtige Passwort passt, ein falsches nicht"""
stored = hash_password("hunter2")
assert check_password("hunter2", stored), "das richtige Passwort wurde abgelehnt"
assert not check_password("hunter3", stored), "ein falsches Passwort wurde angenommen"
def test_salted():
"""Dasselbe Passwort zweimal gehasht ergibt verschiedene Ergebnisse"""
assert hash_password("hunter2") != hash_password("hunter2"), "kein zufälliges Salt: gleiche Passwörter ergeben gleiche Hashes"
def test_uses_pbkdf2():
"""Der gespeicherte Hash ist PBKDF2-HMAC-SHA256 mit einem 16-Byte-Salt"""
salt_hex, _, digest_hex = hash_password("pw").partition("$")
assert len(salt_hex) == 32, f"der Salt-Teil ist {salt_hex!r}; nutzen Sie 16 Zufallsbytes, hexkodiert"
want = hashlib.pbkdf2_hmac("sha256", b"pw", bytes.fromhex(salt_hex), 100_000).hex()
assert digest_hex == want, "der Hash-Teil ist nicht pbkdf2_hmac('sha256', password, salt, ITERATIONS)"
Unter macOS und Linux tippen Sie python3, wo in diesen Befehlen python steht, wie in der ersten Lektion.
Programm ausführen:
python main.pyPrüfungen ausführen (learnrun.py muss im selben Ordner liegen):
python learnrun.py testlearnrun.py herunterladenÜbung 3 von 4
render(template) setzt Werte roh ein, und report baut seine Zeilen mit f-Strings, ein Name wie "<b>bob</b>" wird also HTML. Lassen Sie render die Umwandlung und Formatangabe jeder Interpolation anwenden und das Ergebnis dann mit html.escape maskieren. Bauen Sie danach die Zeilen von report als t-Strings und geben Sie sie durch render.
Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.
Beim ersten Ausführen lädt Ihr Browser Python herunter (bis zu 6.5 MB) und speichert es im Cache. Ihr Code bleibt auf Ihrem Gerät.
from string.templatelib import convert: value = convert(item.value, item.conversion).
Dann escape(format(value, item.format_spec)), mit escape aus html.
Schreiben Sie in report render(t"<li>{name}: {score:>3}</li>"): Maskiert werden nur die Werte, nicht Ihre eigenen Tags.
Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.
from html import escape
from string.templatelib import Interpolation, Template, convert
def render(template: Template) -> str:
"""Join the template, HTML-escaping every interpolated value."""
parts: list[str] = []
for item in template:
if isinstance(item, Interpolation):
value = convert(item.value, item.conversion)
parts.append(escape(format(value, item.format_spec)))
else:
parts.append(item)
return "".join(parts)
def report(title: str, rows: list[tuple[str, int]]) -> str:
"""Return an HTML report: a heading and one list item per row."""
lines = [render(t"<h1>{title}</h1>")]
for name, score in rows:
lines.append(render(t"<li>{name}: {score:>3}</li>"))
return "\n".join(lines)
Installieren Sie Python 3.14 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.
main.py
from string.templatelib import Interpolation, Template
def render(template: Template) -> str:
"""Join the template, HTML-escaping every interpolated value."""
return "".join(
str(item.value) if isinstance(item, Interpolation) else item for item in template
)
def report(title: str, rows: list[tuple[str, int]]) -> str:
"""Return an HTML report: a heading and one list item per row."""
lines = [f"<h1>{title}</h1>"]
for name, score in rows:
lines.append(f"<li>{name}: {score:>3}</li>")
return "\n".join(lines)
test_main.py
from main import render, report
def test_render_escapes():
"""Eingesetzte Werte werden HTML-maskiert"""
x = "<script>"
got = render(t"<p>{x}</p>")
assert got == "<p><script></p>", f"render lieferte {got!r}"
def test_render_format_spec():
"""Die Formatangabe wird angewandt"""
n = 7
got = render(t"[{n:>4}]")
assert got == "[ 7]", f"render lieferte {got!r}; wenden Sie format(value, item.format_spec) an"
def test_render_conversion():
"""!r wird angewandt, danach maskiert"""
v = "a<b"
got = render(t"{v!r}")
assert got == "'a<b'", f"render lieferte {got!r}; wenden Sie convert(value, item.conversion) an"
def test_report():
"""Der Bericht maskiert den Titel und jeden Namen"""
got = report("Scores <2026>", [("ada", 3), ("<b>bob</b>", 12)])
want = "<h1>Scores <2026></h1>\n<li>ada: 3</li>\n<li><b>bob</b>: 12</li>"
assert got == want, f"report lieferte {got!r}"
Unter macOS und Linux tippen Sie python3, wo in diesen Befehlen python steht, wie in der ersten Lektion.
Programm ausführen:
python main.pyPrüfungen ausführen (learnrun.py muss im selben Ordner liegen):
python learnrun.py testlearnrun.py herunterladenÜbung 4 von 4
Prüfen Sie vor python3 -m build die Projektdatei. package_info(text) soll "name version" liefern und ValueError auslösen, außer es gibt eine Tabelle [build-system] mit einer Liste requires und einem String build-backend sowie eine Tabelle [project], deren name und version Strings sind. Der Starter stürzt stattdessen mit KeyError oder TypeError ab.
Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.
Beim ersten Ausführen lädt Ihr Browser Python herunter (bis zu 6.5 MB) und speichert es im Cache. Ihr Code bleibt auf Ihrem Gerät.
Nutzen Sie data.get("project") und data.get("build-system") und prüfen Sie, ob beides ein dict ist.
Prüfen Sie isinstance(name, str) und isinstance(version, str); version = 1 ist in TOML ein int.
Prüfen Sie isinstance(system.get("requires"), list) und isinstance(system.get("build-backend"), str).
Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.
import tomllib
def package_info(text: str) -> str:
"""Check a pyproject.toml before building; return "name version"."""
data = tomllib.loads(text)
project = data.get("project")
system = data.get("build-system")
if not isinstance(project, dict) or not isinstance(system, dict):
raise ValueError("need a [project] and a [build-system] table")
name = project.get("name")
version = project.get("version")
if not isinstance(name, str) or not isinstance(version, str):
raise ValueError("[project] needs name and version as strings")
if not isinstance(system.get("requires"), list) or not isinstance(system.get("build-backend"), str):
raise ValueError("[build-system] needs a requires list and a build-backend string")
return f"{name} {version}"
Installieren Sie Python 3.14 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.
main.py
import tomllib
def package_info(text: str) -> str:
"""Check a pyproject.toml before building; return "name version"."""
data = tomllib.loads(text)
return data["project"]["name"] + " " + data["project"]["version"]
test_main.py
from main import package_info
GOOD = """
[build-system]
requires = ["hatchling >= 1.26"]
build-backend = "hatchling.build"
[project]
name = "example_package"
version = "0.0.1"
"""
NO_BUILD_SYSTEM = """
[project]
name = "example_package"
version = "0.0.1"
"""
NUMBER_VERSION = """
[build-system]
requires = ["hatchling >= 1.26"]
build-backend = "hatchling.build"
[project]
name = "example_package"
version = 1
"""
NO_PROJECT = """
[build-system]
requires = ["hatchling >= 1.26"]
build-backend = "hatchling.build"
"""
def rejects(text):
try:
package_info(text)
except ValueError:
return True
return False
def test_good():
"""Eine vollständige pyproject.toml ergibt Name und Version"""
got = package_info(GOOD)
assert got == "example_package 0.0.1", f"package_info lieferte {got!r}"
def test_needs_build_system():
"""Eine fehlende Tabelle [build-system] wird abgelehnt"""
assert rejects(NO_BUILD_SYSTEM), "eine Datei ohne [build-system] wurde angenommen oder löste den falschen Fehler aus"
def test_version_is_string():
"""Eine Version als Zahl wird abgelehnt"""
assert rejects(NUMBER_VERSION), "version = 1 wurde angenommen oder löste den falschen Fehler aus; sie muss ein String sein"
def test_needs_project():
"""Eine fehlende Tabelle [project] wird mit ValueError abgelehnt"""
assert rejects(NO_PROJECT), "eine Datei ohne [project] wurde angenommen oder löste den falschen Fehler aus"
Unter macOS und Linux tippen Sie python3, wo in diesen Befehlen python steht, wie in der ersten Lektion.
Programm ausführen:
python main.pyPrüfungen ausführen (learnrun.py muss im selben Ordner liegen):
python learnrun.py testlearnrun.py herunterladenimport hashlib
digest = hashlib.sha256("report,2026").hexdigest()
Was Python ausgibt
TypeError: Strings must be encoded before hashingWarum, und die Lösung
Hashfunktionen arbeiten mit bytes, und derselbe Text lässt sich unterschiedlich kodieren. Kodieren Sie ausdrücklich, meist "report,2026".encode() (UTF-8), damit jedes Programm, das den Digest prüft, dieselben Bytes hasht.
import hmac
tag = hmac.new(b"server-key", b"user=ada").hexdigest()
Was Python ausgibt
TypeError: Missing required argument 'digestmod'.Warum, und die Lösung
digestmod ist Pflicht: Übergeben Sie den Namen des Algorithmus als drittes Argument, hmac.new(key, msg, "sha256"), oder als digestmod=hashlib.sha256. Es gibt keinen Standardwert, die Wahl ist also immer im Code sichtbar.
import hashlib
import secrets
salt = secrets.token_bytes(16)
key = hashlib.pbkdf2_hmac("sha256", "hunter2", salt, 100_000)
Was Python ausgibt
TypeError: a bytes-like object is required, not 'str'Warum, und die Lösung
pbkdf2_hmac liest Passwort und Salt als bytes. Kodieren Sie das Passwort zuerst: "hunter2".encode(). Das Salt aus secrets.token_bytes ist schon bytes.
Python im Browser: Pyodide 314.0.7, MPL-2.0. Lizenz und Quellcode
5 Fragen, ohne Hinweise. Ab 80 % ist die Lektion abgeschlossen.
Erledigen Sie zuerst alle Aufgaben oben, um das Abschlussquiz freizuschalten.