文字列暗号化と値の暗号化による機密情報の保護
ほとんどすべてのアプリケーションは、コードの中に機密情報を含んだまま配布されています。API キー、エンドポイント、接続文字列、ライセンスのパラメーター、「マジック」ナンバー、ルックアップテーブルなどです。デコンパイラーを使えば、数秒で読み取れます。このサンプルでは、Babel Obfuscator の NuGet パッケージを使い、コードを 1 行も変更せずに、文字列暗号化と値と配列の暗号化でこれらのリテラルを配布するアセンブリから取り除きます。
このサンプルを使用するには、Babel Obfuscator のサイトライセンス(Ultimate、Server、Data Center のいずれかのエディション)が必要です。ソースコードは GitHub で公開しています。
git clone https://github.com/babelfornet/secret-protection-nuget-example.gitSecretApp コンソールプロジェクトは、Babel Obfuscator の NuGet パッケージを(ビルド専用で)参照し、Release で 2 つの暗号化レイヤーを有効にします。
<ItemGroup Condition="'$(Configuration)' == 'Release'">
<PackageReference Include="Babel.Obfuscator" Version="12.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<StringEncryption>stream</StringEncryption>
<ValueEncryption>int32=true;int64=true;single=true;double=true;array=true;true</ValueEncryption>
<ControlFlowObfuscation>if=true;switch=true;case=true;chain=true;true</ControlFlowObfuscation>
<BabelWarningsAsErrors>W00000</BabelWarningsAsErrors>
</PropertyGroup>文字列アルゴリズム stream は、すべての文字列リテラルを暗号化されたテーブルに移します。一方、値と配列の暗号化は、数値定数とインラインの配列を IL から取り除きます。どちらも実行時に必要に応じて復号されるため、プログラムの動作はこれまでとまったく変わらず、定数だけがバイナリから消えます。BabelWarningsAsErrors を W00000 に設定すると、評価モードのビルドはエラーになるため、保護されていないバイナリを誤って配布することはありません。
機密情報そのものは src/SecretApp/Secrets.cs にあります。
internal static class Secrets
{
// Strings -> removed by String Encryption (stream)
public static readonly string ApiKey = "DEMO-API-KEY-0000-1111-2222-3333-4444-5555";
public static readonly string ServiceEndpoint = "https://api.internal.example.com/v3/ingest";
public static readonly string ConnectionString = "Server=db.internal;Database=Orders;User Id=svc;Password=__DEMO_PLACEHOLDER__;";
// Numbers -> removed by Value Encryption
public static readonly int ApiPort = 8443;
public static readonly double RiskThreshold = 0.8734;
// Lookup table -> removed by Array Encryption
public static readonly int[] RolloutBuckets = { 12, 47, 63, 88, 91, 128, /* … */ 8192, 9001 };
}このサンプルは、重要な点も 1 つ示しています。機密情報は const ではなく static readonly として宣言されています。const の値はアセンブリのメタデータに埋め込まれ、使用箇所ごとにインライン展開されるため、難読化後も残り、読み取れる状態のままです。static readonly フィールドには ldstr/ldc 命令で値が代入され、文字列暗号化と値の暗号化がこの命令を書き換えます。サンプルを Release でビルドして 2 つのアセンブリを検索すると、この違いを確認できます。API キー、エンドポイント、接続文字列は Debug ビルドには存在しますが、Release ビルドからは消えており、プログラムの出力は変わりません。