バイトコード配列エンコードキーの外部化
vmBytecodeArrayEncodingKey で独自の VM バイトコード暗号化キーを指定し、キーゲッターを介して実行時にそれを返します — バンドルの外に保持し、クライアントストレージから読み取り、あるいはバックエンドから取得します。
これらのオプションの役割
vmBytecodeArrayEncoding は VM バイトコード配列を暗号化し、出力内に平文として
存在しないようにします。デフォルトでは、暗号化キーは環境から導出されクライアント側で再構築されるため、キーを
一切扱う必要がありません。これは便利ですが、キーの素材は依然としてバンドル内に残ります。
2 つのオプションを使えば、キーをバンドルから取り出し、自分で制御できます。
vmBytecodeArrayEncodingKey— コンパイル時に指定するキーです。設定すると、デフォルトの環境由来キーの代わりに 使用され、難読化された出力には埋め込まれません。vmBytecodeArrayEncodingKeyGetter— 実行時にその同じキーを返す JavaScript 式です。そのまま埋め込まれ、 難読化されたコードが読み込まれるときにブラウザ内で評価されます。
要点は分離です。キーがコード内に存在しないため、バンドルを純粋に静的にスキャンしてもキーを復元できません。 コードを実行するには実行時にキーが存在している必要があるため、真に秘密というわけではありませんが、キーがどこから 来て誰が見られるかを自分で決められます。
2 つのキーが組み合わさる仕組み
あなたのキーが単独で使われることはありません。両側で、難読化ツールが管理する内部キーと混合されます。
- コンパイル時。
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 で保護されたバンドルが例外をスローする場合は、まず キーのパスを確認してください。ゲッターがページ上で解決すること、空でない文字列を返すこと、そしてビルド時に使った ものと同じ値を返すことを確認します。
