Bytecode Array Encoding Key の外部化
vmBytecodeArrayEncodingKey で VM バイトコードの暗号化キーを自分で指定し、実行時にキーゲッターを通じて返します。キーはバンドルに含めず、クライアントのストレージから読み取るか、バックエンドから取得します。
視聴する
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)
これらのオプションの役割
vmBytecodeArrayEncoding は VM のバイトコード配列を暗号化し、出力に平文のまま残らないようにします。デフォルトでは、暗号化キーは環境から導出され、クライアント側で再構築されるため、キーを自分で扱う必要はありません。これは便利ですが、キーの素材は依然としてバンドル内に存在します。
次の 2 つのオプションを使うと、キーをバンドルから取り出して自分で管理できます。
vmBytecodeArrayEncodingKey:コンパイル時に指定するキーです。設定すると、環境から導出されるデフォルトのキーの代わりに使用され、難読化された出力には埋め込まれません。vmBytecodeArrayEncodingKeyGetter:実行時に同じキーを返す JavaScript の式です。そのまま出力に埋め込まれ、難読化されたコードの読み込み時にブラウザーで評価されます。
目的は分離です。キーがコード内にないため、バンドルを純粋に静的に解析するだけではキーを復元できません。コードを実行するには実行時にキーが存在している必要があるので、本当の意味での秘密ではありません。しかし、キーをどこから取得し、誰に見せるかを自分で決められます。
この 2 つのオプションは対になっており、難読化ツールはそれを強制します。片方だけを設定すると、ビルド時の検証エラーになります(vmBytecodeArrayEncodingKey だけでは実行時にキーを取得する手段がなく、ゲッターだけでは一致させるコンパイル時のキーがありません)。また、バイトコード配列が実際に暗号化されていなければ何の効果もないため、vmBytecodeArrayEncoding: true も有効にする必要があります。あるいは、内部で vmBytecodeArrayEncoding を強制的に有効にする vmSelfDefending: true でもかまいません。両方のキーを、そのどちらかと一緒に設定してください。
2 つのキーの組み合わせ方
指定したキーが単独で使われることはありません。どちらの側でも、難読化ツールが管理する内部キーと組み合わされます。
- コンパイル時。
vmBytecodeArrayEncodingKeyは難読化ツールが導出する内部キーと組み合わされ、その結果得られる混合キーでバイトコード配列がエンコードされます。 - 実行時。
vmBytecodeArrayEncodingKeyGetterが解決した値は、クライアント側でさまざまな実行時の要素から再構築された同じ内部キーと組み合わされ、バイトコードのデコードに使われます。
どちらの側でも指定したキーが内部キーと混合されるため、ゲッターは vmBytecodeArrayEncodingKey に渡したものと完全に同じ文字列に解決される必要があります。どちらか一方だけでは不十分です。指定したキーだけでは内部キーがないためバイトコードをデコードできず、内部キーも指定したキーがなければ役に立ちません。だからこそ、キーを誰が受け取るかを管理することが、実際にコードを守ることになります。
実行時にキーを渡す
デフォルトでは、ゲッターは同期的です。式は、難読化されたコードの読み込み時にすぐキーを返す必要があります。Cookie、localStorage、グローバル変数、サーバーが挿入した DOM 要素など、クライアント上にすでに存在する任意のソースから読み取ってください。
キーは、難読化されたコードが実行される前に存在している必要があります。
その他の同期的なソースも同じように動作します。アプリがすでに値を設定しているものを選んでください。
キーを、難読化されたコードと同じファイルやスクリプトに置かないでください。そこにインライン化すると、この仕組みの意味がすべて失われます。バンドルを静的に解析するだけで、コードとそのキーの両方が復元されてしまうからです。キーは別のソースに保存し、コンパイル時の vmBytecodeArrayEncodingKey はコミットせずに、環境変数やシークレットから注入してください。
バックエンドからキーを取得する(非同期)
vmAsyncExecutor が必要 · v7.3.0+同期的なゲッターは、クライアント上にすでにあるものしか読み取れません。キーをサーバーから取得して、認証の背後に置いたり無効化したりできるようにするには、ゲッターを非同期にする必要があり、それには vmAsyncExecutor が必要です。Async Executor を有効にすると、ゲッターは Promise を返せるようになり、VM は実行前にそれを待機します。
vmAsyncExecutor を有効にすると、仮想化される対象も狭まります。このモードでは最も外側の async 関数だけが VM にコンパイルされ、同期コードは仮想化されません(それ以外の難読化は引き続き適用されます)。プログラムの大部分が同期的な場合は、保護したいコードを async 関数でラップして、引き続き対象になるようにしてください。規則の全体については vmAsyncExecutor を参照してください。
Promise を返すゲッターには vmAsyncExecutor が必須です。これはビルド時に検査できないため、vmAsyncExecutor を無効にしたまま Promise を返すゲッターを使うと、実行時に失敗します。
サーバー側では、検証済みのセッションやライセンスの確認など、アプリケーションが信頼する情報に基づいて、どのキーを返すかを決めます。Origin や Referer だけでは呼び出し元を認証できず、同一オリジンの GET リクエストでは Origin が省略されることもあります。ここでのポイントは、信頼できない呼び出し元を拒否するのではなく、誤ったキー(下の VM_DECOY_KEY)を返すことです。すると保護されたコードはひとりでに失敗します(エラー、誤った結果、あるいはページが応答しなくなる)。これは、何を回避すればよいかを攻撃者に正確に教えてしまう明白な 401 よりも目立ちません。
あるユーザーのキーが別のユーザーに配信されないよう、レスポンスのキャッシュを無効にしてください。キーを正しいビルドのバージョンに対応させ、キーとバンドルは一緒にデプロイしてください。本物のキーを受け取ったクライアントは、実行時にそのキーを調べることができます。
このエンドポイントからは、ビルド時に vmBytecodeArrayEncodingKey に渡したものと完全に同じ文字列を返してください。上のゲッターは相対 URL(/api/vm-key)を取得するため、別のオリジンでホストされたバンドルのコピーはそのオリジンに /api/vm-key を要求するため、あなたのキーを受け取ることはありません。おとりのキーは、エンドポイントに到達はしたものの信頼できるセッションがない呼び出し元(認証されていないリクエストや、あなたのオリジン経由でプロキシされた盗まれたバンドル)が受け取るものです。いずれの場合も本物のキーは届かず、保護されたコードは実行されません。
何を「有効」とみなすかは、完全にアプリケーション次第です。認証済みのセッション、署名付きのライセンス、あるいはその任意の組み合わせが考えられます。キーをどのように生成するにせよ、それを Promise で包めば、Async Executor が VM を実行する前にその結果を待機します。
キーが一致しない場合
難読化されたコードは、ゲッターが難読化時に使ったものとまったく同じキーを返した場合にのみ動作します。キーが異なる場合、またはゲッターが undefined、null、空文字列を返した場合、コードは実行時に失敗します。誤った結果を返すか、通常の実行時エラーを投げるか、応答しなくなります。
キー固有の専用エラーメッセージは、意図的に用意されていません。キーの不一致は、他の実行時エラーと区別がつきません。そのため、VM で保護されたバンドルがこのオプションを使ったときにだけ例外を投げる、誤った結果を返す、またはフリーズする場合は、まずキーの経路を確認してください。ゲッターがページ上で解決されること、空でない文字列を返すこと、ビルド時と同じ値を返すことを確かめてください。
