Externalizar la clave de Bytecode Array Encoding
Proporciona tu propia clave de cifrado del bytecode de la VM con vmBytecodeArrayEncodingKey y entrégala de nuevo en tiempo de ejecución mediante un getter de clave, fuera del bundle, leída del almacenamiento del cliente o descargada desde tu backend.
Ver
Obfuscator.io Async Executor: Async Bytecode Key Getter and Async-Only VM Virtualization
Obfuscator.io Custom Bytecode Key: Compile-Time Key and Runtime Key Getter (Cookie, Fetch, Decoy Key)
Qué hacen estas opciones
vmBytecodeArrayEncoding cifra el array de bytecode de la VM para que no quede en
la salida como texto plano. De forma predeterminada, la clave de cifrado se deriva del entorno y se reconstruye en el cliente, así
que nunca tienes que manejarla. Es cómodo, pero el material de la clave sigue estando en el bundle.
Dos opciones te permiten sacar la clave del bundle y controlarla tú mismo:
vmBytecodeArrayEncodingKey: la clave que proporcionas en tiempo de compilación. Cuando se establece, se usa en lugar de la clave predeterminada derivada del entorno, y no se incrusta en la salida ofuscada.vmBytecodeArrayEncodingKeyGetter: una expresión de JavaScript que devuelve esa misma clave en tiempo de ejecución. Se incrusta literalmente y se evalúa en el navegador cuando se carga el código ofuscado.
Lo que se busca es la separación: como la clave no está en el código, un análisis puramente estático del bundle no puede recuperarla. Aun así, tiene que estar presente en tiempo de ejecución para que el código funcione, por lo que no es realmente secreta; pero tú decides de dónde procede y quién puede verla.
Estas dos opciones van en pareja, y el ofuscador lo exige: definir una sin la otra es un error de validación en la compilación
(vmBytecodeArrayEncodingKey por sí sola no tiene forma de obtener la clave en tiempo de ejecución; un getter por sí solo no
tiene ninguna clave de compilación con la que coincidir). Además, no hacen nada si el array de bytecode no se está cifrando
realmente, así que también debe estar activado vmBytecodeArrayEncoding: true, o bien vmSelfDefending: true, que fuerza
internamente la activación de vmBytecodeArrayEncoding. Define ambas claves junto con una de esas dos opciones.
Cómo se combinan las dos claves
Tu clave nunca se usa por sí sola: en ambos lados se mezcla con una clave interna que controla el ofuscador:
- En tiempo de compilación.
vmBytecodeArrayEncodingKeyse combina con una clave interna que deriva el ofuscador, y el array de bytecode se codifica con la clave mezclada resultante. - En tiempo de ejecución. El valor al que se resuelve tu
vmBytecodeArrayEncodingKeyGetterse combina con la misma clave interna, reconstruida en el cliente a partir de diversos factores de ejecución, para decodificar el bytecode.
Como ambos lados mezclan tu clave con la clave interna, el getter debe resolverse exactamente a la misma cadena que
pasaste como vmBytecodeArrayEncodingKey. Ninguna de las dos piezas basta por sí sola: tu clave sin la clave interna no
puede decodificar el bytecode, y la clave interna no sirve de nada sin la tuya. Por eso lo que realmente protege el
código es controlar quién recibe tu clave.
Proporcionar la clave en tiempo de ejecución
De forma predeterminada, el getter es síncrono: la expresión debe devolver la clave inmediatamente cuando se carga el código
ofuscado. Léela de cualquier fuente que ya esté presente en el cliente: una cookie, localStorage, una variable global o
un elemento del DOM inyectado por el servidor.
La clave debe existir antes de que se ejecute el código ofuscado:
Otras fuentes síncronas funcionan igual; elige la que tu aplicación ya rellene:
No pongas la clave en el mismo archivo o script que el código ofuscado. Incluirla ahí anula todo el propósito: un
análisis estático del bundle recuperaría tanto el código como su clave. Guárdala en una fuente separada, e inyecta la
vmBytecodeArrayEncodingKey de compilación desde una variable de entorno o un secreto en lugar de hacer commit de ella.
Obtener la clave desde tu backend (asíncrono)
Requiere vmAsyncExecutor · v7.3.0+Un getter síncrono solo puede leer lo que ya está en el cliente. Para obtener la clave desde tu servidor, de modo que
puedas protegerla con autenticación y revocarla, el getter tiene que ser asíncrono, y eso requiere
vmAsyncExecutor. Con Async Executor activado, el getter puede devolver
una Promise, y la VM la espera antes de ejecutarse.
Activar vmAsyncExecutor también limita lo que se virtualiza: en este modo solo se compilan en la VM las funciones async
más externas, y el código síncrono no se virtualiza (el resto de la ofuscación se sigue aplicando). Si tu programa es mayoritariamente síncrono, envuelve el código que quieres
proteger en una función async para que siga quedando cubierto; consulta vmAsyncExecutor
para ver la regla completa.
Un getter que devuelve una Promise requiere vmAsyncExecutor. Esto no puede comprobarse en tiempo de compilación,
así que un getter con Promise y vmAsyncExecutor desactivado falla en tiempo de ejecución.
En el servidor, decide qué clave devolver según aquello en lo que confíe tu aplicación: una sesión validada, una
comprobación de licencia, etc. Origin o Referer por sí solos no autentican a quien llama, y las solicitudes GET del
mismo origen pueden omitir Origin. El truco: en lugar de rechazar a quien no es de confianza, devuelve una clave
incorrecta (VM_DECOY_KEY más abajo). El código protegido falla entonces por sí solo (errores, resultados
incorrectos o una página que deja de responder), lo que es más discreto que un 401 evidente que le dice al atacante exactamente qué tiene que eludir.
Desactiva el almacenamiento en caché de la respuesta para que la clave de un cliente nunca se sirva a otro. Vincula las claves a la versión de compilación correcta y despliega las claves y los bundles a la vez. Un cliente que recibe la clave real aún puede inspeccionarla en tiempo de ejecución.
Sirve desde este endpoint exactamente la misma cadena que pasaste como vmBytecodeArrayEncodingKey en la compilación. El getter
anterior obtiene una URL relativa (/api/vm-key), así que una copia del bundle alojada en otro origen solicita /api/vm-key
a ese origen, así que nunca recibe tu clave; la clave señuelo es lo que recibe quien sí llega a tu endpoint, pero sin una
sesión de confianza (una solicitud sin autenticar, un bundle robado servido a través de tu origen mediante un proxy). En ambos casos,
la clave real nunca llega y el código protegido no se ejecuta.
Lo que cuenta como "válido" depende por completo de la aplicación: una sesión autenticada, una licencia firmada o
cualquier combinación. Sea lo que sea lo que produzca la clave, envuélvelo en una Promise y el ejecutor asíncrono la
esperará antes de ejecutar la VM.
Cuando la clave no coincide
El código ofuscado solo funciona cuando el getter devuelve exactamente la misma clave usada durante la ofuscación.
Si las claves difieren, o si el getter devuelve undefined, null o una cadena vacía, el código falla en tiempo de
ejecución: produce resultados incorrectos, lanza un error de ejecución corriente o deja de responder.
Deliberadamente no existe un mensaje de error específico de la clave: un fallo de clave es indistinguible de cualquier otro fallo en tiempo de ejecución. Así que, cuando un bundle protegido con VM solo lanza errores, devuelve resultados incorrectos o se bloquea desde que usas esta opción, revisa primero la ruta de la clave: que el getter se resuelva en la página, que devuelva una cadena no vacía y que devuelva el mismo valor con el que compilaste.
