Den Bytecode-Array-Schlüssel vom Backend abrufen
Halten Sie den Schlüssel zur Entschlüsselung des VM-Bytecodes vom Client fern - ein asynchroner Key-Getter holt ihn beim Laden von Ihrem Backend, abgesichert durch Authentifizierung, sodass ein gestohlenes Bundle wirkungslos bleibt.
Das Problem
vmBytecodeArrayEncoding verschlüsselt den VM-Bytecode mit einem Schlüssel. Wird
dieser Schlüssel im Bundle mitgeliefert, besitzt jeder mit der Datei alles, was zur Entschlüsselung nötig ist. Ein
vmBytecodeArrayEncodingKeyGetter erlaubt es, den Schlüssel anderswo aufzubewahren und ihn erst zur Laufzeit zu
erzeugen – ein synchroner Getter kann jedoch nur lesen, was bereits auf dem Client vorliegt (eine globale Variable, ein
Cookie, localStorage). Damit der Schlüssel von Ihrem Server abgerufen werden kann – und Sie ihn hinter einer
Authentifizierung absichern und widerrufen können –, muss der Getter asynchron sein.
Die Lösung
Aktivieren Sie vmAsyncExecutor (v7.3.0+). Mit dem asynchronen Executor darf der
Key-Getter ein Promise zurückgeben und den Schlüssel somit per fetch von Ihrem Backend holen, bevor die VM startet.
Drei Optionen greifen dabei ineinander:
vmBytecodeArrayEncoding: true– verschlüsselt das Bytecode-Array.vmBytecodeArrayEncodingKey– der zur Kompilierzeit verwendete Schlüssel (er bleibt auf Ihrem Server und gehört nicht ins Bundle).vmBytecodeArrayEncodingKeyGetter– ein JS-Ausdruck, der genau diesen Schlüssel zur Laufzeit zurückgibt. Mit aktiviertemvmAsyncExecutordarf er einPromisezurückgeben; ohne diese Option muss der Getter den Schlüssel synchron liefern.
Obfuskierungskonfiguration für den Client
JavaScriptObfuscator.obfuscate(sourceCode, {
vmObfuscation: true,
vmAsyncExecutor: true,
vmBytecodeArrayEncoding: true,
vmBytecodeArrayEncodingKey: process.env.VM_KEY, // e.g. 'mySecretKey123'
vmBytecodeArrayEncodingKeyGetter:
'fetch("/api/vm-key", { credentials: "include" }).then((res) => res.text())'
});
Der Getter-Ausdruck wird wortwörtlich eingebettet und im Browser ausgewertet. Halten Sie den zur Kompilierzeit
verwendeten vmBytecodeArrayEncodingKey aus Ihrem Client-Repository heraus – injizieren Sie ihn beim Build aus einer
Umgebungsvariablen oder einem Secret und liefern Sie exakt dieselbe Zeichenkette über den unten beschriebenen Endpunkt aus.
Wie die beiden Schlüssel zusammenspielen
Ihr Schlüssel wird nie allein verwendet – auf beiden Seiten wird er mit einem internen Schlüssel gemischt, den der Obfuscator kontrolliert:
- Zur Kompilierzeit.
vmBytecodeArrayEncodingKeywird mit einem internen Schlüssel kombiniert, den der Obfuscator ableitet, und das Bytecode-Array wird mit dem daraus entstehenden Mischschlüssel codiert. - Zur Laufzeit. Der Wert, zu dem Ihr
vmBytecodeArrayEncodingKeyGetteraufgelöst wird – also die Antwort Ihres Servers –, wird mit demselben internen Schlüssel kombiniert, der auf dem Client aus verschiedenen Laufzeitfaktoren rekonstruiert wird, um den Bytecode zu decodieren.
Da beide Seiten Ihren Schlüssel mit dem internen Schlüssel mischen, muss der Getter zu exakt derselben Zeichenkette
aufgelöst werden, die Sie als vmBytecodeArrayEncodingKey übergeben haben. Kein Teil genügt für sich allein: Ihr Schlüssel
ohne den internen Schlüssel kann den Bytecode nicht decodieren, und der interne Schlüssel ist ohne Ihren nutzlos – genau
deshalb bleibt ein gestohlenes Bundle wirkungslos, wenn Sie Ihren Schlüssel nur an authentifizierte Aufrufer ausliefern.
Serverseite
Der Endpunkt entscheidet anhand dessen, worauf Ihre Anwendung vertraut, welcher Schlüssel zurückgegeben wird – eine
gültige Sitzung, ein erwarteter Origin- oder Referer-Header, eine Lizenzprüfung und so weiter. Der Kniff: Statt nicht
vertrauenswürdige Aufrufer abzuweisen, geben Sie einen falschen Schlüssel zurück. Der Bytecode decodiert dann zu
Datenmüll, und der geschützte Code scheitert von allein – das ist unauffälliger als ein offensichtlicher 401, der einem
Angreifer genau verrät, was er umgehen muss.
// Express example — the exact checks depend on your app
app.get('/api/vm-key', (req, res) => {
const origin = req.get('origin');
const trusted =
req.session?.user && // a valid session, and
origin === 'https://app.example.com'; // the expected production origin
res.type('text/plain').send(
// Real key for valid users; a decoy for everyone else
// (no session, or a localhost / unexpected origin).
trusted ? process.env.VM_KEY : process.env.VM_DECOY_KEY
);
});
