🔐 Dateien, Speichern & Verschlüsselung
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 | Typ | Lesbar | Geschwindigkeit | Anwendung |
|---|---|---|---|---|
| JSON | Text | ✅ Ja | Mittel | Konfiguration, Spielstände, APIs |
| CSV | Text | ✅ Ja | Schnell | Highscores, Tabellendaten |
| YAML | Text | ✅ Ja | Langsam | Komplexe Konfiguration |
| TOML | Text | ✅ Ja | Mittel | Projektdateien, Settings |
| Pickle | Binär | ❌ Nein | Sehr schnell | Python-Objekte, ML-Modelle |
| SQLite | Datenbank | ⚠️ Tool | Schnell + SQL | Leaderboards, komplexe Daten |
JSON – JavaScript Object Notation
JSON ist das universellste Format. Lesbar, weit verbreitet, und direkt von Python's json-Modul unterstützt.
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 {}
CSV – Comma Separated Values
CSV ist ideal für tabellarische Daten wie Highscores, Statistiken oder Level-Definitionen.
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.
# 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)
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 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.
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']
SQLite – Eingebettete Datenbank
SQLite ist in Python eingebaut (sqlite3 Modul). Ideal für Highscores, Spieler-Profile und komplexe strukturierte Daten.
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()
Symmetrische Verschlüsselung (AES / Fernet)
Symmetrische Verschlüsselung: Derselbe Schlüssel zum Ver- und Entschlüsseln. cryptography-Bibliothek bietet Fernet (AES-128-CBC + HMAC).
# 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)
Asymmetrische Verschlüsselung (RSA)
RSA verwendet ein Schlüsselpaar: Public Key zum Verschlüsseln, Private Key zum Entschlüsseln. Ideal für sichere Kommunikation.
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.
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
Spielstand verschlüsselt speichern
Hier kombinieren wir JSON + Fernet-Verschlüsselung für einen vollständigen, sicheren Spielstand-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', ...}