Skip to Content
New release 12 available 🎉
ObfuscatorNuGet PackageSecret Protection

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.git

The 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.

Last updated on