Documentation
/
Recipes
/

Bytecode Array Encoding Key

Externalizing the Bytecode Array Encoding Key

Pro

Supply your own VM bytecode encryption key with vmBytecodeArrayEncodingKey and hand it back at runtime through a key getter - kept out of the bundle, read from client storage, or fetched from your backend.

Watch

Obfuscator.io Async Executor: Async Bytecode Key Getter and Async-Only VM Virtualization

Watch on YouTube

Obfuscator.io Custom Bytecode Key: Compile-Time Key and Runtime Key Getter (Cookie, Fetch, Decoy Key)

Watch on YouTube

What these options do

vmBytecodeArrayEncoding encrypts the VM bytecode array so it does not sit in the output as plaintext. By default the encryption key is derived from the environment and reconstructed on the client, so you never handle it. That is convenient, but the key material still lives in the bundle.

Two options let you take the key out of the bundle and control it yourself:

  • vmBytecodeArrayEncodingKey - the key you supply at compile time. When set, it is used instead of the default environment-derived key, and it is not embedded in the obfuscated output.
  • vmBytecodeArrayEncodingKeyGetter - a JavaScript expression that returns that same key at runtime. It is embedded verbatim and evaluated in the browser when the obfuscated code loads.

The point is separation: because the key is not in the code, a purely static scan of the bundle cannot recover it. It still has to be present at runtime for the code to run, so it is not truly secret - but you decide where it comes from and who gets to see it.

These two options are a pair, and the obfuscator enforces it: setting one without the other is a build-time validation error (vmBytecodeArrayEncodingKey alone has no way to obtain the key at runtime; a getter alone has no compile-time key to agree with). They also do nothing unless the bytecode array is actually being encrypted, so vmBytecodeArrayEncoding: true must be on as well - or vmSelfDefending: true, which forces vmBytecodeArrayEncoding on internally. Set both keys together with one of those.

How the two keys combine

Your key is never used on its own - on both sides it is mixed with an internal key the obfuscator controls:

  • Compile time. vmBytecodeArrayEncodingKey is combined with an internal key that the obfuscator derives, and the bytecode array is encoded with the resulting mixed key.
  • Runtime. The value your vmBytecodeArrayEncodingKeyGetter resolves to is combined with the same internal key, reconstructed on the client from various runtime factors, to decode the bytecode.

Because both sides mix your key with the internal key, the getter must resolve to the exact same string you passed as vmBytecodeArrayEncodingKey. Neither piece is sufficient alone: your key without the internal key cannot decode the bytecode, and the internal key is useless without yours - which is why controlling who receives your key is what actually protects the code.

Supplying the key at runtime

By default the getter is synchronous: the expression must return the key immediately when the obfuscated code loads. Read it from any source that is already present on the client - a cookie, localStorage, a global variable, or a server-injected DOM element.

JavaScript

The key must exist before the obfuscated code runs:

JavaScript

Other synchronous sources work the same way - pick whichever your app already populates:

JavaScript

Keep the key out of the same file or script as the obfuscated code. Inlining it there defeats the whole point - a static scan of the bundle would recover both the code and its key. Store it in a separate source, and inject the compile-time vmBytecodeArrayEncodingKey from an environment variable or secret rather than committing it.

Fetching the key from your backend (async)

Requires vmAsyncExecutor · v7.3.0+

A synchronous getter can only read what is already on the client. To fetch the key from your server - so you can gate it behind authentication and revoke it - the getter has to be asynchronous, and that requires vmAsyncExecutor. With the async executor enabled, the getter may return a Promise, and the VM awaits it before running.

Enabling vmAsyncExecutor also narrows what gets virtualized: in this mode only the outermost async functions are compiled into the VM, and synchronous code is not virtualized (the rest of the obfuscation still applies). If your program is mostly synchronous, wrap the code you want protected in an async function so it still gets coverage - see vmAsyncExecutor for the full rule.

JavaScript

A Promise-returning getter requires vmAsyncExecutor. This cannot be checked at build time, so a Promise getter with vmAsyncExecutor off fails at runtime.

On the server, decide which key to return based on whatever your application trusts - a validated session, a license check, and so on. Origin or Referer alone does not authenticate a caller, and same-origin GET requests may omit Origin. The twist: instead of rejecting untrusted callers, return a wrong key (VM_DECOY_KEY below). The protected code then fails on its own (errors, wrong results, or a page that stops responding), which is stealthier than an obvious 401 that tells an attacker exactly what to bypass.

Disable response caching, so one caller's key is never served to another. Bind keys to the correct build version and deploy keys and bundles together. A client that receives the real key can still inspect it at runtime.

JavaScript

Serve the exact same string from this endpoint that you passed as vmBytecodeArrayEncodingKey at build time. The getter above fetches a relative URL (/api/vm-key), so a copy of the bundle hosted on another origin requests /api/vm-key from that origin, so it never receives your key; the decoy is what a caller gets when it does reach your endpoint but without a trusted session (an unauthenticated request, a stolen bundle proxied through your origin). Either way the real key never arrives and the protected code does not run.

What counts as "valid" is entirely application-specific: an authenticated session, a signed license, or any combination. Whatever produces the key, wrap it in a Promise and the async executor will await it before running the VM.

When the key doesn't match

The obfuscated code only works when the getter returns exactly the same key used during obfuscation. If the keys differ - or the getter returns undefined, null, or an empty string - the code fails at runtime: it produces wrong results, throws an ordinary runtime error, or stops responding.

There is deliberately no distinct, key-specific error message: a failed key is indistinguishable from any other runtime fault. So when a VM-protected bundle throws, returns wrong results, or freezes only once this option is in play, check the key path first - that the getter resolves on the page, returns a non-empty string, and returns the same value you built with.