Ntrlegendzip

def encrypt_zip(
    src_paths: List[pathlib.Path],
    dst_path: pathlib.Path,
    password: str,
    compression: int = zipfile.ZIP_DEFLATED,
    compresslevel: int = 9,
) -> None:
    # Normalise arguments
    src_paths = [p if isinstance(p, pathlib.Path) else pathlib.Path(p) for p in src_paths]
    dst_path = pathlib.Path(dst_path)
# Ensure destination folder exists
    dst_path.parent.mkdir(parents=True, exist_ok=True)
# Open a new zip file in write‑binary mode
    with zipfile.ZipFile(
        dst_path,
        mode='w',
        compression=compression,
        compresslevel=compresslevel,
        allowZip64=True,
    ) as zf:
        for src in src_paths:
            if not src.exists():
                raise FileNotFoundError(f"Source src!s does not exist")
            # Walk directories recursively
            for root, _, files in os.walk(src):
                for fname in files:
                    full_path = pathlib.Path(root) / fname
                    # Compute the archive name (relative to the root `src`)
                    arcname = full_path.relative_to(src.parent).as_posix()
# ----- read raw bytes -----
                    with full_path.open('rb') as f:
                        plaintext = f.read()
# ----- encrypt -----
                    salt = secrets.token_bytes(SALT_SIZE)
                    iv = secrets.token_bytes(IV_SIZE)
                    key = _derive_key(password, salt)
                    aesgcm = AESGCM(key)
                    ciphertext = aesgcm.encrypt(iv, plaintext, None)  # returns ct || tag
# GCM tag is last TAG_SIZE bytes of the ciphertext
                    tag = ciphertext[-TAG_SIZE:]
                    ct_body = ciphertext[:-TAG_SIZE]
# ----- build header + payload -----
                    header = _make_encryption_header(salt, iv, tag)
                    payload = header + ct_body
# ----- write to zip -----
                    zinfo = zipfile.ZipInfo(arcname)
                    # Preserve original timestamp (optional)
                    st = full_path.stat()
                    zinfo.date_time = tuple(time.localtime(st.st_mtime)[:6])
                    zinfo.compress_type = compression
                    # Force ZIP stored size to match our payload length
                    zinfo.file_size = len(payload)
                    # Store encrypted data as a regular file entry
                    zf.writestr(zinfo, payload)

The holy grail for most users is media content (images, videos, PDFs). If you open the zip and find a .exe, .scr, or .bat file, delete the archive immediately. Legitimate "legendary" collections of images should only contain .jpg, .png, .gif, or .txt files.

# ntrlegendzip/encrypted.py
import os
import zipfile
import pathlib
import secrets
import struct
from typing import List
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.hazmat.primitives import hashes
from ._constants import (
    MAGIC, VERSION, SALT_SIZE, IV_SIZE, TAG_SIZE,
    PBKDF2_ITERATIONS, KEY_SIZE,
)
class NtlzError(RuntimeError): pass
class NtlzBadPassword(NtlzError): pass
class NtlzCorruptHeader(NtlzError): pass
def _derive_key(password: str, salt: bytes) -> bytes:
    kdf = PBKDF2HMAC(
        algorithm=hashes.SHA256(),
        length=KEY_SIZE,
        salt=salt,
        iterations=PBKDF2_ITERATIONS,
    )
    return kdf.derive(password.encode('utf-8'))
def _make_encryption_header(salt: bytes, iv: bytes, tag: bytes) -> bytes:
    """
    Layout (total 3+1+16+12+16 = 48 bytes):
        MAGIC (3) | VERSION (1) | SALT (16) | IV (12) | TAG (16)
    """
    return MAGIC + VERSION + salt + iv + tag
def _parse_encryption_header(data: bytes) -> tuple[bytes, bytes, bytes]:
    if len(data) < len(MAGIC) + 1 + SALT_SIZE + IV_SIZE + TAG_SIZE:
        raise NtlzCorruptHeader("Header too short")
    if not data.startswith(MAGIC):
        raise NtlzCorruptHeader("Missing NLZ magic")
    # Slice according to the layout defined above
    offset = len(MAGIC) + 1
    salt = data[offset:offset + SALT_SIZE]
    offset += SALT_SIZE
    iv = data[offset:offset + IV_SIZE]
    offset += IV_SIZE
    tag = data[offset:offset + TAG_SIZE]
    return salt, iv, tag