Storing secrets in .NET Core applications

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:

  1. Generate keys: “dotnet secrets generate”
  2. Encrypt a value: “dotnet secrets encrypt 
    ‘value_to_encrypt’ -key ‘key’ -iv ‘iv’  “
  3. 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:

  1. Development
    1. KEY: HpY2DSjtv09fQarKmrHH0ScayzFEUsnoIl98P138zaE=
    2. IV: W2fzhJRADbSdprCk+Qhnmg==
  2. Production
    1. KEY: GVJNfEdqUN+tgZQF8c2OPUb2/r00aiKryeY4H39WnS4=
    2. 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):

  1. Development: Server=localhost;Database=secrets-sample_development;User Id=local;Password=Testing;
  2. 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:

We’re running env Development

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!

Related posts

7 Thoughts to “Storing secrets in .NET Core applications”

  1. Varunjan Dibi

    Can I manipulate the appsettings.json directly with the CLI?

    1. afivan

      Unfortunately, not yet. I was thinking to add some functionality like that, however I’m quite time constrained right now 🙂

  2. Sac

    Where should I store the “SECRETS_KEY” & “SECRETS_IV” in production mode in Non Azure application running without internet access?

    1. afivan

      Hey Sac,

      Are you running in Docker? Although not really recommended, you can store them in environment variables.

      Cheers,
      Andrei

  3. Ola
    Hi, I've been trying to use the library in my core 3.0 project with with Mindgaze.Tools.Secrets Version="2.0.3". Got error "string is not base-64" when running the  dotnet secrets encrypt 'My Secret Value'.. and so on - exactly as in the exxample. Changed to 'MySring' - same error. Any ideas on how to overcome the error? Thanks
    

     

    1. afivan

      Hello Ola,

      Can you try to use the latest version https://www.nuget.org/packages/Mindgaze.Tools.Secrets/2.1.3

      In dotnet core 3.0 I think you need to install it differently something like:
      dotnet tool install Mindgaze.Tools.Secrets

      Let me know if you manage to do it,
      Andrei

Leave a Reply