it-swarm-es.com

¿Cómo puedo enumerar todas las claves RSA guardadas en Microsoft CSP?

Tengo una aplicación que crea varias claves y las almacena en varias tiendas (en este caso, la tienda Machine).

¿Cómo puedo enumerar todas las claves en un sistema Windows dado?

      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);
18
goodguys_activate

Estas claves se almacenan en las ubicaciones enumeradas al final de esta publicación. Muchos administradores de red no son conscientes del propósito de estos archivos, y algunas publicaciones de foros en la web aconsejan incorrectamente a las personas que eliminen estos archivos. Por supuesto, el impacto de tal acción es específico de implementación/aplicación. No pude leer los archivos con el siguiente código (quizás sea necesario algún cambio)

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

        }

Parece que la herramienta "Herramienta de migración de estado de usuario" es necesaria para mover estos datos de una computadora a otra. Además, alguna herramienta necesitará exponer las claves de CryptoAPI al CNG después de tal movimiento.

No conozco ninguna forma de ver los archivos relacionados containerName referenciados en el CSP.

Los CSP CryptoAPI heredados de Microsoft almacenan claves privadas en los siguientes directorios.

Usuario privado

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

Sistema local privado

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

Servicio local privado

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

Servicio de red privado

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

Privado compartido

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

CNG almacena claves privadas en los siguientes directorios.

Usuario privado
% APPDATA%\Microsoft\Crypto\Keys

Sistema local privado % ALLUSERSPROFILE%\Application Data\Microsoft\Crypto\SystemKeys

Servicio local privado % WINDIR%\ServiceProfiles\LocalService

Servicio de red privado % WINDIR%\ServiceProfiles\NetworkService

Privado compartido % ALLUSERSPROFILE%\Application Data\Microsoft\Crypto\Keys

Referencia:

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

[~ # ~] ldap [~ # ~]

Estas claves también se almacenan en LDAP si el roaming de credenciales está habilitado

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

Reemplace el NOMBRE DE USUARIO de Word en este comando con el nombre de usuario donde el roaming de credenciales no funciona. Para asegurarse de que la replicación de Active Directory ya se realizó, use la opción -s en el comando y reemplace% LOGONSERVER% con el servidor en el que el usuario realmente inicia sesión. Asegúrese de que el archivo cscverify.ldf muestre valores para los atributos exportados.

El tamaño de las entradas LDAP está controlado por las claves de registro DIMSRoarmingMaxNumTokens y DIMSRoamingMaxTokenSize ( fuente )

16
goodguys_activate

Puede enumerar contenedores de claves utilizando solo C #, pero debe aprovechar P/Invoke para hacerlo. En realidad, este es el enfoque que utilizan los infames tilidad KeyPal . Aquí hay una pequeña aplicación de C # para enumerar los nombres de contenedor de clave de máquina. Una vez que tenga los nombres, puede utilizar la clase CspParameters para crear una instancia del conjunto de claves RSA correspondiente al contenedor de claves.

Gracias a Pinvoke.net por las firmas P/Invoke de CryptAcquireContext , CryptGetProvParam , CryptReleaseContext para aprovechar lo que se requiere de 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;
    }
}
5
Derek W

¡Algunos CSP permite enumerar contenedores de claves. Tienes que hacerlo con código nativo (C, no C #), y usar CryptGetProvParam () con el PP_ENUMCONTAINERS bandera. El código se ve así:

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

(Este código supone que los nombres de los contenedores de claves caben en 1024 bytes; esto no es una suposición irrazonable).

Para cada contenedor de claves, es posible que desee "abrirlo" y obtener el tipo y tamaño de clave; posiblemente exportar la clave pública por completo. Esto se puede hacer con código .NET (use System.Security.Cryptography.CspParameters para designar un contenedor de claves específico en un CSP específico).

Nota importante: no todos los CSP admiten dicha enumeración. En algunos casos, el conjunto de claves existentes está mal definido, p. si el CSP solicita dinámicamente una contraseña de usuario y genera un par de claves sobre la marcha, con un PRNG derivado del nombre del contenedor y la contraseña. Para dicho CSP, el número de "existentes "Las teclas (al menos en potentia) son prácticamente infinitas, por lo que no podrá enumerarlas todas.

3
Tom Leek

Misma respuesta que @ian pero como script de Powershell. En este ejemplo leyendo las llaves de la máquina.

$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
    }
}
2
8DH

El nombre del contenedor de claves está incrustado en el archivo en ASCII que comienza en el byte 40; su longitud se almacena en el byte 8 (pero resta 1). La mayoría de los archivos (al menos en mi sistema ) tienen un GUID como nombre del contenedor, en formato de 38 caracteres que incluye {-}, y tienen 39 como su octavo byte. El siguiente código extraerá el nombre del contenedor de uno de estos archivos:

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

Este nombre de contenedor se puede usar para cargar los detalles de la clave a través de CspParameters.

2
ian