はじめに
JavaScriptの難読化で調べると最有力として挙がる「JavaScript Obfuscator」。オプションの項目を眺めていると、難読化以外にも色々機能が実装されていて面白かったので、README.mdのオプション項目の日本語訳という形で紹介します。シンプルな英語だし訳いらんというのは(ry
JavaScript Obfuscator オプション
JS Obfuscatorでは次のオプションを使用できます。
options:
{
compact: true,
controlFlowFlattening: false,
controlFlowFlatteningThreshold: 0.75,
deadCodeInjection: false,
deadCodeInjectionThreshold: 0.4,
debugProtection: false,
debugProtectionInterval: false,
disableConsoleOutput: false,
domainLock: [],
identifierNamesGenerator: 'hexadecimal',
identifiersPrefix: '',
inputFileName: '',
log: false,
renameGlobals: false,
reservedNames: [],
reservedStrings: [],
rotateStringArray: true,
seed: 0,
selfDefending: false,
sourceMap: false,
sourceMapBaseUrl: '',
sourceMapFileName: '',
sourceMapMode: 'separate',
stringArray: true,
stringArrayEncoding: false,
stringArrayThreshold: 0.75,
target: 'browser',
transformObjectKeys: false,
unicodeEscapeSequence: false
}
CLI options:
-v, --version
-h, --help
-o, --output
--compact <boolean>
--config <string>
--control-flow-flattening <boolean>
--control-flow-flattening-threshold <number>
--dead-code-injection <boolean>
--dead-code-injection-threshold <number>
--debug-protection <boolean>
--debug-protection-interval <boolean>
--disable-console-output <boolean>
--domain-lock '<list>' (comma separated)
--exclude '<list>' (comma separated)
--identifier-names-generator <string> [hexadecimal, mangled]
--identifiers-prefix <string>
--log <boolean>
--rename-globals <boolean>
--reserved-names '<list>' (comma separated)
--reserved-strings '<list>' (comma separated)
--rotate-string-array <boolean>
--seed <number>
--self-defending <boolean>
--source-map <boolean>
--source-map-base-url <string>
--source-map-file-name <string>
--source-map-mode <string> [inline, separate]
--string-array <boolean>
--string-array-encoding <boolean|string> [true, false, base64, rc4]
--string-array-threshold <number>
--target <string> [browser, browser-no-eval, node]
--transform-object-keys <boolean>
--unicode-escape-sequence <boolean>
compact
型: boolean
デフォルト値: true
コードを1行に纏める。
config
型: string
デフォルト値: ``
オプション設定を別ファイル化した場合のファイル名。(JS,JSONファイルに対応)これらは、CLIに直接渡されるオプションによってオーバーライドされます。
controlFlowFlattening
型: boolean
デフォルト値: false
このオプションは実行速度への影響が大きく、最大1.5倍遅くなります。 controlFlowFlatteningThreshold
の項目で影響を受けるノードの割合を設定できます。
コード制御フローのフラット化を有効にします。ソースコードの構造を変換することにより、プログラムの流れを理解し辛くします。
例:
// input
(function(){
function foo () {
return function () {
var sum = 1 + 2;
console.log(1);
console.log(2);
console.log(3);
console.log(4);
console.log(5);
console.log(6);
}
}
foo()();
})();
// output
(function () {
function _0x3bfc5c() {
return function () {
var _0x3260a5 = {
'WtABe': '4|0|6|5|3|2|1',
'GokKo': function _0xf87260(_0x427a8e, _0x43354c) {
return _0x427a8e + _0x43354c;
}
};
var _0x1ad4d6 = _0x3260a5['WtABe']['split']('|'), _0x1a7b12 = 0x0;
while (!![]) {
switch (_0x1ad4d6[_0x1a7b12++]) {
case '0':
console['log'](0x1);
continue;
case '1':
console['log'](0x6);
continue;
case '2':
console['log'](0x5);
continue;
case '3':
console['log'](0x4);
continue;
case '4':
var _0x1f2f2f = _0x3260a5['GokKo'](0x1, 0x2);
continue;
case '5':
console['log'](0x3);
continue;
case '6':
console['log'](0x2);
continue;
}
break;
}
};
}
_0x3bfc5c()();
}());
controlFlowFlatteningThreshold
型: number
デフォルト値: 0.75
最小値: 0
最大値: 1
controlFlowFlattening
による変換がノードに適用される確率。
大量の制御フロー変換によりコードの速度が低下し、コードサイズが増加する可能性があるため、元のコードサイズが大きい場合にこの設定は特に役立ちます。
controlFlowFlatteningThreshold:0
はcontrolFlowFlattening:false
と同じです。
deadCodeInjection
型: boolean
デフォルト値: false
コードのサイズが劇的に増加します(最大200%)。コードのサイズより難読化の方が重要な場合のみ使用します。 deadCodeInjectionThreshold
の項目で影響を受けるノードの割合を設定できます。
このオプションは、 stringArray
オプションを強制的に有効にします。
到達不能なランダムブロックが追加されます。
例:
// input
(function(){
if (true) {
var foo = function () {
console.log('abc');
console.log('cde');
console.log('efg');
console.log('hij');
};
var bar = function () {
console.log('klm');
console.log('nop');
console.log('qrs');
};
var baz = function () {
console.log('tuv');
console.log('wxy');
console.log('z');
};
foo();
bar();
baz();
}
})();
// output
var _0x5024 = [
'zaU',
'log',
'tuv',
'wxy',
'abc',
'cde',
'efg',
'hij',
'QhG',
'TeI',
'klm',
'nop',
'qrs',
'bZd',
'HMx'
];
var _0x4502 = function (_0x1254b1, _0x583689) {
_0x1254b1 = _0x1254b1 - 0x0;
var _0x529b49 = _0x5024[_0x1254b1];
return _0x529b49;
};
(function () {
if (!![]) {
var _0x16c18d = function () {
if (_0x4502('0x0') !== _0x4502('0x0')) {
console[_0x4502('0x1')](_0x4502('0x2'));
console[_0x4502('0x1')](_0x4502('0x3'));
console[_0x4502('0x1')]('z');
} else {
console[_0x4502('0x1')](_0x4502('0x4'));
console[_0x4502('0x1')](_0x4502('0x5'));
console[_0x4502('0x1')](_0x4502('0x6'));
console[_0x4502('0x1')](_0x4502('0x7'));
}
};
var _0x1f7292 = function () {
if (_0x4502('0x8') === _0x4502('0x9')) {
console[_0x4502('0x1')](_0x4502('0xa'));
console[_0x4502('0x1')](_0x4502('0xb'));
console[_0x4502('0x1')](_0x4502('0xc'));
} else {
console[_0x4502('0x1')](_0x4502('0xa'));
console[_0x4502('0x1')](_0x4502('0xb'));
console[_0x4502('0x1')](_0x4502('0xc'));
}
};
var _0x33b212 = function () {
if (_0x4502('0xd') !== _0x4502('0xe')) {
console[_0x4502('0x1')](_0x4502('0x2'));
console[_0x4502('0x1')](_0x4502('0x3'));
console[_0x4502('0x1')]('z');
} else {
console[_0x4502('0x1')](_0x4502('0x4'));
console[_0x4502('0x1')](_0x4502('0x5'));
console[_0x4502('0x1')](_0x4502('0x6'));
console[_0x4502('0x1')](_0x4502('0x7'));
}
};
_0x16c18d();
_0x1f7292();
_0x33b212();
}
}());
deadCodeInjectionThreshold
型: number
デフォルト値: 0.4
最小値: 0
最大値: 1
deadCodeInjection
の影響を受けるノードの割合を設定できます。
debugProtection
型: boolean
デフォルト値: false
ブラウザの開発者ツールを開いた場合、ブラウザがフリーズします。
このオプションにより、WebKitベースのブラウザとMozilla Firefoxの両方で開発者ツールのコンソール
タブを使用することはほとんど不可能になります。
- WebKitベース:サイトウィンドウをブロックしますが、デベロッパーツールパネルはナビゲートできます。
- Firefox:サイトウィンドウをブロックしませんが、それでも開発者ツールを使用できません。
debugProtectionInterval
型: boolean
デフォルト値: false
ブラウザがフリーズします! 自己責任で使用してください。
開発者ツールを開いていると、定期的に[コンソール]タブのデバッグモードに切り替わるようになり、他の機能の使用が難しくなります。 debugProtection
が有効になっている場合に機能します。
disableConsoleOutput
型: boolean
デフォルト値: false
console.log
, console.info
, console.error
, console.warn
, console.debug
, console.exception
, console.trace
を空の関数に置き換えて使用できなくします。これによりデバッガーの使用が難しくなります。
domainLock
型: string[]
デフォルト値: []
特定のドメインおよび/またはサブドメインでのみ実行されるようにロックします。これは、ソースコードをコピーして貼り付け、他の場所で実行するだけの人にとっては非常に困難です。
複数のドメインとサブドメイン
複数のドメインまたはサブドメインにロックすることも可能です。 たとえば、コードが www.example.com でのみ実行されるようにするにはwww.example.com
を追加し、example.comのサブドメインで機能させるには、.example.com
を使用します。
exclude
型: string[]
デフォルト値: []
ファイル名またはグロブを設定すると難読化から除外できます。
identifierNamesGenerator
型: string
デフォルト値: hexadecimal
識別子名ジェネレータを設定します。
利用可能な値:
* hexadecimal
: 識別子名が_0xabc123
のようになります。
* mangled
: 識別子名がa
, b
, c
のようになります。
identifiersPrefix
型: string
デフォルト値: ''
すべてのグローバル識別子のプレフィックスを設定します。
複数のファイルを難読化する場合は、このオプションを使用します。 このオプションは、これらのファイルのグローバル識別子間の競合を回避するのに役立ちます。 プレフィックスはファイルごとに異なる必要があります。
inputFileName
型: string
デフォルト値: ''
入力ファイルの名前をソースコードで設定できます。 この名前は、ソースマップの生成に内部的に使用されます。
log
型: boolean
デフォルト値: false
開発者ツールのコンソールへのロギングを有効にします。
renameGlobals
型: boolean
デフォルト値: false
このオプションはコードを破壊する可能性があります。それが何をするか知っている場合にのみ有効にしてください!
グローバル変数および関数名の難読化を宣言付きで有効にします。
reservedNames
型: string[]
デフォルト値: []
識別子名が設定した正規表現と一致した場合、難読化と識別子の生成が無効になります。
例:
{
reservedNames: [
'^someVariable',
'functionParameter_\d'
]
}
reservedStrings
型: string[]
デフォルト値: []
文字列が設定した正規表現と一致した場合、変換が無効になります。
例:
{
reservedStrings: [
'react-native',
'\.\/src\/test',
'some-string_\d'
]
}
rotateStringArray
型: boolean
デフォルト値: true
stringArray
が有効である必要があります
固定されたランダムな(コードの難読化で生成された)場所で stringArray
配列をシフトします。 これにより、削除された文字列の順序を元の場所に一致させることが難しくなります。
元のソースコードが小さくない場合は、このオプションをお勧めします。ヘルパー関数が注目を集めることができるためです。
seed
型: number
デフォルト値: 0
乱数生成器のシードを設定します。これは、繰り返し可能な結果を作成するのに役立ちます。シードが0
の場合、乱数生成器はシードなしで機能します。
selfDefending
型: boolean
デフォルト値: false
このオプションで難読化した後は、難読化されたコードを変更しないでください。uglifyingは自己防衛を引き起こし、コードは機能しなくなります!
このオプションは強制的に compact
をtrue
に設定します
コード整形(フォーマット)および変数名変更に対して耐性のあるコードを出力します。出力されたコードに対してこれらを行うと機能しなくなるため、解析と変更が難しくなります。
sourceMap
型: boolean
デフォルト値: false
難読化されたコードのソースマップ生成を有効にします。
ソースマップは、難読化されたJavaScriptソースコードのデバッグに役立ちます。 本番環境でデバッグする場合、またはデバッグする必要がある場合は、別のソースマップファイルを秘密の場所にアップロードしてから、ブラウザをそこを参照することができます。
sourceMapBaseUrl
型: string
デフォルト値: ``
ソースマップのインポートURLの値を設定します。sourceMapMode: 'separate'
の場合のみ有効です。
CLI 例:
javascript-obfuscator input.js --output out.js --source-map true --source-map-base-url 'http://localhost:9000'
結果:
//# sourceMappingURL=http://localhost:9000/out.js.map
sourceMapFileName
型: string
デフォルト値: ``
出力されるソースマップのファイル名を設定します。sourceMapMode: 'separate'
の場合のみ有効です。
CLI 例:
javascript-obfuscator input.js --output out.js --source-map true --source-map-base-url 'http://localhost:9000' --source-map-file-name example
結果:
//# sourceMappingURL=http://localhost:9000/example.js.map
sourceMapMode
型: string
デフォルト値: separate
ソースマップの生成モードを指定します。:
* inline
- 個別のファイルを持つ代わりに、ソースマップを含む単一のファイルを出力します。;
* separate
- 対応する '.map'ファイルをソースマップとともに生成します。CLIで実行する場合、難読化されたコード //#sourceMappingUrl = file.js.map
を使用して、ソースマップファイルへのリンクをファイルの最後に追加します。
stringArray
型: boolean
デフォルト値: true
文字列リテラルを削除し特別な配列に置き換えます。たとえばvar m = "Hello World"
というようなコードでは"Hello World"
の部分が置き換えられ、var m = _0x12c456 [0x1];
のようになります。
stringArrayEncoding
型: boolean|string
デフォルト値: false
stringArray
オプションが有効になっている必要があります。
このオプションは、スクリプトの速度を低下させる可能性があります。
base64
またはrc4
を使用してstringArray
のすべての文字列リテラルをエンコードし、実行時にデコードするために使用される特別なコードを挿入します。
利用可能な値:
* true
(boolean
): base64
でstringArray
をエンコードします。
* false
(boolean
): stringArray
をエンコードしません。
* 'base64'
(string
): base64
でstringArray
をエンコードします。
* 'rc4'
(string
): rc4
でstringArray
をエンコードします。 base64
と比べ約30-50%遅くなりますが, 初期値を取得するのがより難しくなります。 この設定を使用する際はサイズが非常に大きくなるのを防ぐため、 unicodeEscapeSequence
は無効にすることをお勧めします。
stringArrayThreshold
型: number
デフォルト値: 0.8
最小値: 0
最大値: 1
stringArray
を有効にする必要があります。
文字列リテラルが stringArray
に挿入される確率(0〜1)を調整できます。
string array
を繰り返し呼び出し、コードの速度を低下させる可能性があるため、コードサイズが大きい場合にこの設定は特に役立ちます。
stringArrayThreshold: 0
はstringArray: false
と同じ結果になります.
target
型: string
デフォルト値: browser
難読化されたコードのターゲット環境を設定できます。
利用可能な値:
* browser
;
* browser-no-eval
;
* node
.
現在、browser
およびnode
ターゲットの出力コードは同じです。
browser-no-eval
ターゲットの出力コードはeval
を使用していません。
transformObjectKeys
型: boolean
デフォルト値: false
オブジェクトのキーの変換を有効にします。
例:
// input
(function(){
var object = {
foo: 'test1',
bar: {
baz: 'test2'
}
};
})();
// output
var _0x5a21 = [
'foo',
'test1',
'bar',
'baz',
'test2'
];
var _0x223f = function (_0x474dc0, _0x10db96) {
_0x474dc0 = _0x474dc0 - 0x0;
var _0x4c8bf7 = _0x5a21[_0x474dc0];
return _0x4c8bf7;
};
(function () {
var _0x2e1a8e = {};
_0x2e1a8e[_0x223f('0x0')] = _0x223f('0x1');
_0x2e1a8e[_0x223f('0x2')] = {};
_0x2e1a8e[_0x223f('0x2')][_0x223f('0x3')] = _0x223f('0x4');
}());
unicodeEscapeSequence
型: boolean
デフォルト値: false
Unicodeエスケープシーケンスへの文字列変換を有効/無効にすることができます。
Unicodeエスケープシーケンスによりコードサイズが大幅に増加し、文字列を簡単に元のビューに戻すことができます。 このオプションは、小さなソースコードに対してのみ有効にすることをお勧めします。
推奨オプション設定
高難読化, 低パフォーマンス
50%~100%パフォーマンスが遅くなります。
{
compact: true,
controlFlowFlattening: true,
controlFlowFlatteningThreshold: 1,
deadCodeInjection: true,
deadCodeInjectionThreshold: 1,
debugProtection: true,
debugProtectionInterval: true,
disableConsoleOutput: true,
identifierNamesGenerator: 'hexadecimal',
log: false,
renameGlobals: false,
rotateStringArray: true,
selfDefending: true,
stringArray: true,
stringArrayEncoding: 'rc4',
stringArrayThreshold: 1,
transformObjectKeys: true,
unicodeEscapeSequence: false
}
中難読化, 最適性能
30%~35%パフォーマンスが遅くなります。
{
compact: true,
controlFlowFlattening: true,
controlFlowFlatteningThreshold: 0.75,
deadCodeInjection: true,
deadCodeInjectionThreshold: 0.4,
debugProtection: false,
debugProtectionInterval: false,
disableConsoleOutput: true,
identifierNamesGenerator: 'hexadecimal',
log: false,
renameGlobals: false,
rotateStringArray: true,
selfDefending: true,
stringArray: true,
stringArrayEncoding: 'base64',
stringArrayThreshold: 0.75,
transformObjectKeys: true,
unicodeEscapeSequence: false
}
低難読化、高パフォーマンス
パフォーマンスはわずかに遅くなります。
{
compact: true,
controlFlowFlattening: false,
deadCodeInjection: false,
debugProtection: false,
debugProtectionInterval: false,
disableConsoleOutput: true,
identifierNamesGenerator: 'hexadecimal',
log: false,
renameGlobals: false,
rotateStringArray: true,
selfDefending: true,
stringArray: true,
stringArrayEncoding: false,
stringArrayThreshold: 0.75,
unicodeEscapeSequence: false
}
感想
ただの高性能難読化ツールだと思っていたので、ドメインロックや開発者ツールキラーまで実装されてて驚きました。あと自己防衛機能(selfDefending)は発想すらなくて感心しました。どんな実装してんだろ……(なお調べる気力はない)
プラグインとしてWebpack,Gulp,Grunt等色々対応してますし、オンライン版まであるので、完成間近/完成したプロダクトをお持ちの方は試してみてはいかがでしょうか。
ちなみに現在はES2019まで対応しています。当然のことながら最新構文の対応は若干遅れますので、ハマらないよう注意しましょう。特にElectronだと「Chromiumしか考えなくていいから最新構文使い放題だぜ~」となりがちなんで……(経験談)
おまけ:Webpackを通した使い方
プラグイン一覧の中にあるwebpack-obfucatorを使います。
このモジュールにはpluginとして使うか、loaderとして使うかの2種類の使い方があります。
自作モジュールと他人のモジュールを別々のjsファイルに纏めるならplugin、全部一つのjsファイルにまとめるならloaderがいいということのようです。複数バンドルに分けるような人はWebpackマスターでわざわざここ見る必要ないかと思うので、loaderを使っていきます。
さてwebpack.config.jsのloaderの項目に加えればいいのは想像つくかと思うのですが、問題はTypeScriptの場合です。この場合.tsのファイルにts-loaderとobfuscator-loaderを2つ通さないといけないのでWebpackに慣れてないと迷うかと思います。webpackのリファレンス見てもらうのが一番いいんですが、エイリアスやら省略記法多すぎて頭こんがらがってきたので、下記に一番無難そうな例を置いときます。
const webpackObfuscator = require('webpack-obfuscator');
module: {
rules: [
{
enforce: 'post',
test: /\.ts$/,
use: [
{
loader: webpackObfuscator.loader,
options: //オプションのオブジェクト
}
],
exclude: /node_modules/
},
{
test: /\.ts$/,
use: [
{ loader: 'ts-loader' }
],
exclude: /node_modules/
}
]
}
selfDefending設定のこともあり、基本的に一番最後に通すことが推奨されています。rulesの配列内の後ろから順に処理されるので、一番先頭に置いておくのもいいのですが、enforce:'post'
を設定することで最後に処理するよう強制することができます。
あと「ts-loader通した後の処理だから、testプロパティは.tsか.js、どっちを対象にすればいいのかな?」と迷うかと思いますが、ここは.tsのままで大丈夫です。
参考
JavaScript難読化ツールの紹介と比較
ウェブアプリをソースごとパクる業者に対する対策
JavaScript の便利な難読化ツール「JavaScript Obfuscator Tool」
Nuxt.js プロジェクトで JavaScript を難読化