21

I have an application that is creating several keys and storing them in various stores (in this case the Machine store).

How can I enumerate all the keys on a given Windows system?

      CspParameters cspParams = new CspParameters();
        cspParams.KeyContainerName = containerName + " " + g.ToString() ;
        cspParams.Flags = CspProviderFlags.UseMachineKeyStore;

        // Create a new RSA key and save it in the container.  This key will encrypt
        // a symmetric key, which will then be encryped in the XML document.
        RSACryptoServiceProvider rsaKey = new RSACryptoServiceProvider(cspParams);
        rsaKey.PersistKeyInCsp = true;
        Console.WriteLine(rsaKey.ToXmlString(false));
        string PublicKeyTest = rsaKey.ToXmlString(false);
AviD
  • 72,708
  • 22
  • 137
  • 218
makerofthings7
  • 50,488
  • 54
  • 253
  • 542
  • Related: these keys may be stored in Client Services Credential Roaming, and may be accessible by LDAP http://technet.microsoft.com/en-us/library/cc512692 – makerofthings7 Jun 02 '12 at 14:19

6 Answers6

18

These keys are stored in the locations listed at the bottom of this post. Many network administrators aren't aware of the purpose of these files, and some forum posts on the web incorrectly advise people to delete these files. Of course, the impact of such an action is implementation/application specific. I was not able to read the files using the following code (perhaps some change is needed)

  var files = System.IO.Directory.GetFiles(@"C:\ProgramData\Application Data\Microsoft\Crypto\RSA\MachineKeys\");

        foreach (var f in files)
        {           
            RSACryptoServiceProvider rsaKey = new RSACryptoServiceProvider();
            var readFile = File.OpenRead(  f.ToString());
            byte[] FileOut = new byte[readFile.Length];
            readFile.Read( FileOut, 0, (int)readFile.Length-1);
            rsaKey.ImportCspBlob(FileOut);

        }

It appears that the tool "User State Migration tool" is required to move this data from one computer to another. In addition some tool will need to expose the keys from CryptoAPI to the CNG after such a move.

I am not aware of any way to view the related files containerName referenced in the CSP.

The Microsoft legacy CryptoAPI CSPs store private keys in the following directories.

User private

%APPDATA%\Microsoft\Crypto\RSA\User SID\ %APPDATA%\Microsoft\Crypto\DSS\User SID\

Local system private

%ALLUSERSPROFILE%\Application Data\Microsoft\Crypto\RSA\S-1-5-18\ %ALLUSERSPROFILE%\Application Data\Microsoft\Crypto\DSS\S-1-5-18\

Local service private

%ALLUSERSPROFILE%\Application Data\Microsoft\Crypto\RSA\S-1-5-19\ %ALLUSERSPROFILE%\Application Data\Microsoft\Crypto\DSS\S-1-5-19\

Network service private

%ALLUSERSPROFILE%\Application Data\Microsoft\Crypto\RSA\S-1-5-20\ %ALLUSERSPROFILE%\Application Data\Microsoft\Crypto\DSS\S-1-5-20\

Shared private

%ALLUSERSPROFILE%\Application Data\Microsoft\Crypto\RSA\MachineKeys %ALLUSERSPROFILE%\Application Data\Microsoft\Crypto\DSS\MachineKeys

CNG stores private keys in the following directories.

User private
%APPDATA%\Microsoft\Crypto\Keys

Local system private %ALLUSERSPROFILE%\Application Data\Microsoft\Crypto\SystemKeys

Local service private %WINDIR%\ServiceProfiles\LocalService

Network service private %WINDIR%\ServiceProfiles\NetworkService

Shared private %ALLUSERSPROFILE%\Application Data\Microsoft\Crypto\Keys

Reference:

http://msdn.microsoft.com/en-us/library/bb204778(v=vs.85).aspx

LDAP

These keys are also stored in LDAP if credential roaming is enabled

ldifde.exe -s %LOGONSERVER% -f cscverify.ldf -r "(cn=USERNAME)" -l msPKIAccountCredentials,msPKIRoamingTimeStamp,msPKIDPAPIMasterKeys

Replace the word USERNAME in this command with the user name where credential roaming does not work. To ensure that Active Directory replication was already performed, use the -s option in the command and replace %LOGONSERVER% with the server the user actually logs on to. Make sure that the cscverify.ldf file shows values for the exported attributes.

The size of the LDAP entries is controled by DIMSRoarmingMaxNumTokens and DIMSRoamingMaxTokenSize registry keys (source)

makerofthings7
  • 50,488
  • 54
  • 253
  • 542
  • 3
    There is a tool called KeyPal that will let you view the container names. For example, "KeyPal.exe LM" will show you all the Local Machine key containers. You can download it from http://www.jensign.com/KeyPal/index.html – Jamey Oct 05 '12 at 15:25
  • 2
    Also: https://github.com/HardFork/KeyPal – SplashHit Oct 18 '17 at 17:40
8

You can enumerate key containers using just C#, but you must leverage P/Invoke in order to do so. Actually, this is the approach that is utilized by the infamous KeyPal utility. Here is a little C# application to list out the machine key container names. Once you have the names, then you can utilize the CspParameters class to instantiate the RSA Keyset corresponding to the key container.

Thanks to Pinvoke.net for the P/Invoke signatures of CryptAcquireContext, CryptGetProvParam, CryptReleaseContext in order to leverage what is required from the Windows CryptoAPI.

class Program
{
    static long CRYPT_MACHINE_KEYSET = 0x20;
    static long CRYPT_VERIFYCONTEXT = 0xF0000000;
    static uint CRYPT_FIRST = 1;
    static uint CRYPT_NEXT = 2;

    static uint PROV_RSA_FULL = 1;
    static uint PP_ENUMCONTAINERS = 2;

    [DllImport("advapi32.dll", SetLastError = true)]
    static extern bool CryptGetProvParam(
       IntPtr hProv,
       uint dwParam,
       [MarshalAs(UnmanagedType.LPStr)] StringBuilder pbData,
       ref uint dwDataLen,
       uint dwFlags);

    [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool CryptAcquireContext(
        ref IntPtr hProv,
        string pszContainer,
        string pszProvider,
        uint dwProvType,
        uint dwFlags);

    [DllImport("advapi32.dll", EntryPoint = "CryptReleaseContext", CharSet = CharSet.Unicode, SetLastError = true)]
    static extern bool CryptReleaseContext(
       IntPtr hProv,
       Int32 dwFlags);

    static void Main(string[] args)
    {
        Console.WriteLine("Key Container Names:");
        IEnumerable<string> keyContainerNames = GetKeyContainerNames();
        foreach (string name in keyContainerNames)
        {
            Console.WriteLine(name);
        }

        Console.WriteLine("Press any key to continue...");
        Console.ReadKey();
    }

    public static IEnumerable<string> GetKeyContainerNames()
    {
        var keyContainerNameList = new List<string>();

        IntPtr hProv = IntPtr.Zero;
        uint flags = (uint)(CRYPT_MACHINE_KEYSET | CRYPT_VERIFYCONTEXT);
        if (CryptAcquireContext(ref hProv, null, null, PROV_RSA_FULL, flags) == false)
            throw new Exception("CryptAcquireContext");

        uint bufferLength = 2048;
        StringBuilder stringBuilder = new StringBuilder((int)bufferLength);
        if (CryptGetProvParam(hProv, PP_ENUMCONTAINERS, stringBuilder, ref bufferLength, CRYPT_FIRST) == false)
            return keyContainerNameList;

        keyContainerNameList.Add(stringBuilder.ToString());

        while (CryptGetProvParam(hProv, PP_ENUMCONTAINERS, stringBuilder, ref bufferLength, CRYPT_NEXT))
        {
            keyContainerNameList.Add(stringBuilder.ToString());
        }

        if (hProv != IntPtr.Zero)
        {
            CryptReleaseContext(hProv, 0);
        }

        return keyContainerNameList;
    }
}
Derek W
  • 181
  • 1
  • 3
8

Same answer as @ian but as a Powershell script. In this example reading the machine keys.

$containerPath = "C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys"

Get-ChildItem $containerPath | ForEach-Object {
    try { 
        $bytes = [System.IO.File]::ReadAllBytes("$($_.fullName)")
        # Decode $byte[8]-1 bytes from position 40, assuming ASCII encoding.
        $ContainerName = [System.Text.Encoding]::ASCII.GetString($bytes, 40, $bytes[8]-1)
    } catch {
        $ContainerName="No read access"
    }
    [PSCustomObject]@{
        Container = $ContainerName
        FileName = $_.Name
    }
}
8DH
  • 181
  • 1
  • 2
4

Some CSP allow for enumerating key containers. You have to do it with native code (C, not C#), and use CryptGetProvParam() with the PP_ENUMCONTAINERS flag. The code looks like this:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <windows.h>
#include <wincrypt.h>

static void
usage(void)
{
        fprintf(stderr,
"Usage: enumkeys.exe [ csp ]\n");
        exit(EXIT_FAILURE);
}

static void
failerr(char *funName)
{
        fprintf(stderr, "%s() failed: 0x%08lX\n",
                funName, (unsigned)GetLastError());
        exit(EXIT_FAILURE);
}

int
main(int argc, char *argv[])
{
        int size;
        char *csp, buf[1024];
        HCRYPTPROV hprov;
        DWORD flags, buf_len;

        if (argc > 2) {
                usage();
        }
        if (argc > 1) {
                csp = argv[1];
        } else {
                csp = MS_STRONG_PROV_A;
        }
        hprov = 0;
        flags = CRYPT_VERIFYCONTEXT | CRYPT_MACHINE_KEYSET;
        if (!CryptAcquireContextA(&hprov, NULL, csp, PROV_RSA_FULL, flags)) {
                failerr("CryptAcquireContext");
        }
        buf_len = sizeof buf;
        if (!CryptGetProvParam(hprov, PP_ENUMCONTAINERS,
                buf, &buf_len, CRYPT_FIRST))
        {
                if (GetLastError() == ERROR_NO_MORE_ITEMS) {
                        printf("No container.\n");
                        exit(EXIT_SUCCESS);
                } else {
                        failerr("CryptGetProvParam");
                }
        }
        for (;;) {
                printf("Container: '%s'\n", buf);
                buf_len = sizeof buf;
                if (!CryptGetProvParam(hprov, PP_ENUMCONTAINERS,
                        buf, &buf_len, CRYPT_NEXT))
                {
                        if (GetLastError() == ERROR_NO_MORE_ITEMS) {
                                break;
                        }
                        failerr("CryptGetProvParam");
                }
        }
        CryptReleaseContext(hprov, 0);
        return EXIT_SUCCESS;
}

(This code assumes that key container names fit in 1024 bytes; this is not an unreasonable assumption.)

For each key container, you may then want to "open" it and obtain the key type and size; possibly export the public key altogether. This can be done with .NET code (use System.Security.Cryptography.CspParameters to designate a specific key container on a specific CSP).

Important note: not all CSP support such enumeration. In some cases, the set of existing keys is ill-defined, e.g. if the CSP dynamically asks for a user password, and generates a key pair on the fly, with a PRNG seeded from the container name and the password. For such a CSP, the number of "existing" keys (at least in potentia) is virtually infinite, so you won't be able to enumerate them all.

Tom Leek
  • 170,038
  • 29
  • 342
  • 480
3

PowerShell solution based entirely on Derek W's answer.

$TypeDefinition=@'
using System;
using System.Text;
using System.Collections.Generic;

namespace KeyContainerFinder {
  using System.ComponentModel;
  using System.Runtime.InteropServices;

  public class Program {
    static long CRYPT_MACHINE_KEYSET = 0x20;
    static long CRYPT_VERIFYCONTEXT = 0xF0000000;
    static uint CRYPT_FIRST = 1;
    static uint CRYPT_NEXT = 2;

    static uint PROV_RSA_FULL = 1;
    static uint PP_ENUMCONTAINERS = 2;

    [DllImport("advapi32.dll", SetLastError = true)]
    static extern bool CryptGetProvParam(
       IntPtr hProv,
       uint dwParam,
       [MarshalAs(UnmanagedType.LPStr)] StringBuilder pbData,
       ref uint dwDataLen,
       uint dwFlags);

    [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool CryptAcquireContext(
        ref IntPtr hProv,
        string pszContainer,
        string pszProvider,
        uint dwProvType,
        uint dwFlags);

    [DllImport("advapi32.dll", EntryPoint = "CryptReleaseContext", CharSet = CharSet.Unicode, SetLastError = true)]
    static extern bool CryptReleaseContext(IntPtr hProv, Int32 dwFlags);

    static void Main(string[] args)     {
        Console.WriteLine("Key Container Names:");
        IEnumerable<string> keyContainerNames = GetKeyContainerNames();
        foreach (string name in keyContainerNames) { Console.WriteLine(name); }

        Console.WriteLine("Press any key to continue...");
        Console.ReadKey();
    }

    public static IEnumerable<string> GetKeyContainerNames() {
        var keyContainerNameList = new List<string>();

        IntPtr hProv = IntPtr.Zero;
        uint flags = (uint)(CRYPT_MACHINE_KEYSET | CRYPT_VERIFYCONTEXT);
        if (CryptAcquireContext(ref hProv, null, null, PROV_RSA_FULL, flags) == false)
            throw new Exception("CryptAcquireContext");

        uint bufferLength = 2048;
        StringBuilder stringBuilder = new StringBuilder((int)bufferLength);
        if (CryptGetProvParam(hProv, PP_ENUMCONTAINERS, stringBuilder, ref bufferLength, CRYPT_FIRST) == false)
            return keyContainerNameList;

        keyContainerNameList.Add(stringBuilder.ToString());

        while (CryptGetProvParam(hProv, PP_ENUMCONTAINERS, stringBuilder, ref bufferLength, CRYPT_NEXT)) {
            keyContainerNameList.Add(stringBuilder.ToString());
        }

        if (hProv != IntPtr.Zero) { CryptReleaseContext(hProv, 0); }

        return keyContainerNameList;
    }
  }
}
'@

Add-Type -TypeDefinition $TypeDefinition > $null
Clear-Host
[KeyContainerFinder.Program]::GetKeyContainerNames()
2

The key container name is embedded in the file in ASCII encoding starting at byte 40; its length is stored at byte 8 (but subtract 1). The majority of the files (at least on my system) have a GUID as a container name, in 38-character format including {-}, and have 39 as their 8th byte. The following code will extract the container name from one of these files:

byte[] bytes = File.ReadAllBytes(fileName);<br>
string containerName = Encoding.ASCII.GetString(bytes, 40, bytes[8] - 1);

This container name can then be used to load the details of the key via CspParameters.

Adi
  • 43,953
  • 16
  • 137
  • 168
ian
  • 21
  • 1