Home › Dateien & Sicherheit

🔐 Dateien, Speichern & Verschlüsselung

INTERMEDIATE ⏱ ~50 Min Python 3.12+

Dateiformate im Überblick

Spielstände, Konfigurationen, Highscores – sie alle müssen gespeichert werden. Python bietet viele Optionen. Die richtige Wahl hängt vom Anwendungsfall ab.

Format-Vergleich Simulator – Klicke ein Format
Klicke ein Format um Details zu sehen...
FormatTypLesbarGeschwindigkeitAnwendung
JSONText✅ JaMittelKonfiguration, Spielstände, APIs
CSVText✅ JaSchnellHighscores, Tabellendaten
YAMLText✅ JaLangsamKomplexe Konfiguration
TOMLText✅ JaMittelProjektdateien, Settings
PickleBinär❌ NeinSehr schnellPython-Objekte, ML-Modelle
SQLiteDatenbank⚠️ ToolSchnell + SQLLeaderboards, komplexe Daten

JSON – JavaScript Object Notation

JSON ist das universellste Format. Lesbar, weit verbreitet, und direkt von Python's json-Modul unterstützt.

PYTHON – JSON lesen & schreiben
import json
from pathlib import Path

# ─── SCHREIBEN ───
save_data = {
    "player_name": "Leon",
    "score": 9001,
    "level": 7,
    "inventory": ["sword", "potion", "key"],
    "position": {"x": 340, "y": 220},
    "settings": {"volume": 0.7, "fullscreen": False}
}

# In Datei schreiben (indent=2 für lesbare Formatierung)
with open("savegame.json", "w", encoding="utf-8") as f:
    json.dump(save_data, f, indent=2, ensure_ascii=False)

# Als String (z.B. für Netzwerk)
json_string = json.dumps(save_data, indent=2)

# ─── LESEN ───
with open("savegame.json", "r", encoding="utf-8") as f:
    loaded = json.load(f)

print(loaded["player_name"])  # Leon
print(loaded["inventory"][0])  # sword

# ─── SICHERES LADEN (mit Fehlerbehandlung) ───
def load_save(path: str) -> dict:
    p = Path(path)
    if not p.exists():
        return {}  # Standard-Wert wenn Datei nicht existiert
    try:
        return json.loads(p.read_text(encoding="utf-8"))
    except (json.JSONDecodeError, OSError) as e:
        print(f"Fehler beim Laden: {e}")
        return {}
JSON Encoder/Decoder Simulator
Python Dict eingeben:
Formatiertes JSON (indent=2):

CSV – Comma Separated Values

CSV ist ideal für tabellarische Daten wie Highscores, Statistiken oder Level-Definitionen.

PYTHON – CSV lesen & schreiben
import csv
from pathlib import Path

# ─── SCHREIBEN ───
highscores = [
    {"name": "Leon", "score": 9001, "date": "2026-05-01"},
    {"name": "Anna", "score": 7500, "date": "2026-04-28"},
    {"name": "Max", "score": 6200, "date": "2026-04-25"},
]

with open("highscores.csv", "w", newline="", encoding="utf-8") as f:
    writer = csv.DictWriter(f, fieldnames=["name", "score", "date"])
    writer.writeheader()
    writer.writerows(highscores)

# ─── LESEN ───
with open("highscores.csv", "r", encoding="utf-8") as f:
    reader = csv.DictReader(f)
    scores = list(reader)

# Nach Score sortieren
scores.sort(key=lambda x: int(x["score"]), reverse=True)
for i, row in enumerate(scores[:3], 1):
    print(f"#{i}: {row['name']} – {row['score']}")

YAML – Yet Another Markup Language

YAML ist besonders gut lesbar für Menschen. Ideal für Spiel-Konfigurationen, Level-Definitionen oder komplexe Hierarchien.

TERMINAL + PYTHON – PyYAML
# Installieren (YAML ist nicht in der Stdlib)
pip install pyyaml

# config.yaml Datei:
"""
game:
  title: "SampleCraft Adventure"
  version: "1.0.0"
  resolution:
    width: 1920
    height: 1080
  
player:
  start_lives: 3
  start_health: 100
  speed: 5.0
  
audio:
  master_volume: 0.8
  music_volume: 0.5
  sfx_volume: 1.0
  
levels:
  - id: 1
    name: "Greenfield"
    map: "maps/level1.tmx"
  - id: 2
    name: "Dark Forest"
    map: "maps/level2.tmx"
"""

import yaml

# ─── LESEN ───
with open("config.yaml", "r", encoding="utf-8") as f:
    config = yaml.safe_load(f)  # safe_load = sicher (kein Code-Exec)

print(config["game"]["title"])      # SampleCraft Adventure
print(config["player"]["speed"])    # 5.0
print(config["levels"][0]["name"])  # Greenfield

# ─── SCHREIBEN ───
with open("config.yaml", "w", encoding="utf-8") as f:
    yaml.dump(config, f, allow_unicode=True, default_flow_style=False)
⚠️ Sicherheit: Verwende immer yaml.safe_load() statt yaml.load()! Die unsichere Variante kann beim Parsen beliebigen Python-Code ausführen – ein kritisches Sicherheitsrisiko.

TOML – Tom's Obvious Minimal Language

TOML ist seit Python 3.11 in der Stdlib (tomllib). Klar strukturiert, ideal für Konfigurationsdateien wie pyproject.toml.

PYTHON – TOML (Python 3.11+)
# Python 3.11+ hat tomllib eingebaut (nur lesen)!
# Zum Schreiben: pip install tomli-w
import tomllib   # stdlib ab 3.11
import tomli_w   # pip install tomli-w

# config.toml:
"""
[game]
title = "SampleCraft"
version = "1.0"

[player]
speed = 5.0
jump_force = 18.0
start_lives = 3

[audio]
music_volume = 0.5
sfx_volume = 1.0

[display]
width = 1920
height = 1080
fullscreen = false
"""

# Lesen (binär öffnen!)
with open("config.toml", "rb") as f:
    config = tomllib.load(f)

print(config["game"]["title"])   # SampleCraft
print(config["player"]["speed"]) # 5.0

# Schreiben mit tomli-w
data = {"game": {"title": "SampleCraft"}, "player": {"speed": 5.0}}
with open("config.toml", "wb") as f:
    tomli_w.dump(data, f)

Pickle – Python-native Serialisierung

Pickle serialisiert beliebige Python-Objekte in Binärformat. Sehr schnell, aber nicht sicher für externe Daten.

PYTHON – Pickle
import pickle
from dataclasses import dataclass, field
from typing import List

@dataclass
class GameState:
    player_name: str = "Player"
    score: int = 0
    level: int = 1
    inventory: List[str] = field(default_factory=list)
    position: tuple = (0, 0)

# State erstellen
state = GameState(
    player_name="Leon",
    score=9001,
    inventory=["sword", "potion"],
    position=(340, 220)
)

# ─── SPEICHERN ───
with open("save.pkl", "wb") as f:
    pickle.dump(state, f, protocol=pickle.HIGHEST_PROTOCOL)

# ─── LADEN ───
with open("save.pkl", "rb") as f:
    loaded_state = pickle.load(f)

print(loaded_state.player_name)   # Leon
print(loaded_state.inventory)     # ['sword', 'potion']
🚨 SICHERHEITSWARNUNG: Lade NIEMALS Pickle-Dateien aus unbekannten Quellen! Pickle kann beim Deserialisieren beliebigen Code ausführen. Nutze Pickle nur für eigene, lokale Dateien.

SQLite – Eingebettete Datenbank

SQLite ist in Python eingebaut (sqlite3 Modul). Ideal für Highscores, Spieler-Profile und komplexe strukturierte Daten.

PYTHON – SQLite Highscore
import sqlite3
from contextlib import contextmanager
from datetime import datetime

# ─── DATENBANK INITIALISIEREN ───
def init_db(path: str = "game.db"):
    conn = sqlite3.connect(path)
    conn.execute("""
        CREATE TABLE IF NOT EXISTS highscores (
            id       INTEGER PRIMARY KEY AUTOINCREMENT,
            name     TEXT    NOT NULL,
            score    INTEGER NOT NULL,
            level    INTEGER DEFAULT 1,
            date     TEXT    DEFAULT CURRENT_TIMESTAMP
        )
    """)
    conn.execute("""
        CREATE TABLE IF NOT EXISTS players (
            id         INTEGER PRIMARY KEY,
            name       TEXT UNIQUE NOT NULL,
            total_time REAL    DEFAULT 0,
            games      INTEGER DEFAULT 0
        )
    """)
    conn.commit()
    return conn

# ─── EINTRAG HINZUFÜGEN ───
def add_score(conn, name: str, score: int, level: int):
    conn.execute(
        "INSERT INTO highscores (name, score, level) VALUES (?, ?, ?)",
        (name, score, level)
    )
    conn.commit()

# ─── TOP 10 ABRUFEN ───
def get_top10(conn) -> list:
    cursor = conn.execute(
        "SELECT name, score, level, date FROM highscores ORDER BY score DESC LIMIT 10"
    )
    return cursor.fetchall()

# ─── VERWENDUNG ───
conn = init_db()
add_score(conn, "Leon", 9001, 7)
add_score(conn, "Anna", 7500, 5)

for i, (name, score, level, date) in enumerate(get_top10(conn), 1):
    print(f"#{i:2d} {name:15s} {score:8d} pts  Level {level}")

conn.close()
Highscore-Tabelle Simulator

Symmetrische Verschlüsselung (AES / Fernet)

Symmetrische Verschlüsselung: Derselbe Schlüssel zum Ver- und Entschlüsseln. cryptography-Bibliothek bietet Fernet (AES-128-CBC + HMAC).

TERMINAL + PYTHON – Fernet (AES)
# Installieren
pip install cryptography

from cryptography.fernet import Fernet
import json, base64
from pathlib import Path

# ─── SCHLÜSSEL GENERIEREN & SPEICHERN ───
key = Fernet.generate_key()   # 32-Byte URL-safe base64
print(key)  # b'xxx...' – geheim halten!

# Schlüssel persistent speichern
Path("secret.key").write_bytes(key)

# Schlüssel laden
key = Path("secret.key").read_bytes()
f = Fernet(key)

# ─── DATEN VERSCHLÜSSELN ───
save_data = {"name": "Leon", "score": 9001, "cheat": False}
plain = json.dumps(save_data).encode()

encrypted = f.encrypt(plain)
print(encrypted)  # b'gAAAAAB...' – unlesbarer Ciphertext

# In Datei speichern
Path("save_encrypted.bin").write_bytes(encrypted)

# ─── ENTSCHLÜSSELN ───
encrypted = Path("save_encrypted.bin").read_bytes()
decrypted_bytes = f.decrypt(encrypted)
loaded = json.loads(decrypted_bytes.decode())
print(loaded)  # {'name': 'Leon', 'score': 9001, ...}

# ─── PASSWORT-BASIERT (PBKDF2) ───
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
import os

password = b"mein_super_passwort"
salt = os.urandom(16)  # Zufälliger Salt (speichern!)

kdf = PBKDF2HMAC(
    algorithm=hashes.SHA256(),
    length=32,
    salt=salt,
    iterations=480_000  # 2024 Empfehlung NIST
)
key = base64.urlsafe_b64encode(kdf.derive(password))
f = Fernet(key)
Verschlüsselungs-Simulator (Demo mit Web Crypto API)
Klartext:
Verschlüsselt (Base64):
Klicke 'Verschlüsseln' um zu starten...

Asymmetrische Verschlüsselung (RSA)

RSA verwendet ein Schlüsselpaar: Public Key zum Verschlüsseln, Private Key zum Entschlüsseln. Ideal für sichere Kommunikation.

PYTHON – RSA mit cryptography
from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.primitives import hashes, serialization

# ─── SCHLÜSSELPAAR GENERIEREN ───
private_key = rsa.generate_private_key(
    public_exponent=65537,
    key_size=2048  # 2048 oder 4096 Bit empfohlen
)
public_key = private_key.public_key()

# Schlüssel als PEM speichern
pem_private = private_key.private_bytes(
    encoding=serialization.Encoding.PEM,
    format=serialization.PrivateFormat.PKCS8,
    encryption_algorithm=serialization.BestAvailableEncryption(b"passphrase")
)
pem_public = public_key.public_bytes(
    encoding=serialization.Encoding.PEM,
    format=serialization.PublicFormat.SubjectPublicKeyInfo
)

# ─── VERSCHLÜSSELN (mit Public Key) ───
message = b"Geheime Spielstand-Daten"
ciphertext = public_key.encrypt(
    message,
    padding.OAEP(
        mgf=padding.MGF1(algorithm=hashes.SHA256()),
        algorithm=hashes.SHA256(),
        label=None
    )
)

# ─── ENTSCHLÜSSELN (mit Private Key) ───
plaintext = private_key.decrypt(
    ciphertext,
    padding.OAEP(
        mgf=padding.MGF1(algorithm=hashes.SHA256()),
        algorithm=hashes.SHA256(),
        label=None
    )
)
print(plaintext)  # b'Geheime Spielstand-Daten'

# ─── DIGITALE SIGNATUR ───
signature = private_key.sign(
    message,
    padding.PSS(
        mgf=padding.MGF1(hashes.SHA256()),
        salt_length=padding.PSS.MAX_LENGTH
    ),
    hashes.SHA256()
)

# Signatur prüfen (wirft Exception wenn ungültig)
public_key.verify(signature, message,
    padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH),
    hashes.SHA256())

Hashing & Passwörter sicher speichern

Passwörter dürfen niemals im Klartext gespeichert werden! Stattdessen verwendet man kryptografische Hash-Funktionen mit Salt.

PYTHON – Hashing
import hashlib
import hmac
import os

# ─── SHA-256 Hash ───
data = b"Spielstand-Integrität"
h = hashlib.sha256(data).hexdigest()
print(h)  # 64-Zeichen Hex-String

# ─── DATEI-INTEGRITÄT prüfen ───
def file_hash(path: str) -> str:
    h = hashlib.sha256()
    with open(path, "rb") as f:
        while chunk := f.read(8192):
            h.update(chunk)
    return h.hexdigest()

# ─── PASSWORT SICHER HASHEN (PBKDF2) ───
def hash_password(password: str) -> dict:
    salt = os.urandom(32)  # Zufälliger 32-Byte Salt
    key = hashlib.pbkdf2_hmac(
        "sha256", password.encode(),
        salt, iterations=600_000  # NIST 2024 Empfehlung
    )
    return {"salt": salt.hex(), "key": key.hex()}

def verify_password(password: str, stored: dict) -> bool:
    salt = bytes.fromhex(stored["salt"])
    key = hashlib.pbkdf2_hmac(
        "sha256", password.encode(), salt, iterations=600_000
    )
    return hmac.compare_digest(key.hex(), stored["key"])

# Verwendung
stored = hash_password("mein_passwort")
print(verify_password("mein_passwort", stored))    # True
print(verify_password("falsches_pw", stored))      # False

# ─── bcrypt (noch sicherer, externe Lib) ───
# pip install bcrypt
import bcrypt
hashed = bcrypt.hashpw(b"passwort", bcrypt.gensalt(rounds=12))
ok = bcrypt.checkpw(b"passwort", hashed)  # True
Hash-Simulator – Sieh wie Hashing funktioniert
Klicke 'Hash berechnen'...
💡 Ändere nur einen Buchstaben – der Hash ändert sich komplett! Das nennt sich "Avalanche Effect".

Spielstand verschlüsselt speichern

Hier kombinieren wir JSON + Fernet-Verschlüsselung für einen vollständigen, sicheren Spielstand-Manager.

PYTHON – Vollständiger Save Manager
from cryptography.fernet import Fernet
from pathlib import Path
import json, hashlib, os

class SaveManager:
    """Verschlüsselter Spielstand-Manager für Arcade-Spiele."""

    def __init__(self, save_dir: str = "saves"):
        self.save_dir = Path(save_dir)
        self.save_dir.mkdir(exist_ok=True)
        self.key_path = self.save_dir / ".key"
        self.fernet = Fernet(self._get_or_create_key())

    def _get_or_create_key(self) -> bytes:
        if self.key_path.exists():
            return self.key_path.read_bytes()
        key = Fernet.generate_key()
        self.key_path.write_bytes(key)
        return key

    def save(self, slot: int, data: dict) -> None:
        """Spielstand in verschlüsselter Datei speichern."""
        json_bytes = json.dumps(data, ensure_ascii=False).encode()
        encrypted = self.fernet.encrypt(json_bytes)
        path = self.save_dir / f"save_{slot:02d}.dat"
        path.write_bytes(encrypted)
        print(f"Gespeichert: {path}")

    def load(self, slot: int) -> dict | None:
        """Spielstand laden und entschlüsseln."""
        path = self.save_dir / f"save_{slot:02d}.dat"
        if not path.exists():
            return None
        try:
            encrypted = path.read_bytes()
            decrypted = self.fernet.decrypt(encrypted)
            return json.loads(decrypted.decode())
        except Exception as e:
            print(f"Fehler beim Laden: {e}")
            return None

    def delete(self, slot: int) -> None:
        path = self.save_dir / f"save_{slot:02d}.dat"
        path.unlink(missing_ok=True)

    def list_saves(self) -> list[int]:
        return [int(p.stem.split("_")[1]) for p in self.save_dir.glob("save_*.dat")]

# In Arcade verwenden:
save_manager = SaveManager()

# Speichern
save_manager.save(1, {
    "player_name": "Leon",
    "score": 9001,
    "level": 7,
    "position": [340, 220],
    "inventory": ["sword", "potion"]
})

# Laden
data = save_manager.load(1)
print(data)  # {'player_name': 'Leon', ...}