Protéger les secrets avec le chiffrement des chaînes et des valeurs
Presque toutes les applications sont livrées avec des secrets dans leur code : clés API, points de terminaison, chaînes de connexion, paramètres de licence, nombres « magiques » et tables de correspondance. Un décompilateur les lit en quelques secondes. Cet exemple utilise le package NuGet Babel Obfuscator pour retirer ces littéraux de l’assembly livré grâce au chiffrement des chaînes et au chiffrement des valeurs et des tableaux, sans modifier une seule ligne de code.
Pour utiliser cet exemple, vous avez besoin d’une licence de site pour Babel Obfuscator (édition Ultimate, Server ou Data Center). Le code source est disponible sur GitHub :
git clone https://github.com/babelfornet/secret-protection-nuget-example.gitLe projet console SecretApp référence le package NuGet Babel Obfuscator (utilisé uniquement à la
génération) et active les deux couches de chiffrement en 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>L’algorithme de chaînes stream
déplace chaque littéral de chaîne dans une table chiffrée, tandis que le
chiffrement des valeurs et des tableaux retire de l’IL
les constantes numériques et les tableaux en ligne. Dans les deux cas, le déchiffrement a lieu à la
demande, à l’exécution : le programme se comporte exactement comme avant, alors que les constantes
disparaissent du binaire. Régler BabelWarningsAsErrors sur W00000 transforme en erreur un build
en mode d’évaluation, de sorte qu’un binaire non protégé ne puisse jamais être livré par accident.
Les secrets eux-mêmes se trouvent dans 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 };
}L’exemple met aussi en évidence un détail important : les secrets sont déclarés static readonly,
et non const. Une valeur const est inscrite dans les métadonnées de l’assembly et recopiée à
chaque endroit où elle est utilisée ; elle survit donc à l’obfuscation et reste lisible. Un champ
static readonly, lui, est affecté par une instruction ldstr/ldc que le chiffrement des chaînes
et des valeurs réécrit. Générer l’exemple en Release et rechercher dans les deux assemblies confirme
la différence : la clé API, le point de terminaison et la chaîne de connexion sont présents dans le
build Debug et absents du build Release, la sortie du programme étant identique.