Вынесение ключа кодирования массива байт-кода
Задайте собственный ключ шифрования байт-кода 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 бандл выбрасывает ошибку только когда эта опция задействована, сначала проверьте путь ключа — что геттер разрешается на странице, возвращает непустую строку и возвращает то же значение, с которым вы собирали.
