"""Versioned, authenticated backup envelope; no archive extraction or executable pickle.""" import hashlib,json,secrets,zlib MAGIC=b'ATHENA-DECK-BACKUP\x01\n' MAX=64*1024*1024 def password(value): if not isinstance(value,str) or not 16<=len(value)<=256:raise ValueError('Backup-Kennwort: 16 bis 256 Zeichen erforderlich.') return value.encode() def seal(document,phrase): from cryptography.hazmat.primitives.ciphers.aead import AESGCM salt=secrets.token_bytes(16);nonce=secrets.token_bytes(12);head=MAGIC+salt+nonce key=hashlib.scrypt(password(phrase),salt=salt,n=32768,r=8,p=1,maxmem=64*1024*1024,dklen=32) raw=json.dumps(document,ensure_ascii=False,allow_nan=False,separators=(',',':')).encode() if len(raw)>MAX:raise ValueError('Konfigurationsbackup überschreitet 64 MiB.') return head+AESGCM(key).encrypt(nonce,zlib.compress(raw),head) def open_backup(raw,phrase): from cryptography.hazmat.primitives.ciphers.aead import AESGCM from cryptography.exceptions import InvalidTag if not isinstance(raw,bytes) or not len(MAGIC)+44<=len(raw)<=MAX or not raw.startswith(MAGIC):raise ValueError('Kein unterstütztes Athena-Deck-Backup.') salt=raw[len(MAGIC):len(MAGIC)+16];nonce=raw[len(MAGIC)+16:len(MAGIC)+28];head=raw[:len(MAGIC)+28] key=hashlib.scrypt(password(phrase),salt=salt,n=32768,r=8,p=1,maxmem=64*1024*1024,dklen=32) try:packed=AESGCM(key).decrypt(nonce,raw[len(head):],head) except InvalidTag:raise ValueError('Backup-Kennwort falsch oder Backup beschädigt.') from None try: decoder=zlib.decompressobj();data=decoder.decompress(packed,MAX+1) if len(data)>MAX or decoder.unconsumed_tail or not decoder.eof or decoder.unused_data:raise ValueError() result=json.loads(data,parse_constant=lambda _:(_ for _ in ()).throw(ValueError())) except (ValueError,zlib.error,UnicodeError):raise ValueError('Backup-Inhalt ungültig oder zu groß.') from None if not isinstance(result,dict):raise ValueError('Ungültiges Backup.') return result