Документация
/
Рецепты
/

Ключ кодирования массива байт-кода

Вынесение ключа кодирования массива байт-кода

Pro

Задайте собственный ключ шифрования байт-кода VM с помощью vmBytecodeArrayEncodingKey и верните его во время выполнения через геттер ключа — он не попадает в бандл, читается из клиентского хранилища или запрашивается с вашего бэкенда.

Что делают эти опции

vmBytecodeArrayEncoding шифрует массив байт-кода VM, чтобы он не находился в выходных данных в виде открытого текста. По умолчанию ключ шифрования выводится из окружения и восстанавливается на клиенте, поэтому вам не приходится с ним работать. Это удобно, но материал ключа по-прежнему остаётся в бандле.

Две опции позволяют вынести ключ из бандла и управлять им самостоятельно:

  • vmBytecodeArrayEncodingKey — ключ, который вы задаёте на этапе компиляции. Если он задан, то используется вместо ключа по умолчанию, выведенного из окружения, и не встраивается в обфусцированный вывод.
  • vmBytecodeArrayEncodingKeyGetter — выражение JavaScript, которое возвращает тот же самый ключ во время выполнения. Оно встраивается дословно и вычисляется в браузере при загрузке обфусцированного кода.

Суть в разделении: поскольку ключа нет в коде, чисто статический анализ бандла не сможет его восстановить. Он всё равно должен присутствовать во время выполнения, чтобы код заработал, поэтому он не является по-настоящему секретным — но вы решаете, откуда он берётся и кто его видит.

Как объединяются два ключа

Ваш ключ никогда не используется сам по себе — с обеих сторон он смешивается с внутренним ключом, которым управляет обфускатор:

  • Этап компиляции. vmBytecodeArrayEncodingKey объединяется с внутренним ключом, который выводит обфускатор, и массив байт-кода кодируется получившимся смешанным ключом.
  • Время выполнения. Значение, к которому разрешается ваш vmBytecodeArrayEncodingKeyGetter, объединяется с тем же внутренним ключом, восстановленным на клиенте из различных факторов времени выполнения, чтобы декодировать байт-код.

Поскольку обе стороны смешивают ваш ключ с внутренним, геттер должен разрешаться в точно ту же строку, которую вы передали как vmBytecodeArrayEncodingKey. Ни одна часть по отдельности не достаточна: ваш ключ без внутреннего не может декодировать байт-код, а внутренний ключ бесполезен без вашего — именно поэтому контроль над тем, кто получает ваш ключ, и есть то, что на самом деле защищает код.

Предоставление ключа во время выполнения

По умолчанию геттер синхронный: выражение должно вернуть ключ немедленно при загрузке обфусцированного кода. Читайте его из любого источника, который уже присутствует на клиенте — из cookie, localStorage, глобальной переменной или внедрённого сервером элемента DOM.

JavaScriptObfuscator.obfuscate(sourceCode, {
    vmObfuscation: true,
    vmBytecodeArrayEncoding: true,
    vmBytecodeArrayEncodingKey: process.env.VM_KEY,       // e.g. 'mySecretKey123'
    vmBytecodeArrayEncodingKeyGetter: "window.__VM_KEY__" // returns the key at runtime
});

Ключ должен существовать до запуска обфусцированного кода:

// Set by a different script, a server-injected inline script, etc.
window.__VM_KEY__ = 'mySecretKey123';

Другие синхронные источники работают так же — выберите тот, который ваше приложение уже заполняет:

// From a cookie
vmBytecodeArrayEncodingKeyGetter: "document.cookie.match(/vmKey=([^;]+)/)?.[1]"

// From localStorage
vmBytecodeArrayEncodingKeyGetter: "localStorage.getItem('vmKey')"

// From a server-injected meta tag
vmBytecodeArrayEncodingKeyGetter: "document.querySelector('meta[name=\"vm-key\"]').content"

// From a nested object
vmBytecodeArrayEncodingKeyGetter: "window.config.encryption.key"

Запрос ключа с вашего бэкенда (асинхронно)

Требует vmAsyncExecutor · v7.3.0+

Синхронный геттер может прочитать только то, что уже есть на клиенте. Чтобы запросить ключ с вашего сервера — так, чтобы вы могли защитить его аутентификацией и отзывать — геттер должен быть асинхронным, а для этого нужен vmAsyncExecutor. При включённом асинхронном исполнителе геттер может возвращать Promise, и VM дожидается его перед запуском.

JavaScriptObfuscator.obfuscate(sourceCode, {
    vmObfuscation: true,
    vmAsyncExecutor: true,
    vmBytecodeArrayEncoding: true,
    vmBytecodeArrayEncodingKey: process.env.VM_KEY,       // kept on your server, not in the bundle
    vmBytecodeArrayEncodingKeyGetter:
        'fetch("/api/vm-key", { credentials: "include" }).then((res) => res.text())'
});

На сервере решайте, какой ключ вернуть, исходя из того, чему доверяет ваше приложение — действующей сессии, ожидаемого Origin или Referer, проверки лицензии и так далее. Хитрость: вместо того чтобы отклонять недоверенных вызывающих, верните неправильный ключ. Тогда байт-код декодируется в мусор, и защищённый код падает сам по себе, что скрытнее очевидного 401, который подсказывает атакующему, что именно нужно обойти.

// 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
    );
});

Отдавайте с этого эндпоинта точно ту же строку, которую вы передали как vmBytecodeArrayEncodingKey на этапе сборки. Копия бандла, работающая вне вашего окружения, получает ключ-приманку, расшифровывается в ничто и оказывается нерабочей.

Когда ключ не совпадает

Обфусцированный код работает только тогда, когда геттер возвращает точно тот же ключ, что использовался при обфускации. Если ключи различаются — или геттер возвращает undefined, null либо пустую строку — расшифровка порождает неправильный поток ключей, и код падает во время выполнения с мусорным выводом или обычной ошибкой времени выполнения.

Намеренно нет отдельного сообщения об ошибке, специфичного для ключа: неверный ключ неотличим от любого другого сбоя во время выполнения. Поэтому, если защищённый VM бандл выбрасывает ошибку только когда эта опция задействована, сначала проверьте путь ключа — что геттер разрешается на странице, возвращает непустую строку и возвращает то же значение, с которым вы собирали.