November 2, 2026
Stop Treating Secrets Like Plain Text: Protect .NET String Literals
Most security guides talk about authentication, encryption-at-rest, and TLS. All important.
By Skater .NET Obfuscator
2 min read
Stop Treating Secrets Like Plain Text: Protect .NET String Literals
But there's a simpler problem hiding in plain sight:
In .NET, the most sensitive information often lives in string literals β and those strings are sitting inside the assembly in readable form.
This turns decompilation into a scavenger hunt: attackers don't need to understand your architecture. They just need to search for recognizable text.
So let's reframe the problem and solve it systematically.
Step 0: Recognize the threat model (it's not theoretical)
Attackers use .NET decompilers because they're fast and effective.
- Load your assembly in ILSpy/dnSpy
- Search for a known substring
- Instantly discover:
- API base URLs
- database hostnames and credential fields
- license validation endpoints
- "landmark" errors like "License validation failedβ¦"
- even hardcoded crypto-related values
That's the real danger: plaintext strings provide both direct secrets and map markers for finding the logic they want to tamper with.
The core idea: remove plaintext from the managed assembly
A robust mitigation follows this principle:
Sensitive strings should not appear as plaintext in IL that a decompiler can reconstruct.
That leads to two distinct strategies.
Strategy A: keep strings in the managed assembly, but make them unreadable
In-assembly encryption replaces the string literal with an encrypted representation.
At runtime, a decryption routine reconstructs the original value in memory when needed.
Why teams like this
- No extra native dependencies
- Works well for:
- error messages
- UI and logs
- low-to-medium sensitivity strings
- Deployment stays simpler than native extraction
Why it's not "perfect"
The decryption routine is still part of the managed binary. The strings are not plaintext β but an analyst can still locate decryption behavior and observe values in memory.
For many teams, that's an acceptable security upgrade.
Strategy B: move the strings out of the managed binary entirely (strongest static protection)
If the string is high-value β connection strings, API keys, license endpoints β you want higher resistance.
Native DLL storage does exactly that:
- the app extracts the selected strings
- compiles them into a native binary (e.g., a C++ DLL)
- rewrites the .NET assembly to fetch values via P/Invoke
The key security property
The sensitive strings are absent from the managed assembly, which means:
- decompilers can't display them
- static searching can't find them
Instead, the value retrieval is routed through a native dependency that's far harder for .NET-oriented tools to inspect.
"Which should I choose?" Use a risk-based decision rule
A practical decision rule:
- If leaking the string gives attackers credentials or direct control β native DLL storage
- If leaking the string mainly reveals internal behavior β in-assembly encryption
Typical mapping:
- Database connection strings β native DLL storage
- API endpoints/keys β native DLL storage
- License validation URLs β native DLL storage
- Error messages and internal log templates β in-assembly encryption
Don't stop at obfuscation β prove it
After you apply protection:
- Static verification
- open the obfuscated assembly in ILSpy
- search for the original plaintext values
- in-assembly encryption β should look like ciphertext/encoded bytes
- native DLL storage β should return no matches in managed IL
- Runtime verification
- run the app through every code path that uses those strings:
- API calls succeed
- DB connections open
- license checks validate
- Deployment verification (for native DLL storage)
- ensure the native DLL ships beside the app
- test a "missing DLL" scenario if your pipeline allows it β your app should fail predictably rather than silently.
One more thing: selective protection beats blanket protection
Encrypting every string everywhere can create unnecessary complexity and performance overhead.
A better workflow:
- target strings by type and sensitivity
- protect only what matters:
- credentials, endpoints, cryptographic secrets
- leave low-value strings alone or protect them with the lighter approach
Summary
Plaintext string literals are one of the easiest data exposures in a .NET application.
To fix it, remove plaintext from the managed assembly using:
- In-assembly encryption for general protection and lower-risk strings
- Native DLL storage for the highest-value secrets where decompiler exposure must be eliminated
If you want to make attackers work harder than "Ctrl+F," protect your strings like secrets β not like harmless text.