DEV Community

Meow
Meow

Posted on

🔐 Using Environment Variables a Little More Securely

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.

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

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

    string encryptedString = Convert.ToBase64String( encryptedData );
Enter fullscreen mode Exit fullscreen mode

and decrypted as easy as this

byte[] encryptedData = Convert.FromBase64String(encryptedString);
    byte[] decyptedData = ProtectedData.Unprotect(
            encryptedData,
            optionalEntropy: null,
            scope:  DataProtectionScope.LocalMachine
            );
Enter fullscreen mode Exit fullscreen mode

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.

Environment.SetEnvironmentVariable("VAR1", var1);
Enter fullscreen mode Exit fullscreen mode

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.

ProcessStartInfo psi = new ProcessStartInfo
{
    FileName = batFilePath,
    RedirectStandardError = true,
    RedirectStandardOutput = true,
-    UseShellExecute = false,
+    UseShellExecute = false,
    CreateNoWindow = true,
};
Enter fullscreen mode Exit fullscreen mode

start new process for your application

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);
}
Enter fullscreen mode Exit fullscreen mode

For testing purposes you can create bat file like that:

@echo off
echo The value of MY_VARIABLE is: %VAR1%
Enter fullscreen mode Exit fullscreen mode

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

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);
}
Enter fullscreen mode Exit fullscreen mode

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

Top comments (0)