Hello, voici le script python pour virer le mdp du projet
Code :
#!/usr/bin/env python3
"""
Retire la protection par mot de passe d'un fichier .knxproj (ETS 5.7 / 6).
Usage :
python knxproj_unprotect.py projet.knxproj
python knxproj_unprotect.py projet.knxproj -p "MotDePasse" -o projet_clair.knxproj
Si -p est omis, le mot de passe est demandé de façon masquée.
Dépendance : pip install pyzipper
"""
import argparse
import base64
import getpass
import hashlib
import io
import re
import sys
import zipfile
from pathlib import Path
try:
import pyzipper
except ImportError:
sys.exit("Module manquant : installe-le avec pip install pyzipper")
# Paramètres de dérivation utilisés par ETS pour le mot de passe du zip
ETS_SALT = b"21.project.ets.knx.org"
ETS_ITERATIONS = 65536
def derive_zip_password(password: str) -> bytes:
"""Mot de passe ETS -> mot de passe réel du zip (PBKDF2-SHA256 puis base64)."""
key = hashlib.pbkdf2_hmac(
"sha256", password.encode("utf-16-le"), ETS_SALT, ETS_ITERATIONS, dklen=32
)
return base64.b64encode(key)
def candidate_passwords(password: str):
"""Mots de passe à essayer : dérivé ETS en premier, brut en secours."""
yield derive_zip_password(password)
yield password.encode("utf-8")
def _is_encrypted(zf: pyzipper.AESZipFile) -> bool:
return any(info.flag_bits & 0x1 for info in zf.infolist())
def _open_zip(data, password: str | None):
"""Ouvre un zip (chemin ou bytes) ; teste les mots de passe candidats si chiffré."""
src = io.BytesIO(data) if isinstance(data, (bytes, bytearray)) else data
zf = pyzipper.AESZipFile(src)
if not _is_encrypted(zf):
return zf
if password is None:
raise ValueError("Archive chiffrée mais aucun mot de passe fourni.")
# on teste sur le premier fichier non-répertoire
probe = next(i for i in zf.infolist() if not i.is_dir())
for pwd in candidate_passwords(password):
zf.setpassword(pwd)
try:
zf.read(probe)
return zf
except Exception:
continue
raise ValueError("Mot de passe incorrect.")
def unprotect(src: Path, dst: Path, password: str | None) -> None:
outer = _open_zip(str(src), password)
nb_inner = 0
with zipfile.ZipFile(dst, "w", compression=zipfile.ZIP_DEFLATED) as out:
for info in outer.infolist():
if info.is_dir():
continue
data = outer.read(info)
name = info.filename
# Archive interne de projet : P-XXXX.zip -> dossier P-XXXX/
m = re.fullmatch(r"(?:.*/)?(P-[0-9A-Fa-f]+)\.zip", name)
if m:
folder = m.group(1)
inner = _open_zip(data, password)
nb_inner += 1
for iinfo in inner.infolist():
if iinfo.is_dir():
continue
iname = iinfo.filename
# évite un doublon P-XXXX/P-XXXX/... si déjà préfixé
if iname.startswith(folder + "/"):
iname = iname[len(folder) + 1:]
out.writestr(f"{folder}/{iname}", inner.read(iinfo))
continue
out.writestr(name, data)
if nb_inner == 0:
print("Note : aucune archive P-XXXX.zip trouvée, le fichier était peut-être "
"déjà en clair (contenu simplement recopié).")
else:
print(f"OK : {nb_inner} projet(s) déchiffré(s) -> {dst}")
def main() -> None:
ap = argparse.ArgumentParser(description="Retire le mot de passe d'un .knxproj")
ap.add_argument("fichier", type=Path, help="fichier .knxproj protégé")
ap.add_argument("-p", "--password", help="mot de passe du projet")
ap.add_argument("-o", "--output", type=Path,
help="fichier de sortie (défaut : <nom>_unprotected.knxproj)")
args = ap.parse_args()
if not args.fichier.is_file():
sys.exit(f"Fichier introuvable : {args.fichier}")
dst = args.output or args.fichier.with_name(args.fichier.stem + "_unprotected.knxproj")
if dst.resolve() == args.fichier.resolve():
sys.exit("Le fichier de sortie doit être différent du fichier d'entrée.")
password = args.password
if password is None:
password = getpass.getpass("Mot de passe du projet (laisser vide si aucun) : ") or None
try:
unprotect(args.fichier, dst, password)
except ValueError as e:
dst.unlink(missing_ok=True)
sys.exit(f"Erreur : {e}")
if __name__ == "__main__":
main()
