In this post, I’m going to demonstrate how to securely store secrets inside appconfig.json for Dot Net Core applications. This article is addressed to the developers who have many sensitive settings and credentials and are looking for a neat solution to manage them without too much fuss.
To achieve this, we are going to use a library called Mindgaze.Tools.Secrets which can be found on nuget. It uses a DSA algorithm to encrypt/decrypt data. Any encrypt operation will require these two parameters: the KEY and the IV. It’s mandatory to keep these two parameters safe for production or staging environments. Any leak will compromise the security of your system!
Setup the project
We are going to create a new MVC app. To this we will link the Mindgaze.Tools.Secrets library:
$ dotnet new mvc -n Secrets.Sample
$ cd Secrets.Sample
$ dotnet add package Mindgaze.Tools.Secrets
Now the library is installed and ready to use. But most of the time you’ll want also the CLI to be available to work with. There is no command to do this, we’ll have to add this reference manually. Open the project with a text editor and add the following CLI tool reference:
<ItemGroup>
<DotNetCliToolReference Include="Mindgaze.Tools.Secrets" Version="2.0.3" />
</ItemGroup>
After performing this modification, restore the packages and run the command “dotnet secrets” to make sure everything is allright:
$ dotnet restore
...
$ dotnet secrets
Usage: dotnet secrets generateKeys|encrypt|decrypt "string-value"
Encryption keys can be loaded from environment (ASPNETCORE_SECRETS_KEY, ASPNETCORE_SECRETS_IV) or command line -key 'value' and -iv 'value'. The commandline values will override the environment values
CLI interface
Cool! Now that we have it installed, let’s play with the CLI a little bit. As you noticed, the command is called “dotnet secrets” and can perform these operations:
- Generate keys: “dotnet secrets generate”
- Encrypt a value: “dotnet secrets encrypt
‘value_to_encrypt’ -key ‘key’ -iv ‘iv’ “ - Decrypt a value: “dotnet secrets decrypt
‘value_to_decrypt’ -key ‘key’ -iv ‘iv’ “
The generate command requires no arguments but the other 2 require the KEY and IV parameters supplied via the “-key” and “-iv” and the value as final argument. NOTE: the order of these counts. Additionally, you can store the KEY and IV in ASPNETCORE_SECRETS_KEY and ASPNETCORE_SECRETS_IV environment variables. This way you don’t have to supply the parameters each time.
Let’s exemplify these by encrypting a value and decrypting it back:
$ dotnet secrets generate
Key: 'HpY2DSjtv09fQarKmrHH0ScayzFEUsnoIl98P138zaE='
IV: 'W2fzhJRADbSdprCk+Qhnmg=='
# We generated the keys above
$ dotnet secrets encrypt 'My Secret Value' -key 'HpY2DSjtv09fQarKmrHH0ScayzFEUsnoIl98P138zaE=' -iv 'W2fzhJRADbSdprCk+Qhnmg=='
ClaBNKJgp9qrotjaM5bR71cys9o56FTllgQ/xTXpMKM=
# The value above is the encrypted value of 'My Secret Value'
$ dotnet secrets decrypt 'ClaBNKJgp9qrotjaM5bR71cys9o56FTllgQ/xTXpMKM=' -key 'HpY2DSjtv09fQarKmrHH0ScayzFEUsnoIl98P138zaE=' -iv 'W2fzhJRADbSdprCk+Qhnmg=='
My Secret Value
# We now decrypted back the value
Use in the project
Now let’s use it inside the project. First we have to add it in the ConfigureServices method in Startup.cs:
using Mindgaze.Tools.Secrets;
...
public void ConfigureServices(IServiceCollection services)
{
...
services.AddSecrets(Configuration["SECRETS_KEY"], Configuration["SECRETS_IV"]);
}
Now that the library is configured, we can inject the AppSecrets instance in our controllers or services:
public HomeController(AppSecrets appSecrets)
{
// Do something with Encrypt or the Decrypt methods of appSecrets
}
Safely store our first secret
Now to the real deal. In this section we’ll store our first secret in the configuration. I’ll also show how to deal with both development and production configurations. Depending on the number of configurations you may need to manage more security parameter pairs (KEY and IV). In our example, we will need just two pairs:
- Development
- KEY: HpY2DSjtv09fQarKmrHH0ScayzFEUsnoIl98P138zaE=
- IV: W2fzhJRADbSdprCk+Qhnmg==
- Production
- KEY: GVJNfEdqUN+tgZQF8c2OPUb2/r00aiKryeY4H39WnS4=
- IV: cSFvwtcOH8AcZy6psUmNew==
Let’s suppose we have a database connection string we want to store. This is how it looks in both configurations (remember the production should never be accessible to anyone):
- Development: Server=localhost;Database=secrets-sample_development;User Id=local;Password=Testing;
- Production: Server=internal.sqlserver.net;Database=secrets-sample_production;User Id=ssaccess;Password=OurProductionPwd123;
Preparing the development environment
Go to the HomeController class and do the following modification:
private readonly string EnvironmentName;
private readonly string ConnectionString;
public HomeController(AppSecrets appSecrets, IHostingEnvironment hostingEnvironment, IConfiguration configuration)
{
EnvironmentName = hostingEnvironment.EnvironmentName;
ConnectionString = appSecrets.Decrypt(configuration["ConnectionString"]);
}
public IActionResult Index()
{
ViewData["EnvironmentName"] = EnvironmentName;
ViewData["ConnectionString"] = ConnectionString;
return View();
}
In the above code, we inject the secrets, environment and configuration instances of the application. The appSecrets will be used to decrypt the value that comes from configuration and the environment variable is used to know the environment name.
Don’t forget to add these lines to Index.cshtml:
<li>Environment: @ViewData["EnvironmentName"]</li>
<li>Decrypted connection string: @ViewData["ConnectionString"]</li>
Only two easy steps remaining: take the connection string, encrypt it with the required crypto pair and add it to appsettings.Development.json.
appsettings.Development.json
{
"Logging": {
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
}
},
"SECRETS_KEY": "HpY2DSjtv09fQarKmrHH0ScayzFEUsnoIl98P138zaE=",
"SECRETS_IV": "W2fzhJRADbSdprCk+Qhnmg==",
"ConnectionString": "ADF2qRma96iPXpQFj7xrUcyTBWCq5HrRSIvH41dIXi+4qgpVmu/jmBgT0Jmf91aoXEwIGZ4kbo9+7BFnX5XpRHcjgKJRkhuI1qAfYUnkmZnASpKIuxc8N9oCRhgjbO8oLIdKIHyWQcyOKVHfDurWGtJTUATNW0K8oAJN45hZ2P/sjFyW/1ymGz2yKw9wiPFybL+QV3hNquGh1bwBEjBr71tkFgq1hwPa/dQ+nL327Sk="
}
All the effort so far wasn’t in vain, if we run now the application we’ll see the connection string was loaded correctly:
Preparing the production environment
This part is actually very easy. You just need to encrypt the production connection string with the corresponding crypto pair. Put this encrypted value in appsettings.json. If you notice, we’ve also put the KEY and IV in the appsettings.Development.json file. We want every dev to be able to connect to the local database. However, in production, we don’t put those parameters there because it’ll break our security. Depending on your production environment, these parameters will stay safe in the machine’s configuration.
appsettings.json
{
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionString": "7XwkMlrDKXzSWH4iUTyNGfIvFwSzoj9Ytd4GEUBdvpR/MKWwZQZD+nKcK7fWJ/HH3AuPOdNNcCWX+Ieb9vHyFtSgzx4gav73aWbBvMebS5C6ErCPcKtMAsEPnS7TGoFeCgxWfRmbVVsFTsv1aZt6pRhJHnmbyFsi67ipwDYbV3cLIFEPofnmPRhKLeVH04wtLCvunEDL3un9VPeQa5orGluqbX7rPA290YlLozcb3XyOKw8QlM1dgHr5WJfcLD2Rp0kT+/ooEFUazpJ8hYK/86KPqnGr/LNRDaMI6Esp8kM="
}
Running the app in production mode without the parameters set up will crash the application. On our dev machine we can add a new profile in launchSettings.json and run it. We can see that our app works in production mode as well ๐
Conclusion
In this article, I demonstrated how we can store application secrets securely using the Mindgaze.Tools.Secrets library. As long as we keep the encryption pair safe, we can be sure that nobody knows our database connection string, our API keys etc. Moreover, we can eliminate clutter on the prod machine because we now just need to store 2 variables. I believe this is quite helpful!
Full source code on Github
If you have any questions or comments don’t hesitate to contact me!
All the best!