Add encrypted configuration backup and planned restore
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
"""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
|
||||
Reference in New Issue
Block a user