JavascriptでWebアプリケーションを実装しているが、無断で再利用される可能性がある。
このため、極力人手では解釈できないように変換した上で、配布したい。
こんなお悩みを解決します。
私は、JavascriptでWebアプリケーションを作成しており、Webブラウザで動作するコードを公開したことがあります。
その際に、無断利用や実装に基づく不正利用等を防ぐため、「難読化」という対応を行っています。
今回は、Javascriptのソースコードを難読化する「JavaScript Obfuscator」について解説します。
Webブラウザで動作するコードの実装例とともに解説するので、興味がある方はぜひ最後までご覧ください。
注意点
今回、紹介するツールは、以下に示す注意点を理解した上で、利用する必要があります。
- 元のコードに戻すことは困難なため、オリジナルのソースコードは別途保存しておく必要があります。
- 難読化のため、設定に依っては不要なコードが挿入されるため、パフォーマンスが低下する可能性があります。
パフォーマンス低下が許容できない場合、設定の見直しが必要です。
概要
「JavaScript Obfuscator」は、以下のいずれかの方法で利用できます。
- Node.js
- Browser
- Web Site
ここでは、環境構築が不要で容易に利用できるWeb Siteでの使い方について解説します。
該当リンク
以下のリンクにアクセスすると、「JavaScript Obfuscator」が利用可能なページが表示されます。
使い方
ここでは、サンプルとして、以下のようなJavascriptのコードを難読化します。
'use strict';
// sample.js
(function () {
const greeting = (message) => {
const output = `Hello ${message}!`;
console.log(output);
};
greeting('World');
// output: Hello World!
}());
該当ページにアクセス
先ほど示したページにアクセスします。
コードをアップロード
上記のソースコードをファイルに保存し、アップロードします。
ここでは、sample.js
として保存し、アップロードしました。
変換
「Obfuscate」を押下し、Javascriptのコードを難読化します。
変換された結果からコードが難読化されていることが確認できます。
動作確認
変換されたコードをHTMLファイルに反映し、Browserで確認します。ここでは、sample.htmlとして保存しました。
<!DOCTYPE HTML>
<html>
<head>
<title>Sample</title>
</head>
<body>
<script>
'use strict';(function(_0x25f9f3,_0x1f03cc){const _0x7f9672=_0x2bc0,_0x446e71=_0x25f9f3();while(!![]){try{const _0x57d923=parseInt(_0x7f9672(0xd8))/0x1*(parseInt(_0x7f9672(0xd9))/0x2)+-parseInt(_0x7f9672(0xd3))/0x3+parseInt(_0x7f9672(0xd5))/0x4+parseInt(_0x7f9672(0xd4))/0x5+parseInt(_0x7f9672(0xd7))/0x6+parseInt(_0x7f9672(0xcf))/0x7+-parseInt(_0x7f9672(0xd1))/0x8;if(_0x57d923===_0x1f03cc)break;else _0x446e71['push'](_0x446e71['shift']());}catch(_0x58b7ad){_0x446e71['push'](_0x446e71['shift']());}}}(_0x2614,0x24ac0));function _0x2bc0(_0x2714c9,_0x433280){const _0x26140c=_0x2614();return _0x2bc0=function(_0x2bc08e,_0x8f304){_0x2bc08e=_0x2bc08e-0xcf;let _0xe27042=_0x26140c[_0x2bc08e];return _0xe27042;},_0x2bc0(_0x2714c9,_0x433280);}(function(){const _0x33b02f=_0x2bc0,_0x2e5c1b=_0xcaf888=>{const _0x36b83a=_0x2bc0,_0x79a47e=_0x36b83a(0xd0)+_0xcaf888+'!';console[_0x36b83a(0xd6)](_0x79a47e);};_0x2e5c1b(_0x33b02f(0xd2));}());function _0x2614(){const _0x5b7404=['1884752KcCkQl','World','475902axtzks','1372630CRDSgi','77656Sjkfiu','log','203946fYgrmA','1136cbhfks','4SBCCmA','1499631FlDRlX','Hello\x20'];_0x2614=function(){return _0x5b7404;};return _0x2614();}
</script>
</body>
</html>
開発者モード(Google Chromeの場合、F12を押下)を開き、consoleの出力結果を確認します。
期待通り、「Hello World!」が出力されていることが確認できます。
オプションの設定
先程は、初期設定のまま利用しましたが、いくつかオプションがあります。
ここでは、2022年11月時点で設定できるオプションについて解説します。
設定できるオプション一覧
設定できるオプションとして以下のものがあります。
オプション設定例
オプションの詳細を解説する前に、私が考える設定例を提示します。
難読化する際に以下の2点を考慮してオプションを設定しています。
- 関数呼び出しによるパフォーマンス低下を極力防ぐ
極力パフォーマンスを維持したまま難読化したいため。 - 連想配列のキーは変更しない
呼び出し先でキーの情報を利用することがあるため。
上記を踏まえて設定した結果を示します。
また、連想配列形式で示すと、以下のようになります。
{
compact: true,
controlFlowFlattening: true,
controlFlowFlatteningThreshold: 0.3,
deadCodeInjection: true,
deadCodeInjectionThreshold: 0.2,
debugProtection: false,
debugProtectionInterval: 0,
disableConsoleOutput: false,
identifierNamesGenerator: 'hexadecimal',
log: false,
numbersToExpressions: true,
renameGlobals: false,
seed: 0,
selfDefending: true,
simplify: true,
splitStrings: true,
splitStringsChunkLength: 7,
stringArray: true,
stringArrayCallsTransform: false,
stringArrayCallsTransformThreshold: 0.5,
stringArrayEncoding: [],
stringArrayIndexShift: true,
stringArrayRotate: true,
stringArrayShuffle: true,
stringArrayWrappersCount: 1,
stringArrayWrappersChainedCalls: true,
stringArrayWrappersParametersMaxCount: 3,
stringArrayWrappersType: 'variable',
stringArrayThreshold: 0.8,
target: 'browser',
transformObjectKeys: false,
unicodeEscapeSequence: true,
}
JSON版は、以下のようになります。
{
"compact": true,
"controlFlowFlattening": true,
"controlFlowFlatteningThreshold": 0.3,
"deadCodeInjection": true,
"deadCodeInjectionThreshold": 0.2,
"debugProtection": false,
"debugProtectionInterval": 0,
"disableConsoleOutput": false,
"identifierNamesGenerator": "hexadecimal",
"log": false,
"numbersToExpressions": true,
"renameGlobals": false,
"seed": 0,
"selfDefending": true,
"simplify": true,
"splitStrings": true,
"splitStringsChunkLength": 7,
"stringArray": true,
"stringArrayCallsTransform": false,
"stringArrayCallsTransformThreshold": 0.5,
"stringArrayEncoding": [],
"stringArrayIndexShift": true,
"stringArrayRotate": true,
"stringArrayShuffle": true,
"stringArrayWrappersCount": 1,
"stringArrayWrappersChainedCalls": true,
"stringArrayWrappersParametersMaxCount": 3,
"stringArrayWrappersType": "variable",
"stringArrayThreshold": 0.8,
"target": "browser",
"transformObjectKeys": false,
"unicodeEscapeSequence": true
}
オプション詳細
Webページの構成に合わせて順番に解説していきます。
また、一部、私の意見が取り込まれています。私の意見に該当する箇所は赤色の下線で表現しております。
Global
ここでは、難読化時の一般的な設定を行います。
Options Preset
難読化時のプリセット(前もって設定された値)を指定できます。
設定値としては、Default、Low、Medium、Highの4種類から選択でき、それぞれ以下のような違いがあります。
設定値 | 内容 |
---|---|
Default | デフォルトの設定で、高いパフォーマンスが期待できます。 |
Low | 難読化度合いは低くなりますが、難読化しないときと同等程度のパフォーマンスとなります。 |
Medium | 中程度の難読化度合いとなりますが、難読化しない場合よりもパフォーマンスは低下します。 |
High | 高い難読化度合いとなりますが、難読化しない場合よりもはるかにパフォーマンスは低下します。 |
基本的に、Defaultを選択しておけばよいと思います。
Target
option名:target
難読化したコードの実行環境を指定できます。
設定値としては、browser、browser-no-eval、nodeの3種類から選択でき、それぞれ以下のような違いがあります。
設定値 | 内容 | 選択基準 |
---|---|---|
browser | browserで動作するJavascriptコードを出力します。 | 下記のいずれにも該当しない場合 |
browser-no-eval | browserとほとんど同じですが、evalが含まれるコードを出力しません。 | browser上でeval関数を利用したくない場合 |
node | browserと同じ結果となりますが、一部のブラウザ固有のオプション(window.locationなど)は使用できません。 | Node.jsで実行するコードを難読化する場合 |
Seed
option名:seed
乱数発生器のシードを設定します。
同じ乱数のシードを用いて変換することで、再現性のある難読化結果となります。
「0」を指定した場合、乱数生成器はシードなしで機能します。
これは、(現実的な利用範囲では)出力結果が毎回異なることを意味します。
Disable Console Output
option名:disableConsoleOutput
下記のスクリプトの呼び出しを無効にします。
注意点:コンソールへの出力が一切され無くなります。
console.log
console.info
console.error
console.warn
console.debug
console.exception
console.trace
Self Defending
option名:selfDefending
コードの自動フォーマットに対する対策が有効になります。
このオプションを有効にして難読化したコードに対し、自動フォーマットを適用すると期待通り機能しなくなります。
このため、より解読が困難なコードとなります。
このオプションを適用する場合の注意点として、以下の2点が挙げられます。
- 難読化後のコードは、基本的に変更できなくなります。(上記の自動フォーマット対策が機能するため)
- このオプションを有効にした場合、compactオプション(Other Transformations参照)も有効になります。
Debug Protection・Debug Protection Interval
option名:debugProtection、debugProtectionInterval
開発者モードを利用しづらくします。
具体的にはdebugger関数が定期的に呼び出され、開発者モードにおいて実行中の処理が一時停止します。
debugger関数を呼び出す間隔は、「Debug Protection Interval(単位はmsec)」で設定できます。
注意点:このオプションを適用する場合、ブラウザがフリーズする可能性があります。
Ignore Imports
option名:ignoreRequireImports
importの難読化を除外します。
ほとんどの場合、Node.jsで難読化する際にこのオプションが関係します。
Domain lock・Domain Lock Redirect Url
前提:Targetとしてnodeを指定している
option名:domainLock、domainLockRedirectUrl
難読化されたソース コードを特定のドメインやサブドメインでのみ実行できるようにします。
指定されたドメインで実行されていない場合、「Domain Lock Redirect Url」に指定されたページにリダイレクトします。
また、ドメインは複数していでき、www.example.com
でのみ有効にしたい場合、以下のように設定します。
www.example.com
また、複数のサブドメイン(www.example.com
、sub.example.com
)で利用したい場合、以下のように簡略化して設定できます。
.example.com
Enable Source Map・Source Map Mode
option名:sourceMap、sourceMapMode
難読化されたコードのソース マップの生成を有効にします。
また、Source Map Modeの設定により、Source Mapの生成方法を指定できます。
設定値 | 内容 | 注意点 |
---|---|---|
inline | 各.jsファイルの末尾にソース マップを追加します。 | 開発者モードで難読化したコードのデバッグが可能となるため、本番環境で適用した場合、第三者に解読される恐れがあります。 |
separate | ソース マップを含む対応する '.map' ファイルを生成します。 | 末尾にソースマップファイルの情報を付与するため、Source Map Base URL・Source Map File Nameの設定が必要になります。 |
Source Map Base URL・Source Map File Name
前提:Source Map Modeとしてseparateを指定している
option名:sourceMapBaseUrl、sourceMapFileName
ベースURLをソース マップのインポートURLに設定します。
また、出力時のソースマップのファイル名を設定します。
Strings Transformations
文字列の変換方法について規定します。
String Array
option名:stringArray
文字列リテラルを削除し、特別な配列に配置します。
例えば、var m = "Hello World";
は、以下のように置き換えられます。
// 変換前
var m = "Hello World";
// 変換後
var m = _0x12c456[0x1];
String Array Rotate
前提:Strin Arrayが有効である
option名:stringArrayRotate
難読化により生成された固定位置もしくはランダム位置のString Arrayの要素をシフトさせます。
これにより、削除された文字列の順序を元の場所に一致させることが難しくなります。
String Array Shuffle
前提:Strin Arrayが有効である
option名:stringArrayShuffle
String Arrayの要素をシャッフルします。
String Array Threshold
option名:stringArrayThreshold
文字列リテラルがString Arrayに挿入される確率を設定できます。
設定値が1に近いほど多くの文字列リテラルが削除され、String Arrayとして扱われます。
また、0を設定すると、String Arrayを無効にする(文字列リテラルの削除が行われなくなる)ことと等価になります。
String Array Index Shift
前提:Strin Arrayが有効である
option名:stringArrayIndexShift
すべての文字列配列呼び出しに対して、追加のインデックス シフトを有効にします。
String Array Indexes Type
前提:Strin Arrayが有効である
option名:stringArrayIndexesType
文字列配列呼び出しインデックスのタイプを制御できます。
設定値としては、hexadecimal-number
、hexadecimal-numeric-string
の2種類から選択でき、それぞれ以下のような違いがあります。
設定値 | 内容 |
---|---|
hexadecimal-number | 文字配列の呼び出しインデックスを16進数に変換します。 |
hexadecimal-numeric-string | 文字列配列呼び出しインデックスを16進数の数値文字列に変換します。 |
String Array Calls Transform・String Array Calls Transform Threshold
前提:Strin Arrayが有効である
option名:stringArrayCallsTransform、stringArrayCallsTransformThreshold
String Arrayに対する呼び出し時の変換を有効にします。
これらの呼び出し時のすべての引数は、「String Array Calls Transform Threshold」の値に依存して異なるオブジェクトとして抽出されます。
これにより、文字列配列への呼び出しを自動的に見つけることがさらに難しくなります。
String Array Wrappers Count
前提:Strin Arrayが有効である
option名:stringArrayWrappersCount
String Arrayに対し、各ルートまたは関数スコープ内のWrapper関数の数を設定します。
String Array Wrappers Type
前提:Strin Arrayが有効である
option名:stringArrayWrappersType
String Arrayに対し、Wrapper関数の種別を設定できます。
設定値としては、variable
、function
の2種類から選択でき、それぞれ以下のような違いがあります。
設定値 | 内容 |
---|---|
variable | 各スコープの先頭に変数ラッパーを追加します。高速パフォーマンスで動作します。 |
function | 各スコープ内のランダムな位置に関数ラッパーを追加します。variable よりもパフォーマンスが低下しますが、より厳密な難読化が提供されます。 |
String Array Wrappers Parameters Maximum Count
前提:Strin Arrayが有効かつString Array Wrappers Typeがfunctionである
option名:stringArrayWrappersParametersMaxCount
String Arrayに対し、Wrapper関数のパラメータの最大数を制御できます。
デフォルトおよび最小値は2です。推奨値は、2から5のいずれかとなります。
String Array Wrappers Chained Calls
前提:Strin Arrayが有効である
option名:stringArrayWrappersChainedCalls
String Arrayに対し、Wrapper関数間の連鎖呼び出しを有効にします。
String Array Encoding
前提:Strin Arrayが有効である
注意点:このオプションを使用すると、スクリプトが遅くなる可能性があります。
option名:stringArrayEncoding
base64
やrc4
を用いて、String Arrayのすべての文字列リテラルをエンコードし、実行時に出力するための特別なコードを挿入します。
設定値としては、none
、base64
、rc4
の3種類から選択でき、それぞれ以下のような違いがあります。
設定値 | 内容 |
---|---|
none | 文字列リテラルをエンコードしません。 |
base64 | base64を使用して、文字列リテラルをエンコードします。 |
rc4 | rc4を使用して、文字列リテラルをエンコードします。base64よりも30%~50%ほどパフォーマンスが低下しますが、初期値を取得するのがより難しくなります。 |
特別な理由がない限り、none
を設定しておけばよいと思います。
Split Strings・Split Strings Chunk Length
option名:splitStrings、splitStringsChunkLength
文字列リテラルを「Split Strings Chunk Length」で指定される長さに分割します。
Unicode Escape Sequence
option名:unicodeEscapeSequence
Unicode Escape Sequenceへの文字列変換を有効または無効にできます。
このオプションを有効にすることにより、コードサイズが大幅に増加し、文字列を簡単に復号できます。小さなソース コードに対してのみ、このオプションを有効にすることをお勧めします。
Force Transform Strings
option名:forceTransformStrings
渡されたRegExpパターンと一致する文字列リテラルの強制変換を有効にします。
制約:「String Array Threshold」により変換されるべきではない文字列にのみ影響を与えます。
注意点:このオプションは、「Reserved String」よりも優先して実行されますが、conditional comments
(IEの独自拡張である条件分岐コメント)よりも優先度は低くなります。
Reserved Strings
option名:reservedStrings
渡されたRegExpパターンと一致する文字列リテラルの変換を無効にします。
Identifiers Transformations
識別子の変換方法について規定します。
Identifier Names Generator・Identifiers Dictionary
option名:identifierNamesGenerator、identifiersDictionary
識別子名ジェネレータを設定します。
設定値としては、dictionary
、hexadecimal
、mangled
、mangled-shuffled
の4種類から選択でき、それぞれ以下のような違いがあります。
設定値 | 内容 |
---|---|
| Identifiers Dictionaryのリストから識別子名を生成します。 |
| _0xabc123 のような識別子名を生成します。 |
| a, b, c などの短い識別子名を生成します。 |
mangled-shuffled | mangled と同じですが、アルファベットがシャッフルされます。 |
Identifiers Prefix
option名:identifiersPrefix
すべてのグローバル識別子のプレフィックスを設定します。
このオプションは、複数のファイルを難読化する場合に使用します。
注意点:グローバル識別子間の競合を回避するのに利用されるため、プレフィックスはファイルごとに異なる必要があります。
Rename Globals
option名:renameGlobals
グローバル変数名とグローバル関数名を難読化対象に含めます。
注意点:このオプションはコードを壊す可能性があります。
このオプションは通常、無効のままで問題ないと思います。
Rename Properties・Rename Properties Mode・Reserved Names
option名:renameProperties、renamePropertiesMode、reservedNames
プロパティ名の名前変更を有効にします。
すべての組み込みDOMプロパティとコアJavaScriptクラスのプロパティは無視されます。
注意点:このオプションはコードを壊す可能性があります。
プロパティ名の名前変更のモードは、safe
、unsafe
の2種類から選択でき、それぞれ以下のような違いがあります。
設定値 | 内容 |
---|---|
| 実行時エラーを防ぐために、より安全な方法でプロパティの名前を変更します。このため、一部のプロパティが名前変更から除外されます。 |
| 安全でない方法でプロパティの名前を変更します。 |
制約:名前を変更したプロパティ名のフォーマットを設定するために、Identifier Names Generatorを使用します。
また、名前を変更するプロパティを制御するには、Reserved Namesを使用します。
このオプションは通常、無効のままで問題ないと思います。
Other Transformations
その他の変換方法について規定します。
Compact
option名:compact
難読化した結果を1行のコードで出力します。
Simplify
option名:simplify
簡素化による追加のコード難読化を有効にします。
Transform Object Keys
option名:transformObjectKeys
オブジェクト キーの変換を有効にします。
Numbers To Expressions
option名:numbersToExpressions
数式への数値変換を有効にします。
Control Flow Flattening・Control Flow Flattening Threshold
option名:controlFlowFlattening、controlFlowFlatteningThreshold
コード制御フローのフラット化を有効にします。
制御フローの平坦化は、プログラムの理解を妨げるソース コードの構造変換です。
注意点:このオプションは、実行速度が最大1.5倍遅くなるため、パフォーマンスに大きく影響します。
「Control Flow Flattening Threshold」は、制御フローの平坦化により影響を受けるノードの割合を設定するために使われます。
Dead Code Injection・Dead Code Injection Threshold
option名:deadCodeInjection、deadCodeInjectionThreshold
デッドコードのランダムブロックが難読化されたコードに追加されます。
制約:String Arrayが強制的に有効になります。
注意点:難読化されたコードのサイズが大幅に増加します(最大200%)。このため、難読化されたコードのサイズが問題にならない場合にのみ使用してください。
「Dead Code Injection Threshold」は、Dead Code Injectionの影響を受けるノードの割合を設定するために使われます。
まとめ
今回は、Javascriptの難読化ツールである「JavaScript Obfuscator」について解説しました。
オプションの説明が大半を占めていますが、ツールを利用する際に参考になると思います。
Javascriptで実装したコードをWeb上で公開する際は、今回のツールを利用し、第三者に解読されないよう、対策をしておくと良いです。