How can I enumerate all the saved RSA keys in the Microsoft CSP?

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)


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;
    }
}

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.