it-swarm-korea.com

Microsoft CSP에 저장된 모든 RSA 키를 어떻게 열거 할 수 있습니까?

여러 키를 만들어 다양한 상점 (이 경우 Machine store)에 저장하는 응용 프로그램이 있습니다.

주어진 Windows 시스템에서 모든 키를 어떻게 열거 할 수 있습니까?

      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

이 키는이 게시물의 맨 아래에 나열된 위치에 저장됩니다. 많은 네트워크 관리자는 이러한 파일의 목적을 인식하지 못하며 웹의 일부 포럼 게시물은 사람들에게 이러한 파일을 삭제하도록 잘못 권고합니다. 물론 이러한 행동의 영향은 구현/응용 프로그램에 따라 다릅니다. 다음 코드를 사용하여 파일을 읽을 수 없습니다 (어쩌면 약간의 변경이 필요합니다)

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

        }

이 데이터를 한 컴퓨터에서 다른 컴퓨터로 이동하려면 "사용자 상태 마이그레이션 도구"도구가 필요합니다. 또한 일부 도구는 이러한 이동 후 CryptoAPI에서 CNG로 키를 노출해야합니다.

CSP에서 참조 된 관련 파일 containerName을 보는 방법을 모릅니다.

Microsoft 레거시 CryptoAPI CSP는 개인 디렉터리를 다음 디렉터리에 저장합니다.

사용자 개인

% APPDATA %\Microsoft\Crypto\RSA\User SID\% APPDATA %\Microsoft\Crypto\DSS\사용자 SID \

로컬 시스템 개인

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

로컬 서비스 개인

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

네트워크 서비스 개인

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

개인 공유

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

CNG는 개인 키를 다음 디렉토리에 저장합니다.

사용자 개인
% APPDATA %\Microsoft\Crypto\Keys

로컬 시스템 개인 % ALLUSERSPROFILE %\Application Data\Microsoft\Crypto\SystemKeys

로컬 서비스 개인 % WINDIR %\ServiceProfiles\LocalService

네트워크 서비스 개인 % WINDIR %\ServiceProfiles\NetworkService

공유 개인 % ALLUSERSPROFILE %\Application Data\Microsoft\Crypto\Keys

참고:

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

[~ # ~] ldap [~ # ~]

자격 증명 로밍이 활성화 된 경우 이러한 키도 LDAP에 저장됩니다.

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

이 명령에서 Word USERNAME을 자격 증명 로밍이 작동하지 않는 사용자 이름으로 바꾸십시오. Active Directory 복제가 이미 수행되었는지 확인하려면 명령에서 -s 옵션을 사용하고 % LOGONSERVER %를 사용자가 실제로 로그온 한 서버로 바꾸십시오. cscverify.ldf 파일에 내 보낸 속성의 값이 표시되는지 확인하십시오.

LDAP 항목의 크기는 DIMSRoarmingMaxNumTokens 및 DIMSRoamingMaxTokenSize 레지스트리 키 ( source )에 의해 제어됩니다.

16
goodguys_activate

C # 만 사용하여 키 컨테이너를 열거 할 수 있지만 그렇게하려면 P/Invoke를 사용해야합니다. 실제로 이것은 악명 높은 KeyPal 유틸리티 에 의해 사용되는 접근 방식입니다. 다음은 머신 키 컨테이너 이름을 나열하는 작은 C # 응용 프로그램입니다. 이름이 있으면 CspParameters 클래스를 사용하여 키 컨테이너에 해당하는 RSA Keyset을 인스턴스화 할 수 있습니다.

CryptAcquireContext 의 P/Invoke 서명에 대해 Pinvoke.net 덕분에 Windows CryptoAPI에 필요한 것을 활용하기 위해 CryptGetProvParam , CryptReleaseContext .

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

Some CSP를 사용하면 키 컨테이너를 열거 할 수 있습니다. 네이티브 코드 (C #이 아닌 C)로이 작업을 수행해야하며 PP_ENUMCONTAINERS 플래그와 함께 CryptGetProvParam () 을 사용하십시오. 코드는 다음과 같습니다.

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

(이 코드는 키 컨테이너 이름이 1024 바이트에 해당한다고 가정하지만 이는 부당한 가정이 아닙니다.)

그런 다음 각 키 컨테이너에 대해 "열기"하여 키 유형과 크기를 얻을 수 있습니다. 공개 키를 모두 내보낼 수 있습니다. 이것은 .NET 코드를 사용하여 수행 할 수 있습니다 (특정 CSP에서 특정 키 컨테이너를 지정하려면 System.Security.Cryptography.CspParameters 사용).

중요한 주 : 모든 CSP가 그러한 열거를 지원하지는 않습니다. 경우에 따라 기존 키 세트가 잘못 정의 된 경우가 있습니다 (예 : CSP가 동적으로 사용자 비밀번호를 요청하고 컨테이너 이름 및 비밀번호에서 시드 된 PRNG)와 함께 키 쌍을 즉석에서 생성하는 경우 CSP의 경우 "키 (적어도 potentia)는 거의 무한하므로 키를 모두 열거 할 수는 없습니다.

3
Tom Leek

@ian과 같은 대답이지만 Powershell 스크립트입니다. 이 예에서는 기계 키를 읽습니다.

$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

키 컨테이너 이름은 ASCII 바이트 40에서 시작하는 인코딩으로 파일에 포함됩니다. 길이는 바이트 8에 저장되지만 1을 뺍니다. 대부분의 파일 (적어도 내 시스템에는) )는 컨테이너 이름으로 GUID {-}을 포함한 38 자 형식으로, 8 번째 바이트로 39를 갖습니다.) 다음 코드는 다음 파일 중 하나에서 컨테이너 이름을 추출합니다.

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

그런 다음이 컨테이너 이름을 사용하여 CspParameters를 통해 키의 세부 사항을로드 할 수 있습니다.

2
ian