難読化ライブラリ「JavaScript Obfuscator」オプション一覧


# はじめに
JavaScriptの難読化で調べると最有力として挙がる[「JavaScript Obfuscator」](https://github.com/javascript-obfuscator/javascript-obfuscator)。オプションの項目を眺めていると、難読化以外にも色々機能が実装されていて面白かったので、README.mdのオプション項目の日本語訳という形で紹介します。~~シンプルな英語だし訳いらんというのは(ry~~

## JavaScript Obfuscator オプション
JS Obfuscatorでは次のオプションを使用できます。

### options:

```javascript
{
    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:
```sh
    -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`

:warning: **このオプションは実行速度への影響が大きく、最大1.5倍遅くなります。  [`controlFlowFlatteningThreshold`](#controlflowflatteningthreshold) の項目で影響を受けるノードの割合を設定できます。**

コード制御フローのフラット化を有効にします。ソースコードの構造を変換することにより、プログラムの流れを理解し辛くします。

例:

```ts
// 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`](#controlflowflattening) による変換がノードに適用される確率。
大量の制御フロー変換によりコードの速度が低下し、コードサイズが増加する可能性があるため、元のコードサイズが大きい場合にこの設定は特に役立ちます。
`controlFlowFlatteningThreshold：0`は`controlFlowFlattening：false`と同じです。



### `deadCodeInjection`
型: `boolean` デフォルト値: `false`

:warning: **コードのサイズが劇的に増加します（最大200％）。コードのサイズより難読化の方が重要な場合のみ使用します。 [`deadCodeInjectionThreshold`](#deadcodeinjectionthreshold) の項目で影響を受けるノードの割合を設定できます。**
:warning: **このオプションは、 `stringArray`オプションを強制的に有効にします。**

到達不能なランダムブロックが追加されます。

例:

```ts
// 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`

:warning: **ブラウザの開発者ツールを開いた場合、ブラウザがフリーズします。**

このオプションにより、WebKitベースのブラウザとMozilla Firefoxの両方で開発者ツールの`コンソール`タブを使用することはほとんど不可能になります。

* WebKitベース：サイトウィンドウをブロックしますが、デベロッパーツールパネルはナビゲートできます。
* Firefox：サイトウィンドウをブロックしませんが、それでも開発者ツールを使用できません。

### `debugProtectionInterval`
型: `boolean` デフォルト値: `false`

:warning: **ブラウザがフリーズします！ 自己責任で使用してください。**

開発者ツールを開いていると、定期的に[コンソール]タブのデバッグモードに切り替わるようになり、他の機能の使用が難しくなります。 [`debugProtection`](#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`

:warning: **このオプションはコードを破壊する可能性があります。それが何をするか知っている場合にのみ有効にしてください！**

グローバル変数および関数名の難読化を**宣言付き**で有効にします。

### `reservedNames`
型: `string[]` デフォルト値: `[]`

識別子名が設定した正規表現と一致した場合、難読化と識別子の生成が無効になります。

例:

```ts
	{
		reservedNames: [
			'^someVariable',
			'functionParameter_\d'
		]
	}
```

### `reservedStrings`
型: `string[]` デフォルト値: `[]`

文字列が設定した正規表現と一致した場合、変換が無効になります。

例:

```ts
	{
		reservedStrings: [
			'react-native',
			'\.\/src\/test',
			'some-string_\d'
		]
	}
```

### `rotateStringArray`
型: `boolean` デフォルト値: `true`

:warning: **[`stringArray`](#stringarray)が有効である必要があります**

固定されたランダムな（コードの難読化で生成された）場所で `stringArray`配列をシフトします。 これにより、削除された文字列の順序を元の場所に一致させることが難しくなります。

元のソースコードが小さくない場合は、このオプションをお勧めします。ヘルパー関数が注目を集めることができるためです。

### `seed`
型: `number` デフォルト値: `0`

乱数生成器のシードを設定します。これは、繰り返し可能な結果を作成するのに役立ちます。シードが`0`の場合、乱数生成器はシードなしで機能します。

### `selfDefending`
型: `boolean` デフォルト値: `false`

:warning: **このオプションで難読化した後は、難読化されたコードを変更しないでください。uglifyingは自己防衛を引き起こし、コードは機能しなくなります！**
:warning: **このオプションは強制的に `compact`を` true`に設定します**

コード整形（フォーマット）および変数名変更に対して耐性のあるコードを出力します。出力されたコードに対してこれらを行うと機能しなくなるため、解析と変更が難しくなります。

### `sourceMap`
型: `boolean` デフォルト値: `false`

難読化されたコードのソースマップ生成を有効にします。
ソースマップは、難読化されたJavaScriptソースコードのデバッグに役立ちます。 本番環境でデバッグする場合、またはデバッグする必要がある場合は、別のソースマップファイルを秘密の場所にアップロードしてから、ブラウザをそこを参照することができます。

### `sourceMapBaseUrl`
型: `string` デフォルト値: ``

ソースマップのインポートURLの値を設定します。[`sourceMapMode: 'separate'`](#sourcemapmode)の場合のみ有効です。
 
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`

:warning: **`stringArray`オプションが有効になっている必要があります。**

このオプションは、スクリプトの速度を低下させる可能性があります。
`base64`または`rc4`を使用して[`stringArray`](#stringarray)のすべての文字列リテラルをエンコードし、実行時にデコードするために使用される特別なコードを挿入します。

利用可能な値:
* `true` (`boolean`): `base64`で`stringArray`をエンコードします。
* `false` (`boolean`): `stringArray`をエンコードしません。
* `'base64'` (`string`): `base64`で`stringArray`をエンコードします。
* `'rc4'` (`string`): `rc4`で`stringArray`をエンコードします。 **`base64`と比べ約30-50%遅くなりますが, 初期値を取得するのがより難しくなります。** この設定を使用する際はサイズが非常に大きくなるのを防ぐため、 [`unicodeEscapeSequence`](#unicodeescapesequence)は無効にすることをお勧めします。
    
### `stringArrayThreshold`
型: `number` デフォルト値: `0.8` 最小値: `0` 最大値: `1`

:warning: **[`stringArray`](#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`

オブジェクトのキーの変換を有効にします。

例:

```ts
// 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％パフォーマンスが遅くなります。

```javascript
{
    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％パフォーマンスが遅くなります。

```javascript
{
    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
}
```

### 低難読化、高パフォーマンス

パフォーマンスはわずかに遅くなります。

```javascript
{
    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等色々対応してますし、[オンライン版](https://obfuscator.io/)まであるので、完成間近／完成したプロダクトをお持ちの方は試してみてはいかがでしょうか。
ちなみに現在は**[ES2019まで対応](https://github.com/javascript-obfuscator/javascript-obfuscator#what-javascript-versions-are-supported)**しています。当然のことながら最新構文の対応は若干遅れますので、ハマらないよう注意しましょう。特にElectronだと「Chromiumしか考えなくていいから最新構文使い放題だぜ～:v:」となりがちなんで……（経験談）

# おまけ：Webpackを通した使い方
プラグイン一覧の中にある[**webpack-obfucator**](https://github.com/javascript-obfuscator/webpack-obfuscator)を使います。
このモジュールにはpluginとして使うか、loaderとして使うかの2種類の使い方があります。
[自作モジュールと他人のモジュールを別々のjsファイルに纏めるならplugin、全部一つのjsファイルにまとめるならloaderがいい](https://github.com/javascript-obfuscator/obfuscator-loader/blob/master/README.md#why-not-a-plugin)ということのようです。複数バンドルに分けるような人はWebpackマスターでわざわざここ見る必要ないかと思うので、loaderを使っていきます。
さてwebpack.config.jsのloaderの項目に加えればいいのは想像つくかと思うのですが、**問題はTypeScriptの場合です。**この場合.tsのファイルにts-loaderとobfuscator-loaderを2つ通さないといけないのでWebpackに慣れてないと迷うかと思います。[webpackのリファレンス](https://webpack.js.org/concepts/loaders/)見てもらうのが一番いいんですが、エイリアスやら省略記法多すぎて頭こんがらがってきたので、下記に一番無難そうな例を置いときます。

```js
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](#selfdefending)設定のこともあり、基本的に一番最後に通すことが推奨されています。rulesの配列内の**後ろから順に処理される**ので、一番先頭に置いておくのもいいのですが、``enforce:'post'``を設定することで最後に処理するよう強制することができます。
あと「ts-loader通した後の処理だから、testプロパティは.tsか.js、どっちを対象にすればいいのかな？」と迷うかと思いますが、ここは.tsのままで大丈夫です。


# 参考
[JavaScript難読化ツールの紹介と比較](https://kagasu.hatenablog.com/entry/2017/03/30/194024)
[ウェブアプリをソースごとパクる業者に対する対策](https://qiita.com/kacchan6/items/d8576ab6b3c16cf670ca)
[JavaScript の便利な難読化ツール「JavaScript Obfuscator Tool」](https://developer.cybozu.io/hc/ja/community/posts/115010139426-JavaScript-の便利な難読化ツール-JavaScript-Obfuscator-Tool-)
[Nuxt.js プロジェクトで JavaScript を難読化](https://hi120ki.github.io/blog/posts/20190207/)