62f550c373
Selbstgehostete Web-Cloud mit Dateiverwaltung, Kalender, Kontakte, Email-Webclient, Office-Viewer und Passwort-Manager. Backend (Flask/Python): - JWT-Auth mit Access/Refresh Tokens, Benutzerverwaltung - Dateien: Upload/Download, Ordner, Berechtigungen, Share-Links - Kalender: CRUD, Teilen, iCal-Export, CalDAV well-known URLs - Kontakte: Adressbuecher, vCard-Export, Teilen - Email: IMAP/SMTP-Proxy, Multi-Account - Office-Viewer: DOCX/XLSX/PPTX/PDF Vorschau - Passwort-Manager: AES-256-GCM clientseitig, KeePass-Import - Sync-API fuer Desktop/Mobile-Clients - SQLite mit WAL-Modus Frontend (Vue 3 + PrimeVue): - Datei-Explorer mit Breadcrumbs und Share-Dialogen - Monatskalender mit Event-Verwaltung - Kontaktliste mit Adressbuch-Sidebar - Email-Client mit 3-Spalten-Layout - Passwort-Manager mit TOTP und Passwort-Generator - Admin-Panel, Settings, oeffentliche Share-Seite Docker: Multi-Stage Build, Bind Mounts (keine Volumes) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
40 lines
1.3 KiB
Python
40 lines
1.3 KiB
Python
"""Encryption/decryption for email passwords and other server-side secrets.
|
|
|
|
Uses AES-256-GCM with a key derived from the user's encryption key.
|
|
The encryption key is passed from the frontend via X-Encryption-Key header
|
|
and is itself derived from the user's login password using PBKDF2.
|
|
"""
|
|
import base64
|
|
import hashlib
|
|
import os
|
|
|
|
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
|
|
|
|
|
def _derive_key(user_key_b64: str) -> bytes:
|
|
"""Derive a 256-bit AES key from the user's base64-encoded key."""
|
|
try:
|
|
raw = base64.b64decode(user_key_b64)
|
|
except Exception:
|
|
raw = user_key_b64.encode('utf-8')
|
|
return hashlib.sha256(raw).digest()
|
|
|
|
|
|
def encrypt_field(plaintext: str, user_key_b64: str) -> bytes:
|
|
"""Encrypt a string field. Returns nonce + ciphertext as bytes."""
|
|
key = _derive_key(user_key_b64)
|
|
aesgcm = AESGCM(key)
|
|
nonce = os.urandom(12)
|
|
ciphertext = aesgcm.encrypt(nonce, plaintext.encode('utf-8'), None)
|
|
return nonce + ciphertext
|
|
|
|
|
|
def decrypt_field(encrypted: bytes, user_key_b64: str) -> str:
|
|
"""Decrypt a field. Input is nonce (12 bytes) + ciphertext."""
|
|
key = _derive_key(user_key_b64)
|
|
aesgcm = AESGCM(key)
|
|
nonce = encrypted[:12]
|
|
ciphertext = encrypted[12:]
|
|
plaintext = aesgcm.decrypt(nonce, ciphertext, None)
|
|
return plaintext.decode('utf-8')
|