Geheimnisse mit Zeichenfolgen- und Wertverschlüsselung schützen
Fast jede Anwendung liefert in ihrem Code Geheimnisse aus: API-Schlüssel, Endpunkte, Verbindungszeichenfolgen, Lizenzparameter, „magische“ Zahlen und Nachschlagetabellen. Ein Decompiler liest sie in Sekunden. Dieses Beispiel entfernt mit dem NuGet-Paket von Babel Obfuscator diese Literale aus der ausgelieferten Assembly, und zwar mit Zeichenfolgenverschlüsselung und Wert- und Arrayverschlüsselung, ohne eine Zeile Code zu ändern.
Für dieses Beispiel brauchen Sie eine Standortlizenz für Babel Obfuscator (Edition Ultimate, Server oder Data Center). Der Quellcode ist auf GitHub verfügbar:
git clone https://github.com/babelfornet/secret-protection-nuget-example.gitDas Konsolenprojekt SecretApp referenziert das NuGet-Paket von Babel Obfuscator (nur für den Build)
und schaltet in Release die beiden Verschlüsselungsebenen ein:
<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>Der Zeichenfolgenalgorithmus stream
verschiebt jedes Zeichenfolgenliteral in eine verschlüsselte Tabelle, während die
Wert- und Arrayverschlüsselung numerische Konstanten und
Inline-Arrays aus dem IL-Code entfernt. Beides wird zur Laufzeit bei Bedarf entschlüsselt. Das
Programm verhält sich also genau wie zuvor, während die Konstanten aus der Binärdatei verschwinden.
Wird BabelWarningsAsErrors auf W00000 gesetzt, wird ein Build im Evaluierungsmodus zum Fehler,
sodass eine ungeschützte Binärdatei nie versehentlich ausgeliefert wird.
Die Geheimnisse selbst stehen in 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 };
}Das Beispiel zeigt außerdem ein wichtiges Detail: Die Geheimnisse sind als static readonly
deklariert, nicht als const. Ein const-Wert wird in die Metadaten der Assembly geschrieben und
an jeder Verwendungsstelle inline eingesetzt. Er übersteht daher die Verschleierung und bleibt lesbar.
Ein static readonly-Feld wird dagegen mit einer Anweisung ldstr/ldc zugewiesen, die von der
Zeichenfolgen- und Wertverschlüsselung umgeschrieben wird. Ein Build des Beispiels in Release und eine
Suche in den beiden Assemblys bestätigen den Unterschied: API-Schlüssel, Endpunkt und
Verbindungszeichenfolge sind im Debug-Build vorhanden und im Release-Build verschwunden, bei
identischer Programmausgabe.