ライセンスに紐付けたコード暗号化
ライセンスに紐付けたコード暗号化は、パスワードで保護されたコードの一種です。暗号化されたメソッドのロックを解除するパスワードは、ビルドに埋め込まれた固定の定数ではなく、実行時に顧客のライセンスから読み取られる値です。
保護されたメソッドは難読化されたアセンブリに含めて配布されますが、有効なライセンスが正しいパスワードを提供するまで、そのコードは暗号化されたままです。つまり、機密性の高い機能やプレミアム機能は、ライセンスで許可されたユーザーに対してだけ動作し、アプリケーションのそれ以外の部分は、ほかのすべてのユーザーに対しても通常どおり動作します。
これは、Babel Obfuscator 自身が、ライセンスの必要な機能を制限するために内部で使用しているパターンです。すでにご存じの通常のコード暗号化の上に成り立つもので、新しくインストールするものはなく、ルールと小さなフックを書くだけです。
仕組み
- 保護するコードをマークする:XML ルール(または
Obfuscation属性)で、暗号化するメソッドや型を選択し、ソース名とパスワードを割り当てます。暗号化されたコードはアセンブリ内に保持します(Internal = true)。 - パスワード取得フックを用意する:
[Obfuscation(Feature = "msil encryption get password")]でマークしたメソッドがソース名を受け取り、読み込まれたライセンスのフィールドを読み取って、そのソースのパスワードを返します。 - 実行する:保護されたメソッドが最初に呼び出されると、Babel Virtual Machine(BVM)がフックを呼び出して、そのソースのパスワードを取得します。ライセンスに想定どおりのフィールドがあれば、メソッドは復号されて実行されます。フィールドがない場合や値が正しくない場合は、復号に失敗してメソッドを実行できないため、その機能は事実上ロックされます。
ライセンスに紐付けられるのは、<Source> を持つルールに一致したメソッドだけです。単なる --msilencryption(ソースなし)で暗号化されたメソッドは、アセンブリ自体からロックが解除され、ライセンスがなくても動作し続けます。そのため、ライセンスのないアプリケーションも引き続き動作し、利用できなくなるのはライセンスで制限された機能だけです。
コードの暗号化
機密性の高いコードを暗号化し、名前付きのソースとパスワードに紐付けるルールを追加します。
<Rule name="encrypt premium" feature="msil encryption" exclude="false">
<Target>Methods</Target>
<Pattern>ACME.Premium::*</Pattern>
<Properties>
<Source>premium</Source>
<Password>Xa9!k2$LpQ7r</Password>
<Internal>true</Internal>
</Properties>
<Description>Encrypt the premium feature, unlocked by the license.</Description>
</Rule>同じことを、Obfuscation 属性を使ってインラインで記述することもできます。
[Obfuscation(Feature = "msil encryption:internal=true;source=premium;password=Xa9!k2$LpQ7r", Exclude = false)]
internal class Premium
{
public string Run() => "premium result";
}ビルド時に設定する <Password> は、実行時にパスワード取得フックがそのソースに対して返す値と一致している必要があります。ライセンスに紐付けた設計では、フックはその値をライセンスから読み取るため、顧客に発行するライセンスのフィールドに同じ値を格納してください。
ライセンスからのパスワードの読み取り
ソース名を、ライセンスに保持されているパスワードに対応付けるフックを 1 つ宣言します。次の例では、Babel Licensing を使って、ライセンスされた機能を検索し、その値を返します。
[Obfuscation(Feature = "msil encryption get password")]
internal static string GetSourcePassword(string source)
{
var license = LicenseManager.Instance.License;
var feature = license.Features.FirstOrDefault(f => f.Name == source);
if (feature == null)
throw new ApplicationException($"feature '{source}' is not licensed");
return Encoding.UTF8.GetString(feature.Data);
}実行時に、BVM は GetSourcePassword("premium") を呼び出します。フックは顧客のライセンスに格納されたパスワードを返し、プレミアム機能のメソッドが復号されて実行されます。
フックは Babel が挿入するコードから到達できる必要があるため、public または internal として宣言してください。private または protected のままにした場合、Babel は難読化の際に自動的に internal に広げるので(public になることはありません)、挿入されたコードから呼び出せます。
LicenseManager クラスや、エディションごとのライセンスを発行する方法を含む、実行可能な完全な実装については、機能ベースのライセンスのサンプルを参照してください。
あらゆるライセンスの配布方法に対応
フックは読み込まれたライセンスオブジェクトから読み取るだけなので、ライセンスに紐付けたコード暗号化は、そのライセンスがどのようにアプリケーションに届くかには依存しません。Babel Licensing のすべての配布方法で、そのまま動作します。
- ライセンスファイル:クライアントに配布またはダウンロードされる、署名されたライセンスファイル。
- フローティングライセンス:Babel Licensing Service から貸し出されるシート。
- ライセンスアクティベーション:アクティベートしてコンピューターに紐付けたライセンス。
どのモードを選択しても、ライセンス処理のコードがライセンスを検証して読み込めば、パスワード取得フックは必要なフィールドを読み取ることができ、保護された機能のロックが解除されます。
暗号化されたコードをメインアセンブリから完全に切り離したい場合は、このパターンを外部コードファイルと組み合わせて、暗号化されたメソッドをライセンスと一緒に配布してください。