Protecting Secrets with String and Value Encryption
Almost every application ships secrets in its code — API keys, endpoints, connection strings, license parameters, “magic” numbers and lookup tables. A decompiler reads them in seconds. This example uses the Babel Obfuscator NuGet package to remove those literals from the shipped assembly with String Encryption and Value and Array Encryption, without changing a line of code.
To use this example you need a site license for Babel Obfuscator (Ultimate, Server or Data Center editions). The source code is available on GitHub:
git clone https://github.com/babelfornet/secret-protection-nuget-example.gitThe SecretApp console project references the Babel Obfuscator NuGet package (build-only) and
turns on the two encryption layers in Release:
<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>The stream string algorithm
moves every string literal into an encrypted table, while
Value and Array Encryption removes numeric constants and
inline arrays from the IL. Both are decrypted on demand at runtime, so the program behaves exactly
as before while the constants disappear from the binary. Setting BabelWarningsAsErrors to W00000
turns an evaluation-mode build into an error, so an unprotected binary can never ship by accident.
The secrets themselves live 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 };
}The example also highlights an important detail: the secrets are declared static readonly, not
const. A const value is baked into the assembly metadata and inlined at every use site, so it
survives obfuscation and stays readable; a static readonly field is assigned with an ldstr/ldc
instruction that String and Value encryption rewrite. Building the sample in Release and searching
the two assemblies confirms the difference — the API key, endpoint and connection string are present
in the Debug build and gone from the Release one, with identical program output.