🔐 Using Environment Variables a Little More Securely
Tags: cryptography, programming
Published: 2024-02-06 19:51:13

Storing credentials in text files is not such a smart idea. A few days ago I found a post somewhere on reddit where people were discussing a more secure way to use environmental variables for restic. (By default they are stored as plain text).

When I tried to look for a solution for this, I found another post where people talked that you can customise restic code and build it yourself. But who wants to do that?

I wanted something more general. And came up with this. If you are on Windows you can use the Data Protection API [more info here](https://learn.microsoft.com/en-us/aspnet/core/security/data-protection/introduction?view=aspnetcore-8.0). 

The data is then encrypted with a machine key. You just have to store that encrypted data somewhere. (for example in the registry)
## Encrypting and decrypting data

In this example I am using `DataProtectionScope.LocalMachine`, which means the data can be decrypted by any process running on the same machine where the data was encrypted.

Another options is to use `DataProtectionScope.CurrentUser`, which means only the process running under the user who encrypted the data can decrypt it.

Your data can be encrypted like this

```cs
 byte[] encryptedData = ProtectedData.Protect(
        System.Text.Encoding.UTF8.GetBytes(value),
        optionalEntropy: null,
        scope: DataProtectionScope.LocalMachine
        );

    string encryptedString = Convert.ToBase64String( encryptedData );
```

and decrypted as easy as this

```cs
byte[] encryptedData = Convert.FromBase64String(encryptedString);
	byte[] decyptedData = ProtectedData.Unprotect(
			encryptedData,
			optionalEntropy: null,
			scope:  DataProtectionScope.LocalMachine
			);
```

## Using data as environment variable

Now that you know how to encrypt, store and read data, you can use it as an environment variable when invoking external binary.


```cs
Environment.SetEnvironmentVariable("VAR1", var1);
```

and because you want to use environment variables, change `UseShellExecute` to `false`, which means that the process you are starting will not use the operating system shell to start the process. Instead, it will start the process directly. This allows you to redirect the input, output and error streams of the process.

 if `UseShellExecute` is set to `true`, the new process will start in a new shell, and it will not have access to the environment variables set in the parent process.

```diff
ProcessStartInfo psi = new ProcessStartInfo
{
    FileName = batFilePath,
    RedirectStandardError = true,
    RedirectStandardOutput = true,
-    UseShellExecute = false,
+    UseShellExecute = false,
    CreateNoWindow = true,
};
```

start new process for your application

```cs
using (Process process = new Process { StartInfo = psi})
{
    process.Start();
    string output = process.StandardOutput.ReadToEnd();

    process.WaitForExit();

    Console.WriteLine("Microsoft Data protection API example");
    Console.WriteLine("---> " +  output);
}
```

For testing purposes you can create bat file like that:

```cmd
@echo off
echo The value of MY_VARIABLE is: %VAR1%
```

If you run it as a standalone, you will not seed the variable value as this is only set under the process that your bat file is running from.

The above solution displays output after the process has finished. This is not ideal for time-consuming processes from which you want to see output. A possible solution could be as follows

```diff
using (Process process = new Process { StartInfo = psi})
{
+	process.OutputDataReceived += (sender, args) => Console.WriteLine("---> " + args.Data);

    process.Start();
+	process.BeginOutputReadLine();
+	process.BeginErrorReadLine();
-    string output = process.StandardOutput.ReadToEnd();

+	Task processTask = Task.Run(() => process.WaitForExit());
+	await processTask;
-    process.WaitForExit();

-    Console.WriteLine("Microsoft Data protection API example");
-    Console.WriteLine("---> " +  output);
}
```

This is a bit more secure solution at least better than storing them just like that into text file.

However remember that the data encrypted with `DataProtectionScope.LocalMachine` scope can be accessed aby anyone who can use your computer. 

And if you decide to use `DataProtectionScope.CurrentUser`, it is probably a good idea to create another password protected user which will only be used to run your binary and/or provide an admin account to the necessary minimum of people.

Originally publised at [my blog](https://maymeow.com/posts/2024/01/using-environment-variables-a-little-more-securely/)