28 exemples complets · AES-256-GCM · ECDSA P-384 · Argon2id · Anti-Debug · VM · GUI

IronLock v2.0 — Exemples

28 exemples prêts à l'emploi couvrant l'intégralité de l'API IronLock v2 : chiffrement AES-GCM et ChaCha20, licences ECDSA P-384, fingerprint 12 sources, anti-debug Ring0, VM detector, loader, packager, build Cython et migration v1→v2.

AES-256-GCMChaCha20ECDSA P-384 Argon2idFingerprint 12Anti-Debug 12 VM Detect 15Cython
01

Générer une paire de clés ECC P-384

tools/license_generator.py — generate_ecc_keypair()
ECDSA P-384192-bitPremier usage
Génère une paire ECC P-384. La clé privée ne doit jamais être committée dans Git ni envoyée par email. Stocker sur une clé USB chiffrée hors ligne.
from tools.license_generator import generate_ecc_keypair, save_keypair

# Génère une paire ECC P-384
private_pem, public_pem = generate_ecc_keypair()

# Sauvegarde sur disque (chmod 600 automatique sur Unix)
save_keypair(
    private_pem,
    public_pem,
    private_path = "private_key.pem",
    public_path  = "public_key.pem",
)

# Afficher la clé publique à embarquer dans loader.py
print(public_pem)
[+] Private key : private_key.pem ← KEEP SECRET — store offline! [+] Public key : public_key.pem ← Embed in loader.py -----BEGIN PUBLIC KEY----- MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE... -----END PUBLIC KEY-----
🔴
private_key.pem = clé de signature de toutes vos licences. Perte = impossible de regénérer les licences existantes. Stocker sur clé USB AES-256 ou gestionnaire de mots de passe.
02

Générer des clés RSA-2048 (rétro-compat v1)

generate_rsa_keypair() — compatibilité v1
RSA-2048Rétro-compat
À utiliser uniquement si des clients ont encore des licences v1 signées RSA. Pour les nouveaux projets, toujours préférer ECDSA P-384 (exemple 01).
from tools.license_generator import generate_rsa_keypair, save_keypair

# RSA-2048 — v1 uniquement
private_pem, public_pem = generate_rsa_keypair(key_size=2048)
save_keypair(private_pem, public_pem,
             "private_rsa_v1.pem", "public_rsa_v1.pem")

# Dans core/loader.py — rétro-compat
# IRONLOCK_PUBLIC_KEY_RSA_V1 = open("public_rsa_v1.pem").read()
03

Chiffrer un fichier — AES-256-GCM

CryptoEngine.encrypt_file() — AES-GCM
AES-256-GCMAEADArgon2id
Chiffre un fichier en AES-256-GCM avec Argon2id. L'algorithme est sélectionné automatiquement (AES-GCM si AES-NI présent). Le fichier source n'est pas modifié.
import hashlib
from core.crypto_engine import CryptoEngine, build_master_secret

# Construction du secret maître (fingerprint + secret licence)
fingerprint    = "sha256-fingerprint-64-chars"
licence_secret = "hex-secret-64-chars"

master_secret = build_master_secret(fingerprint, licence_secret)
engine        = CryptoEngine(master_secret)

# Chiffrement AES-256-GCM (auto-sélectionné si AES-NI présent)
result = engine.encrypt_file("monapp.exe", "payload.ironenc")

print(f"Algo      : {result['algo']}")
print(f"Original  : {result['size_original']:,} bytes")
print(f"Chiffré   : {result['size_encrypted']:,} bytes")
print(f"Chunks    : {result['chunks_count']}")
print(f"Hash orig : {result['hash_original']}")
Algo : AES-256-GCM Original : 15,234,816 bytes Chiffré : 15,308,240 bytes Chunks : 233 Hash orig : 3a7f8b2c9d1e4f5a6b7c8d9e0f1a2b3c...
04

Chiffrer — ChaCha20-Poly1305 (forcé)

CryptoEngine(prefer_chacha=True)
ChaCha20-Poly1305Constant-timeARM
Force l'utilisation de ChaCha20-Poly1305 — recommandé sur hardware sans AES-NI (ARM bas de gamme, vieilles VMs) car constant-time natif.
from core.crypto_engine import CryptoEngine, build_master_secret

master_secret = build_master_secret("fingerprint", "secret")

# Forcer ChaCha20-Poly1305 (timing-safe sans AES-NI)
engine = CryptoEngine(master_secret, prefer_chacha=True)

result = engine.encrypt_file("payload.py", "payload.ironenc")
print(f"Algo : {result['algo']}")  # ChaCha20-Poly1305

# Le déchiffrement est automatiquement dirigé vers le bon algo
# via le champ ALGO (0x02) dans le header du .ironenc
Algo : ChaCha20-Poly1305
05

Déchiffrer en mémoire (RAM uniquement)

CryptoEngine.decrypt_to_memory()
RAM uniquementAucun fichier tempAuth AEAD
Déchiffre et vérifie l'authenticité du payload directement en mémoire. Aucun fichier temporaire créé. Le hash SHA-256 final est vérifié contre les métadonnées chiffrées.
from core.crypto_engine import CryptoEngine, build_master_secret

master_secret = build_master_secret("fingerprint", "licence_secret")
engine        = CryptoEngine(master_secret)

try:
    payload_bytes = engine.decrypt_to_memory("payload.ironenc")
    print(f"[+] Déchiffré : {len(payload_bytes):,} bytes")
    print(f"[+] Type      : {payload_bytes[:4].hex()}")  # MZ, ELF, PK...
    # → Exécuter en mémoire
except ValueError as e:
    print(f"[!] Échec : {e}")  # Tampered / mauvaise clé
finally:
    # Effacement mémoire best-effort
    payload_bytes = bytes(len(payload_bytes)) if 'payload_bytes' in dir() else None
[+] Déchiffré : 15,234,816 bytes [+] Type : 4d5a0090 (MZ = Windows PE)
06

Déchiffrer un payload v1 (AES-CBC)

Rétro-compat v1 — auto-détection magic IRONLK1
Rétro-compat v1AES-CBC legacy
Aucune configuration requise. Le moteur détecte automatiquement le magic IRONLK1 et bascule sur le chemin de déchiffrement AES-256-GCM legacy.
from core.crypto_engine import CryptoEngine, build_master_secret

# IDENTIQUE à l'exemple 05 — le moteur détecte automatiquement
master_secret = build_master_secret("old_fingerprint_v1", "old_v1_secret")
engine        = CryptoEngine(master_secret)

# payload_v1.ironenc commence par "IRONLK1" → CBC legacy branch
data = engine.decrypt_to_memory("payload_v1.ironenc")

# Vérifier la version du package
with open("payload.ironenc", "rb") as f:
    magic = f.read(7)
    print(f"Format : v1 CBC" if magic == b"IRONLK1" else "Format : v2 GCM")
[+] Déchiffré : 8,421,376 bytes (v1 AES-CBC legacy branch)
07

Argon2id — dérivation de clé maître

derive_master_key() — Argon2id m=64Mo
Argon2idMemory-hardGPU-résistant
Utilisation directe de la fonction de dérivation Argon2id. 64 Mo de RAM requis par opération — rend le brute-force GPU ×62 plus difficile que PBKDF2.
import secrets
from core.crypto_engine import derive_master_key, _ARGON2_AVAILABLE

print(f"Argon2id disponible : {_ARGON2_AVAILABLE}")

salt       = secrets.token_bytes(32)
passphrase = b"mon_secret_machine"

# Argon2id (m=64Mo, t=3, p=4)
master_key = derive_master_key(passphrase, salt)
print(f"Clé dérivée : {master_key.hex()}")
print(f"Longueur    : {len(master_key)} bytes (256-bit)")

# Si argon2-cffi absent → fallback PBKDF2-SHA256 300K auto
Argon2id disponible : True Clé dérivée : 3a7f1b2c9d4e8f5a6b7c8d9e0f1a2b3c... Longueur : 32 bytes (256-bit)
08

HKDF — dérivation de clés de session éphémères

derive_session_key() — HKDF-SHA256
HKDF-SHA256Clés éphémères
Dérive des clés de session distinctes depuis la clé maître via HKDF-SHA256. La clé maître n'est jamais utilisée directement pour le chiffrement.
from core.crypto_engine import derive_master_key, derive_session_key
import secrets

salt       = secrets.token_bytes(32)
master_key = derive_master_key(b"master_secret", salt)

# Deux clés distinctes depuis la même clé maître
enc_key  = derive_session_key(master_key, b"encryption")   # 32B
hmac_key = derive_session_key(master_key, b"hmac")          # 32B
auth_key = derive_session_key(master_key, b"server-auth",   # 64B custom
                               length=64)

print(f"enc_key  : {enc_key.hex()[:32]}...")
print(f"hmac_key : {hmac_key.hex()[:32]}...")
print(f"auth_key : {auth_key.hex()[:32]}...")
enc_key : 3a7f1b2c9d4e8f5a6b7c8d9e0f1a2b3c... hmac_key : f8a2c4e6b0d3f5a7c9e1b3d5f7a9c1e3... auth_key : 1a3b5c7d9e0f2a4b6c8d0e2f4a6b8c0d...
09

Collecter les 12 sources fingerprint

collect_all_sources() + generate_fingerprint()
12 sourcesMulti-OS
Collecte les 12 sources hardware et génère le fingerprint SHA-256 64 chars. À exécuter sur la machine du client pour générer le fichier à envoyer au développeur.
from core.fingerprint import (
    collect_all_sources, generate_fingerprint,
    generate_fingerprint_components,
)

# Collecte des 12 sources (v2)
sources    = collect_all_sources()
fingerprint = generate_fingerprint(sources)
components  = generate_fingerprint_components(sources)

print(f"FINGERPRINT : {fingerprint}")
print(f"Sources     : {len(sources)}/12")

# Afficher les 4 nouvelles sources v2
for k in ["chassis_serial", "volume_serial",
           "cpu_features", "screen_res"]:
    print(f"  {k}: {sources[k]}")
FINGERPRINT : 3a7f1b2c9d4e8f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a Sources : 12/12 chassis_serial : S1Y4NX0B volume_serial : D8F3A21C cpu_features : 7a3b9c2d screen_res : 1920x1080
10

Matching partiel fingerprint

compute_match_score() — tolérance 8/12
ToléranceHardware change
Compare deux sets de composants. Retourne (score, total, clés concordantes). Score ≥ 8/12 = licence valide malgré des changements hardware mineurs.
from core.fingerprint import (
    collect_all_sources, generate_fingerprint_components,
    compute_match_score,
)

# Composants stockés dans la licence
stored_components = {
    "cpu_id": "3a7f1b2c", "mac_address": "b2c9e8f4",
    "disk_serial": "c3d0f7a5", "motherboard_uuid": "d4e1a8b6",
    "bios_serial": "e5f2b9c7", "hostname": "f6a3c0d8",
    "platform_sig": "a7b4d1e9", "cpu_timing": "b8c5e2f0",
    "chassis_serial": "c9d6f3a1", "volume_serial": "d0e7a4b2",
    "cpu_features": "e1f8b5c3", "screen_res": "f2a9c6d4",
}

# Composants actuels (machine peut avoir changé)
sources  = collect_all_sources()
current  = generate_fingerprint_components(sources)

score, total, matched = compute_match_score(stored_components, current)
TOLERANCE = 8

print(f"Score   : {score}/{total}")
print(f"Valide  : {score >= TOLERANCE}")
print(f"Matchés : {matched}")
Score : 10/12 Valide : True (≥ 8 requis) Matchés : ['cpu_id', 'disk_serial', 'motherboard_uuid', 'bios_serial', 'platform_sig', 'cpu_timing', 'chassis_serial', 'volume_serial', 'cpu_features', 'screen_res']
11

Sauvegarder le fingerprint (côté client)

machine_fingerprint.json — fichier à envoyer au développeur
Client-sideJSON
Génère le fichier machine_fingerprint.json que le client envoie au développeur pour générer sa licence. Inclut 12 composants hachés (pas les valeurs brutes).
import json, platform
from core.fingerprint import (
    collect_all_sources, generate_fingerprint,
    generate_fingerprint_components,
)

sources     = collect_all_sources()
fingerprint = generate_fingerprint(sources)
components  = generate_fingerprint_components(sources)

output = {
    "version":     "2.0",
    "fingerprint": fingerprint,
    "components":  components,    # Hashes 16B — pas les valeurs brutes
    "platform":    platform.system(),
    "sources_count": len(components),
}

with open("machine_fingerprint.json", "w") as f:
    json.dump(output, f, indent=2)

print(f"[+] machine_fingerprint.json créé ({len(components)} sources)")
print(f"[+] Envoyer ce fichier au développeur pour obtenir votre licence.")
[+] machine_fingerprint.json créé (12 sources) [+] Envoyer ce fichier au développeur pour obtenir votre licence.
12

Créer une licence ECDSA P-384 (1 an)

create_licence() + sign_licence() — ECDSA P-384
ECDSA P-384192-bit365 jours
Création complète d'une licence 1 an pour un client. Signer avec la clé privée ECC P-384. Vérifier la signature avant envoi.
import json
from tools.license_generator import (
    load_private_key, load_public_key,
    create_licence, sign_licence,
    package_licence, save_licence,
    verify_licence_signature,
)

# Clés ECC P-384
private_key = load_private_key("private_key.pem")
public_key  = load_public_key("public_key.pem")

# Fingerprint client (12 sources)
fp_data = json.loads(open("machine_fingerprint.json").read())

# Création de la licence
licence_data = create_licence(
    customer_name   = "Tristan Ruard",
    customer_email  = "tristan@exemple.com",
    product         = "Mon Application v2.0",
    fingerprint     = fp_data["fingerprint"],
    components      = fp_data["components"],
    duration_days   = 365,
    max_activations = 1,
    perpetual       = False,
)

# Signature ECDSA P-384
signature = sign_licence(licence_data, private_key)
package   = package_licence(licence_data, signature, private_key)

# Vérification avant envoi
valid = verify_licence_signature(package, public_key)
print(f"[+] Signature ECDSA valide : {valid}")
print(f"[+] Format : {package['format']}")
print(f"[+] ID     : {licence_data['id']}")
print(f"[+] Expire : {licence_data['expires_at']}")

save_licence(package, "licence.lic")
[+] Signature ECDSA valide : True [+] Format : IRONLOCK-ECDSA-P384-SHA256-v2 [+] ID : 4c8d7f23-a1b9-4e32-c7d6-8f3b2a1e9c04 [+] Expire : 2026-05-16 [+] Licence saved: licence.lic
13

Licence perpétuelle

create_licence(perpetual=True)
Perpétuelleexpires_at=null
perpetual=Trueexpires_at = null dans la licence. Le loader v2 accepte null comme valeur perpétuelle. Pas de date, pas de vérification d'expiration.
import json
from tools.license_generator import *

private_key = load_private_key("private_key.pem")
fp          = json.loads(open("machine_fingerprint.json").read())

licence_data = create_licence(
    customer_name = "Tristan Ruard",
    product       = "Mon Application v2.0",
    fingerprint   = fp["fingerprint"],
    components    = fp["components"],
    perpetual     = True,             # expires_at = null
    max_activations = 1,
)

print(f"expires_at : {licence_data['expires_at']}")  # None

sig = sign_licence(licence_data, private_key)
pkg = package_licence(licence_data, sig, private_key)
save_licence(pkg, "licence_perpetuelle.lic")
expires_at : None ← Perpétuelle — jamais de vérification expiration
14

Vérifier une signature de licence

verify_licence_signature() — ECDSA P-384
VérificationAudit
Vérification standalone d'une licence sans exécuter le loader. Utile pour auditer une .lic reçue ou intégrer dans un outil de gestion.
import json
from tools.license_generator import load_public_key, verify_licence_signature
from datetime import datetime

public_key = load_public_key("public_key.pem")
package    = json.loads(open("licence.lic").read())
data       = package["data"]

# Vérification cryptographique
valid = verify_licence_signature(package, public_key)

# Vérification expiration
exp = data.get("expires_at")
expired = (not exp) or (datetime.utcnow() > datetime.strptime(exp, "%Y-%m-%d"))

print(f"Client      : {data['customer_name']}")
print(f"Produit     : {data['product']}")
print(f"Signature   : {'✅ VALIDE' if valid else '❌ INVALIDE'}")
print(f"Expire      : {exp or 'Perpétuelle'}")
print(f"Statut      : {'❌ Expirée' if expired else '✅ Active'}")
print(f"Format      : {package['format']}")
print(f"Composants  : {len(data['components'])}/12")
Client : Tristan Ruard Produit : Mon Application v2.0 Signature : ✅ VALIDE Expire : 2026-05-16 Statut : ✅ Active Format : IRONLOCK-ECDSA-P384-SHA256-v2 Composants : 12/12
15

Rétro-compat : accepter licences RSA v1

IRONLOCK_PUBLIC_KEY_RSA_V1 dans loader.py
Rétro-compatRSA-2048
Configure le loader v2 pour accepter également les signatures RSA v1 existantes. La tentative ECDSA P-384 se fait d'abord — RSA uniquement si le format v1 est détecté.
# Dans core/loader.py — avant obfuscation et build

# Clé ECC P-384 principale (nouvelle, v2)
IRONLOCK_PUBLIC_KEY_PEM = """-----BEGIN PUBLIC KEY-----
MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE... (votre clé ECC)
-----END PUBLIC KEY-----"""

# Clé RSA-2048 v1 — rétro-compat
IRONLOCK_PUBLIC_KEY_RSA_V1 = """-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A... (votre ancienne clé RSA)
-----END PUBLIC KEY-----"""

# Le loader tente automatiquement :
# 1. ECDSA P-384 si format = "IRONLOCK-ECDSA-P384-SHA256-v2"
# 2. RSA-PSS     si format = "IRONLOCK-RSA-PSS-SHA256-v1"
16

Licence avec features modules

create_licence(features=[...]) — feature flags
FeaturesModules
Les features permettent de distribuer différentes éditions du même programme depuis un seul payload. Le payload lit licence_data["features"] pour activer les modules.
import json
from tools.license_generator import *

private_key = load_private_key("private_key.pem")
fp          = json.loads(open("machine_fingerprint.json").read())

# Licence "Pro" avec modules avancés
lic_pro = create_licence(
    customer_name = "Tristan Ruard",
    product       = "MonApp v2.0 — Pro",
    fingerprint   = fp["fingerprint"],
    components    = fp["components"],
    duration_days = 365,
    features      = ["standard", "pro", "analytics", "export_pdf"],
)

# Dans le payload — lecture des features
# features = licence_data.get("features", [])
# if "pro" in features: enable_pro_module()
# if "analytics" in features: enable_analytics()

sig = sign_licence(lic_pro, private_key)
pkg = package_licence(lic_pro, sig, private_key)
save_licence(pkg, "licence_pro.lic")
print(f"Features : {lic_pro['features']}")
Features : ['standard', 'pro', 'analytics', 'export_pdf']
17

Anti-debug — 12 checks en ordre aléatoire

run_security_checks(strict=False) — rapport complet
12 checksRing0Ordre aléatoire
Exécute les 12 checks anti-debug dans un ordre aléatoire et retourne un rapport détaillé. Mode strict=True (défaut production) → os._exit(1) si menace détectée.
from core.anti_debug import run_security_checks

# Mode rapport (dev/test)
rapport = run_security_checks(strict=False)

checks_display = [
    ("IsDebuggerPresent",       rapport["debugger_windows"]),
    ("CheckRemoteDebugger",      rapport["debugger_remote"]),
    ("TracerPid (Linux)",         rapport["debugger_linux"]),
    ("Timing anomaly",            rapport["debugger_timing"]),
    ("NtQueryInformationProcess", rapport["nt_query_debug"]),
    ("PEB.NtGlobalFlag",          rapport["peb_flags"]),
    ("RDTSC delta",               rapport["rdtsc_timing"]),
    ("Exception handler",         rapport["exception_handler"]),
    ("CloseHandle(NULL)",          rapport["close_handle"]),
    ("Stack walk",                rapport["stack_walk"]),
]

for name, detected in checks_display:
    icon = "⚠️ DÉTECTÉ" if detected else "✅ Clean"
    print(f"  {name:<30} : {icon}")

print(f"\nProcessus debug : {rapport['debug_processes'] or 'Aucun'}")
print(f"Env debug vars  : {rapport['debug_env_vars'] or 'Aucun'}")
print(f"MENACE DÉTECTÉE : {rapport['threat_detected']}")
IsDebuggerPresent : ✅ Clean NtQueryInformationProcess : ✅ Clean PEB.NtGlobalFlag : ✅ Clean ... Processus debug : Aucun MENACE DÉTECTÉE : False
18

VM Detector — 15 checks + rapport

detect_vm() — score pondéré 15 checks
15 checksCPUIDDocker/WSL
Exécute les 15 checks VM et retourne score, confiance et liste des indicateurs détectés. Score ≥ 3 → VM. Confiance = min(1.0, score / 15.0).
from core.vm_detector import detect_vm

result = detect_vm()

print(f"VM détectée  : {'⚠️ OUI' if result['is_vm'] else '✅ Non'}")
print(f"Score        : {result['score']}")
print(f"Confiance    : {result['confidence']*100:.0f}%")

if result["detections"]:
    print(f"\nIndicateurs ({len(result['detections'])}) :")
    for d in result["detections"]:
        print(f"  - {d}")
else:
    print("\nAucun indicateur de virtualisation.")

# Intégration dans un loader personnalisé
THRESHOLD = 0.3
if result["is_vm"] and result["confidence"] > THRESHOLD:
    print("[!] Exécution en VM non autorisée.")
    import sys; sys.exit(1)
VM détectée : ✅ Non Score : 0 Confiance : 0% Aucun indicateur de virtualisation.
19

Watchdog avec handler personnalisé

WatchdogThread(on_threat=...) — surveillance continue
DaemonHandler custom
Démarre le watchdog avec un handler personnalisé pour alerter une API externe avant l'arrêt. Intervalle de 3 secondes (défaut 5s).
import os, sys, time, urllib.request, json
from core.anti_debug import WatchdogThread

def alert_and_exit(threat_type: str):
    # Envoyer une alerte avant arrêt
    try:
        payload = json.dumps({
            "event":   "security_threat",
            "threat":  threat_type,
            "product": "MonApp v2.0",
        }).encode()
        urllib.request.urlopen(
            "https://api.votreapp.com/security/threat",
            data=payload, timeout=3
        )
    except Exception:
        pass
    # Arrêt immédiat (contourne atexit et finally)
    os._exit(1)

# Démarrage du watchdog
watchdog = WatchdogThread(check_interval=3.0, on_threat=alert_and_exit)
watchdog.start()
print("[+] Watchdog démarré (interval=3s)")

time.sleep(30)  # Simulation exécution programme

watchdog.stop()
print("[+] Watchdog arrêté proprement")
20

Loader minimal — workflow complet

run_loader() — séquence 12 étapes
12 étapesProduction
Invocation standard du loader avec les paramètres de production. Tous les fichiers dans le même répertoire que le script.
from core.loader import run_loader
import sys

# Invocation standard — fichiers dans le même répertoire
run_loader(
    licence_path = "licence.lic",
    payload_path = "payload.ironenc",
    extra_args   = sys.argv[1:],  # Arguments du programme protégé
)

# Séquence complète automatique :
# [1] Anti-debug 12 checks (ordre aléatoire)
# [2] Anti-tamper pré-check
# [3] VM detector 15 checks
# [4] Chargement licence
# [5] Vérif signature ECDSA P-384
# [6] Vérif expiration
# [7] Serveur online (si configuré)
# [8] Fingerprint 12 sources
# [9] Matching 8/12
# [10] Déchiffrement AES-GCM mémoire
# [11] Anti-tamper loader hash
# [12] Exécution + watchdog
21

Loader cloud — VMs autorisées

run_loader(allow_vm=True) — hébergement cloud
CloudVM OKAWS/Azure
Pour les applications déployées sur AWS, Azure, GCP, Docker. Désactive le VM Detector tout en conservant anti-debug, fingerprint et vérification de licence.
from core.loader import run_loader

# Hébergement cloud — VMs explicitement autorisées
run_loader(
    licence_path = "licence.lic",
    payload_path = "payload.ironenc",
    allow_vm     = True,   # Désactive VM Detector
)

# Alternative : configurer dans loader.py avant build
# ALLOW_VM = True
22

Exécuter un payload Python en mémoire

execute_payload_in_memory() — Python exec()
PythonRAM uniquement
Exécute directement un payload Python en mémoire via exec(). Aucun fichier temporaire. Le payload peut lire la licence via une variable injectée dans son namespace.
from core.loader import execute_payload_in_memory

# Payload Python à exécuter
code = b"""
import sys
print(f"[Payload] Python {sys.version}")
print("[Payload] Exécuté entièrement en mémoire — aucun fichier temporaire")
print("[Payload] Arguments :", sys.argv)
"""

# Exécution en mémoire — pas de fichier, pas de trace disque
execute_payload_in_memory(
    payload_bytes = code,
    payload_name  = "app.py",     # Indique le type → Python exec()
    args          = ["--mode", "prod"],
)
[Payload] Python 3.12.0 [Payload] Exécuté entièrement en mémoire — aucun fichier temporaire [Payload] Arguments : ['<ironlock>', '--mode', 'prod']
23

Packager un fichier EXE

package_program() — fichier unique
AES-256-GCMEXE/ELF/.py
Chiffre un fichier unique (EXE, ELF, script Python) en AES-256-GCM et génère le dossier client complet avec les fichiers core IronLock.
from tools.packager import package_program
import secrets

# Générer un master secret pour ce client (32 bytes aléatoires)
master_secret = secrets.token_bytes(32)

result = package_program(
    source        = "monapp.exe",
    product_name  = "Mon Application v2.0",
    output_dir    = "dist_client/",
    version       = "2.0.0",
    master_secret = master_secret,  # Optionnel — auto-généré si None
)

print(f"Output     : {result['output_dir']}")
print(f"Algo       : {result['payload_result']['algo']}")
print(f"Chunks     : {result['payload_result']['chunks_count']}")
print(f"Loader hash: {result['loader_hash'][:32]}...")
Output : dist_client/ Algo : AES-256-GCM Chunks : 233 Loader hash: 3a7f1b2c9d4e8f5a6b7c8d9e0f1a2b3c...
24

Packager un répertoire complet

package_program() — ZIP répertoire → AES-GCM
RépertoireZIP autoRécursif
Chiffre récursivement un répertoire complet : ZIP automatique de tous les fichiers puis chiffrement AES-256-GCM. Le loader dézippe et exécute __main__.py en mémoire.
from tools.packager import package_program

result = package_program(
    source       = "./mon_projet_python/",  # Dossier complet
    product_name = "Suite Applicative v2.0",
    output_dir   = "dist_suite/",
)

# Le payload = ZIP contenant tout mon_projet_python/
# Le loader détecte PK magic → ZipFile → __main__.py → exec()

manifest = result["manifest"]
print(f"Original  : {manifest['payload_name']}")
print(f"Hash      : {manifest['payload_hash'][:32]}...")
print(f"Type      : ZIP → __main__.py → exec() en mémoire")
Original : mon_projet_python.zip Hash : 7b3a9c2d1e5f8a4b... Type : ZIP → __main__.py → exec() en mémoire
25

Build PyArmor + PyInstaller (CLI)

obfuscate_build.py — pipeline 6 étapes
PyArmorPyInstalleronefile
Pipeline complet en 6 étapes : vérification environnement, obfuscation PyArmor, hashes d'intégrité, compilation EXE PyInstaller, assemblage package client.
# Standard — PyArmor obf-code 2 + PyInstaller onefile
python tools/obfuscate_build.py \
  --source-dir core/ \
  --exe-name MonApplication \
  --icon resources/app.ico

# Sans compilation EXE (obfuscation seule)
python tools/obfuscate_build.py \
  --source-dir core/ \
  --skip-pyinstaller

# Onedir (démarrage plus rapide, déploiement plus facile)
python tools/obfuscate_build.py \
  --source-dir core/ \
  --exe-name MonApp \
  --onedir

## Output :
# dist_ironlock_v2_build/
# ├── MonApplication.exe           (ou ELF/Mach-O)
# └── client_package/
#     ├── MonApplication.exe
#     └── README.txt
STEP 1/6 : Environment check → PyArmor ✅ PyInstaller ✅ ECC key ✅ STEP 2/6 : Preparing directories → build_ironlock_v2/ STEP 3/6 : PyArmor obfuscation → obfuscated/ STEP 4/6 : Integrity hashes → integrity_hashes.json STEP 5/6 : PyInstaller → MonApplication.exe (42.1 MB) STEP 6/6 : Client package → client_package/ (42.2 MB) 🎉 BUILD COMPLETE — IronLock v2.0
26

Build C Natif — Cython (v2 recommandé)

obfuscate_build.py --cython — .pyd/.so C natif
Cython 3C natif.pyd/.so
Compile les modules core en extensions C natives (.pyd Windows / .so Linux+macOS) via Cython. Plus de bytecode Python — code C compilé via LLVM, pratiquement impossible à décompiler.
# Prérequis : pip install cython

# Cython C natif + PyInstaller
python tools/obfuscate_build.py \
  --source-dir core/ \
  --exe-name MonApp \
  --cython

## Ce que fait --cython :
# 1. Copie les .py → .pyx
# 2. Génère setup.py Cython
# 3. Compile : python setup.py build_ext --inplace
# 4. Produit : loader.pyd / fingerprint.pyd (Windows)
#              loader.so / fingerprint.so   (Linux/macOS)
# 5. PyInstaller emballe les extensions C natives
#
# Résultat : aucun bytecode Python dans le binaire final
# IDA Pro voit du code C assemblé, pas du Python

# Vérification du résultat
python -c "import loader; print('Chargé depuis .pyd/.so C natif')"
STEP 3/6 : Cython compilation (C native) → loader.pyd (C native extension Windows) → fingerprint.pyd → vm_detector.pyd → anti_debug.pyd → crypto_engine.pyd 🎉 BUILD COMPLETE — IronLock v2.0 (C natif)
27

Self-test complet IronLock v2

Test end-to-end de tous les modules v2
VérificationTous modules
Vérifie que tous les modules IronLock v2 fonctionnent correctement dans l'environnement courant : crypto, fingerprint, anti-debug, VM detector, génération licence.
import os, hashlib, secrets, tempfile

print("═══ IronLock v2.0 — Self-Test Complet ═══\n")

# 1. Crypto Engine
from core.crypto_engine import CryptoEngine, build_master_secret, _ARGON2_AVAILABLE, _HAS_AES_NI
print(f"[Crypto] Argon2id : {'✅' if _ARGON2_AVAILABLE else '⚠️  PBKDF2'}")
print(f"[Crypto] AES-NI   : {'✅' if _HAS_AES_NI else '⚠️  ChaCha20'}")

secret = hashlib.sha256(b"test").digest()
engine = CryptoEngine(secret)
with tempfile.NamedTemporaryFile(delete=False, suffix=".bin") as f:
    f.write(os.urandom(100_000)); src = f.name
r = engine.encrypt_file(src, src + ".enc")
d = engine.decrypt_to_memory(src + ".enc")
ok = d == open(src, "rb").read()
print(f"[Crypto] AES-GCM  : {'✅' if ok else '❌'} ({r['algo']})")
os.unlink(src); os.unlink(src+".enc")

# 2. Fingerprint
from core.fingerprint import collect_all_sources, generate_fingerprint
sources = collect_all_sources()
fp      = generate_fingerprint(sources)
print(f"[Finger] Sources  : {len(sources)}/12 ({'✅' if len(sources)==12 else '⚠️'})")

# 3. Anti-debug
from core.anti_debug import run_security_checks
r = run_security_checks(strict=False)
print(f"[Debug] Threat    : {'⚠️' if r['threat_detected'] else '✅ None'}")

# 4. VM Detector
from core.vm_detector import detect_vm
v = detect_vm()
print(f"[VM]    Score     : {v['score']} ({'⚠️ VM' if v['is_vm'] else '✅ Physique'})")

# 5. Licence (sans clé privée réelle)
from tools.license_generator import generate_ecc_keypair
priv, pub = generate_ecc_keypair()
print("[Lic]   ECC P-384 : ✅ Clé générée")

print("\n═══ SELF-TEST COMPLET ═══")
═══ IronLock v2.0 — Self-Test Complet ═══ [Crypto] Argon2id : ✅ [Crypto] AES-NI : ✅ [Crypto] AES-GCM : ✅ (AES-256-GCM) [Finger] Sources : 12/12 ✅ [Debug] Threat : ✅ None [VM] Score : 0 ✅ Physique [Lic] ECC P-384 : ✅ Clé générée ═══ SELF-TEST COMPLET ═══
28

Migration complète v1 → v2

Guide migration v1 → v2 — sans interruption de service
MigrationZéro interruptionRétro-compat
Migration complète en 5 étapes — maintien de la compatibilité avec les licences v1 existantes pendant la transition.
"""
MIGRATION IronLock v1 → v2 — 5 étapes, zéro interruption

ÉTAPE 1 : Générer les nouvelles clés ECC P-384
"""
from tools.license_generator import generate_ecc_keypair, save_keypair
priv, pub = generate_ecc_keypair()
save_keypair(priv, pub, "private_ecc_v2.pem", "public_ecc_v2.pem")

"""
ÉTAPE 2 : Configurer loader.py v2 avec les DEUX clés
(pendant la période de transition)

IRONLOCK_PUBLIC_KEY_PEM     = open("public_ecc_v2.pem").read()
IRONLOCK_PUBLIC_KEY_RSA_V1  = open("public_rsa_v1.pem").read()  ← Ancien
FINGERPRINT_TOLERANCE       = 8  ← Ajusté pour 12 sources

ÉTAPE 3 : Rechiffrer les payloads existants
"""
from core.crypto_engine import CryptoEngine, build_master_secret
import secrets

old_secret    = build_master_secret("old_fingerprint", "old_secret")
new_secret    = build_master_secret("old_fingerprint", "new_v2_secret")

old_engine    = CryptoEngine(old_secret)
payload_bytes = old_engine.decrypt_to_memory("payload_v1.ironenc")

new_engine    = CryptoEngine(new_secret)
import tempfile, os
with tempfile.NamedTemporaryFile(delete=False) as f:
    f.write(payload_bytes); tmp = f.name
result = new_engine.encrypt_file(tmp, "payload_v2.ironenc")
os.unlink(tmp)
print(f"[+] Re-chiffré : {result['algo']}")

"""
ÉTAPE 4 : Régénérer les licences v2 (ECDSA P-384) lors des renouvellements
         Les licences RSA v1 continuent de fonctionner via rétro-compat

ÉTAPE 5 : Après expiration de TOUTES les licences v1 —
         Retirer IRONLOCK_PUBLIC_KEY_RSA_V1 du loader
         Rebuilder sans la clé RSA
"""
[+] Re-chiffré : AES-256-GCM [+] Migration payload v1 → v2 terminée [+] Les licences RSA v1 existantes continuent de fonctionner [+] Générer les nouvelles licences ECDSA P-384 lors des renouvellements
Migration sans interruption garantie. Les clients avec licences v1 RSA continuent d'utiliser leur logiciel pendant toute la période de transition. Le loader v2 gère les deux formats simultanément.