From a recent conversation with a technical user, a common misconception is that if you store a string (text) inside the program, once it is compiled it is “encrypted”. Or more so, their point was, once it has been compiled, it is almost impossible to get that string out of the binary; therefore, it was safe to hard code the secrets into the application.
I think it is fairly clear that holding secrets in a plain text file is bad. I mean, we can easily read this file below.
{
"database": {
"username": "root",
"password": "super_secret_password",
"host": "postgres.example.com",
"port": 5432
},
"api": {
"key": "api_key_1234567890",
"endpoint": "https://api.example.com"
},
"serviceAccount": {
"clientId": "client_id",
"clientSecret": "client_secret"
}
}JSONBut what if this same set of information was stashed in a compiled binary?
Why Compiling the Secrets is Bad
Here’s a C# class: Config.cs on GitHub. The repo contains both the source code and compiled binaries, including Intermediate Language (IL) and Ahead Of Time (AOT) versions. For this discussion, we’ll focus on the IL version but note that AOT can also be explored with tools like Ghidra.
To follow along, you can download just the .exe and .dll from here: decompile-security/decompile-security/Compiled/IL at main · skywalkerisnull/decompile-security
Decompiling the IL
After the IL has been produced, I removed the PDB file to make it “harder”. But using the free application called dotPeak by Jetbrains I can reveal the structure of the code. Open the .exe file in dotPeak, and navigate to:decompiled-security / <Root Namesapce> / Config / DatabaseConfig
Right click and select “Decompile Sources”
Automatically, in the right panel, it is now clear as day the strings, and the structures. This matches very closely with the original source.

It is a little harder, but instead of using a text editor, I am using a decompiler.
Key Lessons
This shows how it is trivial to extract the strings from the dotnet binary if you don’t have the source code, but do have the .exe or .dll. But what does this actually demonstrate?
- False Security: Relying on the invisibility of hard-coded secrets in compiled code is misleading and insecure.
- Use Secure Methods: Properly manage secrets using environment variables, secrets management tools, or encrypted storage.
- Security by Obscurity is Insufficient: Simply hiding secrets within your code is not a reliable security strategy.
If an attacker or malicious actor can get a hold of the binary, they can get all of the strings that are in that binary. They may get this from any number of places including, backups, dev machines, compromised servers. To get better security, you need to separate the strings that need protection away from the compiled application and ensure that they are not in clear text in a config file.
Secure Methods for Managing Secrets
To be useful, we need to know the alternatives that allow for a more secure environment. The common options are:
- Environment Variables: Store secrets in environment variables. This approach keeps sensitive data out of your source code and can be easily configured for different environments (development, testing, production).
- Secrets Management Tools (Vaults): Utilize secrets management tools such as HashiCorp Vault, AWS Secrets Manager, or Azure Key Vault. These tools provide secure storage, access control, and auditing capabilities for sensitive information.
- Local Hardware Encryption Systems: Leverage hardware-based security features like Trusted Platform Modules (TPM) or secure enclaves. These systems provide hardware-level encryption and secure key storage, making it significantly harder for attackers to access sensitive data.
All of these options may require significant changes to how the development team thinks about security of the secrets, and deploy new tooling. i.e. it is not more secure if the Environment Variables are set by another process from a file that sits on disk next to the binary application.