標準アルゴリズム
Babel Obfuscator には、XOR、HASH、STREAM の 3 つの組み込み文字列暗号化アルゴリズムが付属しています。3 つとも自己完結型で、外部からの入力なしに復号します。そのため、どれを選ぶかは、読み込み時間、出力サイズ、文字列の復元の難しさのトレードオフになります。このページでは、各アルゴリズムとその有効化の方法を説明します。違いは、末尾の比較表にまとめています。
XOR アルゴリズム
Babel Obfuscator が文字列暗号化に使用する XOR アルゴリズムは、インラインの文字列の各文字とランダムな整数キーの XOR をとる単純なアルゴリズムです。ほかの暗号化アルゴリズムと比べて復号が速く、読み込み時間が短いという利点があります。
コマンドライン
コマンドラインから Babel Obfuscator でアプリケーションに XOR 暗号化アルゴリズムを設定するには、次の簡単なコマンドを使用します。
babel myapp.exe --string xorこのコマンドは、アプリケーションの実行可能ファイルに含まれるインラインの文字列に XOR 暗号化を適用するよう Babel に指示します。XOR アルゴリズムの単純さと速さを生かして、文字列を安全に難読化します。
MSBuild の Babel タスク
Babel の MSBuild タスクを使えば、プロジェクトの文字列暗号化に XOR アルゴリズムを簡単に設定できます。MSBuild プロジェクトファイルに、XOR 暗号化方式を指定したプロパティグループと Babel タスクを追加するだけです。
<PropertyGroup>
<StringEncryption>xor</StringEncryption>
</PropertyGroup>
<Babel StringEncryption="$(StringEncryption)" />この構成により、Babel Obfuscator はプロジェクト内の文字列に XOR 暗号化アルゴリズムを適用します。文字列のセキュリティが高まり、改ざんやリバースエンジニアリングに対する耐性が向上します。
HASH アルゴリズム
Babel Obfuscator が使用する HASH アルゴリズムは、整数キーで参照するハッシュテーブルに基づいています。このアルゴリズムは文字列データの圧縮と暗号化の両方を行うため、文字列データ全体のファイルサイズを小さくできます。ただし、圧縮された文字列データを実行時に復号する必要があるため、アプリケーションの読み込み時間に影響することがあります。インラインの文字列の各文字とランダムな整数キーの XOR をとる XOR アルゴリズムと比べると、HASH アルゴリズムは文字列を復号しようとする攻撃に対する保護が強い反面、読み込みは遅くなります。
コマンドライン
CLI から Babel Obfuscator で HASH 暗号化を設定するには、次のコマンドを使用します。
babel myapp.exe --string hashMSBuild の Babel タスク
Babel の MSBuild タスクで HASH 暗号化を有効にするには、StringEncryption プロパティを次のように設定します。
<PropertyGroup>
<StringEncryption>hash</StringEncryption>
</PropertyGroup>
<Babel StringEncryption="$(StringEncryption)" />このように HASH アルゴリズムを組み込むと、ビルド時にプロジェクトの文字列が適切に保護されます。
STREAM アルゴリズム Ultimate
STREAM アルゴリズムは、Babel Obfuscator が提供する、認証付きの新しい文字列暗号化方式です。業界標準の強力な暗号で文字列を 1 つずつ暗号化し、文字列を返す前に認証タグを検証します。これにより STREAM は、従来のアルゴリズムにはない 4 つの特性を備えています。
- 文字列ごとの遅延復号:文字列は、実際に初めて使用されたときにだけ復号され、その結果がキャッシュされます。HASH とは異なり、STREAM がすべての文字列を前もって復号することはありません。そのため、メモリダンプで露出するのは、アプリケーションがすでに使用したわずかな文字列だけです。すべての文字列が平文でメモリ上に存在する瞬間はありません。
- 整合性と文字列ごとのランダム化:認証タグは、暗号化されたデータに対するあらゆる改ざんを検出します。また、同一の 2 つの文字列は異なるバイト列に暗号化されるため、単純な XOR 方式では手がかりを与えてしまう頻度分析や一致分析が通用しません。
- インラインに格納されないキー:キーは、復号呼び出しの隣にリテラルとして置かれることがありません。そのため、自動難読化解除ツールは、呼び出しの横にあるキーを読み取るだけでは済まず、文字列を復元するために実際のコードを実行しなければなりません。
- コードの呼び出しコンテキストに紐付いた復号:復号ルーチンを実行するだけでは不十分です。復号ルーチンは、保護されたアセンブリ自体の内部で実行されている呼び出し元にだけ文字列を返します(.NET ランタイム内部の呼び出し元にも返すため、LINQ/PLINQ や非同期の経路は引き続き動作します)。自動難読化解除ツールは通常、デリゲートを復号ルーチンに直接バインドし、自身のヘルパーアセンブリから一括で呼び出すことで、暗号化された文字列をコマンド 1 つですべてダンプしますが、この方法は拒否されます。完全な動的解析のもとで文字列の復元が不可能になるわけではありませんが、手軽な一括抽出の経路がなくなり、攻撃者は実際のコードを動かさざるを得なくなります。
復号ルーチンはすべてマネージドコードで書かれており、プラットフォームの暗号化プロバイダーに依存しません。その結果、STREAM は .NET Framework 4.x(および 2.0)から .NET 10 まで、また Mono/Xamarin でもそのまま動作します。さらに、Reflection.Emit を使用しないため、NativeAOT およびトリミングされたアプリケーションでも動作します。プラットフォームに依存するアルゴリズムで起こりうる FIPS の起動時の問題の影響も受けません。
STREAM は自己完結型で、XOR や HASH と同じく、外部からの入力なしに復号します。ローカルだけで完結する方式であるため、完全な動的解析のもとで文字列の復元を不可能にすることはできません。これは自己完結型のどの方式にも当てはまります。それでも、自動抽出と手作業によるリバースエンジニアリングの両方のコストを、XOR や HASH よりも大幅に引き上げます。
プラットフォームとフレームワークのサポート
復号ルーチンは完全にマネージドコードで、Reflection.Emit、動的コード、スタックウォーク、プラットフォームの暗号化プロバイダーのいずれも使用しません。そのため STREAM は、.NET の全範囲とモバイルで検証されています。
- .NET Framework 2.0 / 4.x から .NET 10 まで、.NET Core、Mono / Xamarin。
- トリミングされた(ILLink)アプリケーションと NativeAOT アプリケーション:挿入された復号ルーチンと、そこに埋め込まれた文字列テーブルはリンク後も残り、コードは JIT なしで事前コンパイルされます。
- Android と iOS:Android エミュレーターと iOS シミュレーター上で、実際に難読化したアプリを使ってエンドツーエンドで検証済みです。完全にトリミングされた Release ビルドも含みます。文字列は実行時に復号され、パッケージ化されたアセンブリに平文は残りません。.NET MAUI はこれらと同じランタイム(
net*-android/net*-ios)の上に構築されているため、同じサポートが当てはまります。iOS デバイスの完全 AOT は、前述の NativeAOT との互換性でカバーされます。
モバイルでは、STREAM を文字列暗号化だけのパスとして適用し、Java / Objective-C の相互運用レイヤーのために型名とメンバー名をそのまま残します。
babel MyApp.dll --stringencryption stream --notypes --nomethods --nofields --noproperties --noevents --noparameters --novirtualmembers.NET Android / iOS / MAUI のビルド内で Babel を MSBuild のステップとして実行する場合は、アプリのアセンブリのコンパイル後、パッケージ化の前に組み込みます。これにより、パッケージ化されたアプリに難読化されたアセンブリが含まれます。
コマンドライン
コマンドラインから STREAM 暗号化を適用するには、次のようにします。
babel myapp.exe --string streamMSBuild の Babel タスク
Babel の MSBuild タスクで STREAM 暗号化を有効にするには、StringEncryption プロパティを次のように設定します。
<PropertyGroup>
<StringEncryption>stream</StringEncryption>
</PropertyGroup>
<Babel StringEncryption="$(StringEncryption)" />STREAM はほかの保護と併用できます。同じアセンブリで、コード(MSIL)暗号化や改ざん防止と組み合わせられます。
改ざん防止とビルド後の書き換え:改ざん防止は、難読化されたイメージの整合性ハッシュを埋め込みます。Babel の後でアセンブリを書き換える処理、とりわけマネージドアセンブリを出力し直すトリミングまたは NativeAOT での公開は、そのバイト列を変更するため、起動時にチェックが即座に失敗します。これは改ざん防止の性質によるもので、文字列アルゴリズムとは無関係です(XOR、HASH、STREAM のいずれにも同じように影響します)。改ざん防止は、その後トリミングや AOT による書き換えを行わないビルドで有効にし、トリミングまたは AOT の出力では文字列暗号化(とコード暗号化)で保護してください。
アルゴリズムの比較
3 つの組み込みアルゴリズムはすべて自己完結型で、外部からの入力なしに復号します。そのため、どれを選ぶかは、読み込み時間、出力サイズ、文字列の復元の難しさのトレードオフになります。
| XOR | HASH | STREAM | |
|---|---|---|---|
| 暗号 | 文字列ごとの整数キーによるインラインの XOR | プラットフォームの暗号で暗号化された圧縮テーブル | 文字列ごとの、認証付きの業界標準暗号 |
| 文字列をリテラルとして保持 | はい(その場でスクランブル) | いいえ | いいえ |
| 復号モデル | 文字列ごと、アクセスのたびに復号 | 一括:最初の使用時にテーブル全体を復号 | 遅延:各文字列を最初の使用時に復号し、その後はキャッシュ |
| メモリ上に保持される平文 | 一度に 1 つの文字列 | テーブル全体を、実行中ずっと | 実際に使用された文字列のみ |
| 文字列ごとの整合性タグ | — | — | はい |
| 同一の文字列が異なる結果に暗号化される | いいえ | いいえ | はい |
| コンテキスト外(デリゲート呼び出し)からの復号を拒否 | — | — | はい |
| 復号キー | インライン、呼び出しの隣 | BLOB のヘッダーに格納 | インラインには置かれず、実行時に再構築され、制御フローに織り込まれる |
| 完全にマネージドな復号ルーチン(プラットフォームの暗号化プロバイダーなし、FIPS でも安全) | いいえ | いいえ | はい |
| 相対的な出力サイズ | 最大(リテラルがアセンブリに残る) | 最小(単一の圧縮 BLOB) | XOR と HASH の中間 |
| 相対的な実行時コスト | 最小 | 小さいが、すべての文字列の分を最初に負担 | 文字列あたりでは大きいが、使用された文字列の分のみ負担 |
どれを選ぶか
- XOR:最も高速で軽量ですが、保護は最も弱くなります。暗号化された文字列はリテラルとしてアセンブリに残り、文字列ごとのキーがすべての呼び出しの隣に置かれるため、自動ツールなら 1 回のパスで復元できます。保護よりも読み込み時間が重要な場合に選択してください。
- HASH:リテラルを取り除き、すべての文字列を、圧縮して暗号化した 1 つの BLOB にまとめます(出力は最小)。テーブルには改ざんに対する保護があります。その代わり、最初の使用時にテーブル全体が一括で復号されるため、以降は実行が終わるまですべての文字列が平文でメモリ上に残ります。また、復号ルーチンはプラットフォームの暗号化プロバイダーに依存します(FIPS に関する注を参照してください)。
- STREAM:静的解析と動的解析の両方に対して、3 つの中で最も強力です。文字列ごとの認証付き暗号化を行い、全体の一括復号もインラインのキーもありません。完全にマネージドな単一の復号ルーチンは、NativeAOT やトリミングでの公開でもそのまま動作し、FIPS の影響も受けません。文字列あたりの復号コストは高くなりますが、負担するのはアプリケーションが実際に使用する文字列の分だけで、すべての文字列が平文で存在する瞬間もありません。最新の .NET、AOT やトリミングを使う配布、FIPS 環境へのデプロイ、そして文字列の一括抽出への耐性が特に重要な場面では、STREAM を選択してください。