6

So far, to store and retrieve secrets (like credentials) in .NET applications, I successfully used the CredentialManagement package on Windows. Now I'd like to go cross-platform.

So I need to access the Windows Credential Manager from a .NET Core cross-platform application. If it's running on Windows - use the Credential Manager. If it's running on Linux - don't crash (use key chain or whatever, that is the next step).

How would this be done?

(Note: I'm open to alternatives to the Windows Credential Manager but they should provide an equal level of protection.)

Heinrich Ulbricht
  • 10,064
  • 4
  • 54
  • 85

2 Answers2

6

I ended up using the Data Protection API for Windows (DPAPI) to store secrets in a file that is encrypted within the scope of the logged in user.

Encrypted storage of a password:

try
{
    var passwordBytes = Encoding.UTF8.GetBytes(password);
    var protectedPasswordBytes = ProtectedData.Protect(passwordBytes, null, DataProtectionScope.CurrentUser);
    var protectedPasswordBytesBase64 = Convert.ToBase64String(protectedPasswordBytes);
    File.WriteAllText(passwordFilePath, protectedPasswordBytesBase64);
} catch (PlatformNotSupportedException)
{
    Debug.WriteLine("Could not store credentials");
}

Decryption:

if (File.Exists(passwordFilePath))
{
    var protectedPasswordBytesBase64 = File.ReadAllText(passwordFilePath);
    var protectedPasswordBytes = Convert.FromBase64String(protectedPasswordBytesBase64);
    var passwordBytes = ProtectedData.Unprotect(protectedPasswordBytes, null, DataProtectionScope.CurrentUser);
    password = Encoding.UTF8.GetString(passwordBytes);
}

(Only works on Windows, for other OSes I'll use other solutions.)

Heinrich Ulbricht
  • 10,064
  • 4
  • 54
  • 85
  • Just for cross-reference: I only later found another question that is more generally phrased and also got the DPAPI as answer: https://stackoverflow.com/questions/67226127/how-to-store-username-and-password-securely-in-net-core-console-app – Heinrich Ulbricht Aug 27 '21 at 20:08
  • 1
    It's worth noting that this does not work on Windows Home and will throw a NotSupportedException. https://learn.microsoft.com/en-us/dotnet/api/system.io.file.encrypt?view=net-6.0#remarks – Jared Aug 09 '22 at 14:17
1

To determine the operating system on which your application is running. This can help, for reference

  1. RuntimeInformation.IsOSPlatform(OSPlatform) Method
  2. OSPlatform.Windows Property

A complete example for windows(CredentialManagement + Detect Operating System),

using CredentialManagement;
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace DetectOSCredentialManagement
{
    class Program
    {
        static void Main(string[] args)
        {
            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
            {
                Console.WriteLine("Hello Beauty!");
                Program.SetCredentials("FOO", "friday", "fr!d@y0", PersistanceType.LocalComputer);
                var userpass = Program.GetCredential("FOO");
                Console.WriteLine($"User: {userpass.Username} Password: {userpass.Password}");
                Program.RemoveCredentials("FOO");
                Debug.Assert(Program.GetCredential("FOO") == null);
            }
            else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
            {
                Console.WriteLine("Hello Cutie!");
            }
            else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
            {
                Console.WriteLine("Too Costly!");
            }
        }

        public static UserPass GetCredential(string target)
        {
            var cm = new Credential { Target = target };
            if (!cm.Load())
            {
                return null;
            }

            // UserPass is just a class with two string properties for user and pass
            return new UserPass(cm.Username, cm.Password);
        }

        public static bool SetCredentials(
             string target, string username, string password, PersistanceType persistenceType)
        {
            return new Credential
            {
                Target = target,
                Username = username,
                Password = password,
                PersistanceType = persistenceType
            }.Save();
        }

        public static bool RemoveCredentials(string target)
        {
            return new Credential { Target = target }.Delete();
        }
    }
    public class UserPass
    {
        public string Username { get; set; }
        public string Password { get; set; }

        public UserPass(string username, string password)
        {
            Username = username;
            Password = password;
        }
    }
}

System.Security.Permissions -- This dll is also need to run above application.

Utkarsh Dubey
  • 703
  • 11
  • 31
  • 1
    Thank you for this sample! I assume that you used the `CredentialManagement.Standard` Nuget package instead of `CredentialManagement` (which is for .NET Framework)? I'm worried about this dependency then: Microsoft.Win32.SystemEvents.4.5.0 - won't this be a problem? – Heinrich Ulbricht Aug 02 '21 at 09:27
  • You are welcome, I've used `CredentialManagement` not `CredentialManagement.Standard`. – Utkarsh Dubey Aug 02 '21 at 09:31
  • Hm did you try this on non-Windows? Because of the .NET Framework dependency this won't work there - or I don't understand it yet – Heinrich Ulbricht Aug 02 '21 at 09:34
  • Which DLL `CredentialManagement` or `System.Runtime.InteropServices`? – Utkarsh Dubey Aug 02 '21 at 09:37
  • I mean this message that pops up when adding CredentialManagement to a .NET 5 (~=Core) project: `Warning NU1701 Package 'CredentialManagement 1.0.2' was restored using '.NETFramework,Version=v4.6.1, .NETFramework,Version=v4.6.2, .NETFramework,Version=v4.7, .NETFramework,Version=v4.7.1, .NETFramework,Version=v4.7.2, .NETFramework,Version=v4.8' instead of the project target framework 'net5.0'. This package may not be fully compatible with your project.` – Heinrich Ulbricht Aug 02 '21 at 09:40
  • Yes, I'm also not sure about it therefore I'm trying to find an alternate way to store credentials. Why don't you use database or json file to store credentials? You can encrypt or decrypt them accordingly. – Utkarsh Dubey Aug 02 '21 at 09:42
  • 1
    I'm open for alternatives but don't want to reinvent the wheel. But maybe the Data Protection API could be something to look at as an alternative. https://learn.microsoft.com/en-us/aspnet/core/security/data-protection/using-data-protection?view=aspnetcore-5.0 – Heinrich Ulbricht Aug 02 '21 at 09:52
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/235533/discussion-between-utkarsh-dubey-and-heinrich-ulbricht). – Utkarsh Dubey Aug 02 '21 at 09:54