〜 卓越した品質へ 〜

はじめに

PKCS#12ファイルをデコードして秘密鍵や公開鍵を取り出す方法は,RUBYやPythonでは比較的簡単に実行することができます。このブログでも,RUBYでPKCS#12をデコードする方法PythonでPKCS#12をデコードする方法を紹介しています。RUBYやPythonで簡単というよりも,OpenSSLをうまく使うことができるから簡単にできるって言ったほうが良いのかもしれませんが。
今回は,Win32 APIを使ってPKCS#12ファイルをデコードし,公開鍵と秘密鍵を取り出してみます。

手順

その1:PFX系の関数を利用してPKCS#12ファイルを証明書ストアにインポートします。この時に,鍵をエクスポート可能なフラグを立てておく必要があります。そうしないと,秘密鍵のエクスポートに失敗してしまいます。
その2:証明書ストア関数を利用して,証明書ストアからインポートした証明書を検索します。
その3:検索した証明書から,対応するカギハンドルを取得して,鍵をエクスポートしていきます。

サンプルソース

その1:PKCS#12ファイルのインポート

利用する主な関数は次の通りです。

  • PFXIsPFXBlob()
     PKCS#12ファイルが正しい形式かを確認する。
  • PFXVerifyPassword()
     入力したパスワードが正しいかを確認する。
  • PFXImportCertStore()
     PKCS#12ファイルをPCのメモリストアに登録する。
// PKCS#12ファイルを読み出し、メモリストアにインポートする関数
BOOL decodeP12(HCERTSTORE *hStore)
{
  BOOL            bRtn = TRUE;            // 戻り値
  HANDLE          hFile;                  // PKCS#12ファイルのファイルポインタ  
  DWORD           dwReadByte;             // 読み出したファイル
  WCHAR           szPassword[] = L"1111"; // PKCS#12ファイルのパスワード(サンプルなので固定値)
  CRYPT_DATA_BLOB pfxPacket;              // 読み出したPKCS#12ファイルをBLOB形式に格納する構造体

  // PKCS#12ファイルを読み出す。
  // PKCS#12ファイルの格納場所はサンプルなので固定値としている。
  hFile = CreateFile(TEXT("C:\\TrustedDesign\\cert.p12"), GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
  if (hFile == INVALID_HANDLE_VALUE) {
    bRtn = FALSE;
    goto FINAL;
  }

  pfxPacket.cbData = GetFileSize(hFile, NULL);
  pfxPacket.pbData = (LPBYTE)CryptMemAlloc(pfxPacket.cbData);
  ReadFile(hFile, pfxPacket.pbData, pfxPacket.cbData, &dwReadByte, NULL);
  CloseHandle(hFile);

  // PKCS#12ファイルが正しい形式かを確認する。
  if (!PFXIsPFXBlob(&pfxPacket)) {
    bRtn = FALSE;
    goto FINAL;
  }

  // PKCS#12ファイルのパスワードを確認する。
  if (!PFXVerifyPassword(&pfxPacket, szPassword, 0)) {
    CryptMemFree(pfxPacket.pbData);
    bRtn = FALSE;
    goto FINAL;
  }

  // PKCS#12ファイルをエクスポート可能な状態でメモリストアにインポートする。
  *hStore = PFXImportCertStore(&pfxPacket, szPassword, CRYPT_USER_KEYSET | CRYPT_EXPORTABLE);
  if (*hStore == NULL) {
    bRtn = FALSE;
  }

FINAL:
  if (pfxPacket.pbData)
    CryptMemFree(pfxPacket.pbData);

  return bRtn;
}

その2:証明書の検索

利用する主な関数は次の通りです。

  • CertFindCertificateInStore()
     証明書ストアから証明書を検索する。
// 証明書を検索する関数
BOOL findCertificateInStore(HCERTSTORE hStore, PCCERT_CONTEXT  *pContext)
{
  BOOL      bRtn = TRUE;                 // 戻り値
  WCHAR     szCertCn[] = L"Test User1";  // 検索する証明書のcn(サンプルなので固定値)

  *pContext = CertFindCertificateInStore(hStore, PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, 0, CERT_FIND_SUBJECT_STR, szCertCn, NULL);
  if (*pContext == NULL) {
    bRtn = FALSE;
  }
  return bRtn;
}

その3:鍵のエクスポート

利用する主な関数は次の通りです。

  • CryptAcquireCertificatePrivateKey()
     証明書コンテキストを指定して,対応する鍵コンテナのコンテキストハンドルを取得する。
  • CryptGetUserKey()
     鍵ハンドルを取得する。
  • CryptExportKey()
     公開鍵/秘密鍵をエクスポートする。
// 鍵コンテナから鍵をエクスポートする関数
BOOL getKey(PCCERT_CONTEXT  pContext, DWORD dwBlobType, LPBYTE pbKeyBlob, DWORD *pdwBlobLength)
{
  BOOL           bRtn = TRUE;  // 戻り値
  DWORD          dwKeySpec;    // 鍵種別
  HCRYPTPROV     hProv;        // CryptoAPIのコンテキストハンドル
  HCRYPTKEY      hKey;         // 鍵ハンドル

  // 鍵BLOBを鍵の構造体に格納して確認する。
  struct RSA_PUBKEY {
    PUBLICKEYSTRUC  publickeystruc;
    RSAPUBKEY rsapubkey;
    BYTE modulus[2048 / 8];      // サンプルなので鍵長は2048bit固定
  };
  RSA_PUBKEY pubkey;
  
  struct RSA_PRIKEY {
    PUBLICKEYSTRUC  publickeystruc;
    RSAPUBKEY rsapubkey;
    BYTE modulus[2048 / 8];
    BYTE prime1[2048 / 16];
    BYTE prime2[2048 / 16];
    BYTE exponent1[2048 / 16];
    BYTE exponent2[2048 / 16];
    BYTE coefficient[2048 / 16];
    BYTE privateExponent[2048 / 8];
  };
  RSA_PRIKEY privatekey;

  // インポートした鍵コンテナのコンテキストを取得する。
  if (!CryptAcquireCertificatePrivateKey(pContext, 0, NULL, &hProv, &dwKeySpec, NULL)) {
    bRtn = FALSE;
    goto FINAL;
  }

  // 鍵コンテナのカギハンドルを取得する。
  if (!CryptGetUserKey(hProv, dwKeySpec, &hKey)) {
    bRtn = FALSE;
    goto FINAL;
  }

  // 鍵のデータを取得する。
  // 1回目の関数コールでデータ長を取得する。
  if (!CryptExportKey(hKey, NULL, dwBlobType, 0, NULL, pdwBlobLength))
  {
    bRtn = FALSE;
    printf("Error %#x", GetLastError());
    goto FINAL;
  }

  // pbKeyBlobのメモリ領域を確保する。
  if (!(pbKeyBlob = (LPBYTE)CryptMemAlloc(*pdwBlobLength))) {
    bRtn = FALSE;
    goto FINAL;
  }
  // 鍵BLOBのデータを取得する。
  if (!(CryptExportKey(hKey, NULL, dwBlobType, 0, pbKeyBlob, pdwBlobLength)))
  {
    bRtn = FALSE;
    goto FINAL;
  }

  // 鍵のフォーマットの構造体に鍵BLOBをコピーして確認する。
  if (dwBlobType == PUBLICKEYBLOB) {
    memcpy(&pubkey, pbKeyBlob, sizeof(RSA_PUBKEY));
  }
  else if (dwBlobType == PRIVATEKEYBLOB) {
    memcpy(&privatekey, pbKeyBlob, sizeof(RSA_PRIKEY));
  }

FINAL:
  if (hKey)
    CryptDestroyKey(hKey);
  if (hProv)
    CryptReleaseContext(hProv, 0);

  return bRtn;
}

全体のソース

上の各関数を呼び出すメイン関数を作って完成です。ソース全体はこうなります。

// DecodePKCS12.cpp : コンソール アプリケーションのエントリ ポイントを定義します。
//

#include "stdafx.h"
#include "windows.h"

#pragma comment (lib, "crypt32.lib")

// プロトタイプ
BOOL decodeP12(HCERTSTORE *hStore);
BOOL findCertificateInStore(HCERTSTORE hStore, PCCERT_CONTEXT  *pContext);
BOOL getKey(PCCERT_CONTEXT  pContext, DWORD dwBlobType, LPBYTE pbKeyBlob, DWORD *pdwBlobLength);

// PKCS#12ファイルを読み出し、メモリストアにインポートする関数
BOOL decodeP12(HCERTSTORE *hStore)
{
  BOOL            bRtn = TRUE;            // 戻り値
  HANDLE          hFile;                  // PKCS#12ファイルのファイルポインタ  
  DWORD           dwReadByte;             // 読み出したファイル
  WCHAR           szPassword[] = L"1111"; // PKCS#12ファイルのパスワード(サンプルなので固定値)
  CRYPT_DATA_BLOB pfxPacket;              // 読み出したPKCS#12ファイルをBLOB形式に格納する構造体

  // PKCS#12ファイルを読み出す。
  // PKCS#12ファイルの格納場所はサンプルなので固定値としている。
  hFile = CreateFile(TEXT("C:\\TrustedDesign\\cert.p12"), GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
  if (hFile == INVALID_HANDLE_VALUE) {
    bRtn = FALSE;
    goto FINAL;
  }

  pfxPacket.cbData = GetFileSize(hFile, NULL);
  pfxPacket.pbData = (LPBYTE)CryptMemAlloc(pfxPacket.cbData);
  ReadFile(hFile, pfxPacket.pbData, pfxPacket.cbData, &dwReadByte, NULL);
  CloseHandle(hFile);

  // PKCS#12ファイルが正しい形式かを確認する。
  if (!PFXIsPFXBlob(&pfxPacket)) {
    bRtn = FALSE;
    goto FINAL;
  }

  // PKCS#12ファイルのパスワードを確認する。
  if (!PFXVerifyPassword(&pfxPacket, szPassword, 0)) {
    CryptMemFree(pfxPacket.pbData);
    bRtn = FALSE;
    goto FINAL;
  }

  // PKCS#12ファイルをエクスポート可能な状態でメモリストアにインポートする。
  *hStore = PFXImportCertStore(&pfxPacket, szPassword, CRYPT_USER_KEYSET | CRYPT_EXPORTABLE);
  if (*hStore == NULL) {
    bRtn = FALSE;
  }

FINAL:
  if (pfxPacket.pbData)
    CryptMemFree(pfxPacket.pbData);

  return bRtn;
}

// 証明書を検索する関数
BOOL findCertificateInStore(HCERTSTORE hStore, PCCERT_CONTEXT  *pContext)
{
  BOOL      bRtn = TRUE;                 // 戻り値
  WCHAR     szCertCn[] = L"Test User1";  // 検索する証明書のcn(サンプルなので固定値)

  *pContext = CertFindCertificateInStore(hStore, PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, 0, CERT_FIND_SUBJECT_STR, szCertCn, NULL);
  if (*pContext == NULL) {
    bRtn = FALSE;
  }
  return bRtn;
}


// 鍵コンテナから鍵をエクスポートする関数
BOOL getKey(PCCERT_CONTEXT  pContext, DWORD dwBlobType, LPBYTE pbKeyBlob, DWORD *pdwBlobLength)
{
  BOOL           bRtn = TRUE;  // 戻り値
  DWORD          dwKeySpec;    // 鍵種別
  HCRYPTPROV     hProv;        // CryptoAPIのコンテキストハンドル
  HCRYPTKEY      hKey;         // 鍵ハンドル

  // 鍵BLOBを鍵の構造体に格納して確認する。
  struct RSA_PUBKEY {
    PUBLICKEYSTRUC  publickeystruc;
    RSAPUBKEY rsapubkey;
    BYTE modulus[2048 / 8];      // サンプルなので鍵長は2048bit固定
  };
  RSA_PUBKEY pubkey;
  
  struct RSA_PRIKEY {
    PUBLICKEYSTRUC  publickeystruc;
    RSAPUBKEY rsapubkey;
    BYTE modulus[2048 / 8];
    BYTE prime1[2048 / 16];
    BYTE prime2[2048 / 16];
    BYTE exponent1[2048 / 16];
    BYTE exponent2[2048 / 16];
    BYTE coefficient[2048 / 16];
    BYTE privateExponent[2048 / 8];
  };
  RSA_PRIKEY privatekey;

  // インポートした鍵コンテナのコンテキストを取得する。
  if (!CryptAcquireCertificatePrivateKey(pContext, 0, NULL, &hProv, &dwKeySpec, NULL)) {
    bRtn = FALSE;
    goto FINAL;
  }

  // 鍵コンテナのカギハンドルを取得する。
  if (!CryptGetUserKey(hProv, dwKeySpec, &hKey)) {
    bRtn = FALSE;
    goto FINAL;
  }

  // 鍵のデータを取得する。
  // 1回目の関数コールでデータ長を取得する。
  if (!CryptExportKey(hKey, NULL, dwBlobType, 0, NULL, pdwBlobLength))
  {
    bRtn = FALSE;
    printf("Error %#x", GetLastError());
    goto FINAL;
  }

  // pbKeyBlobのメモリ領域を確保する。
  if (!(pbKeyBlob = (LPBYTE)CryptMemAlloc(*pdwBlobLength))) {
    bRtn = FALSE;
    goto FINAL;
  }
  // 鍵BLOBのデータを取得する。
  if (!(CryptExportKey(hKey, NULL, dwBlobType, 0, pbKeyBlob, pdwBlobLength)))
  {
    bRtn = FALSE;
    goto FINAL;
  }

  // 鍵のフォーマットの構造体に鍵BLOBをコピーして確認する。
  if (dwBlobType == PUBLICKEYBLOB) {
    memcpy(&pubkey, pbKeyBlob, sizeof(RSA_PUBKEY));
  }
  else if (dwBlobType == PRIVATEKEYBLOB) {
    memcpy(&privatekey, pbKeyBlob, sizeof(RSA_PRIKEY));
  }

FINAL:
  if (hKey)
    CryptDestroyKey(hKey);
  if (hProv)
    CryptReleaseContext(hProv, 0);

  return bRtn;
}

int main()
{
  BOOL            bRtn = TRUE;      // 内部関数の戻り値
  HCERTSTORE      hMemoryStore;     // PKCS#12ファイルをインポートしたメモリストアのハンドル
  PCCERT_CONTEXT  pContext = NULL;  // 証明書コンテキストハンドル
  DWORD           dwBlobType;       // 鍵BLOBのタイプ
  LPBYTE          pbKeyBlob = NULL; // エクスポートした鍵BLOB
  DWORD           dwBlobLength;     // 鍵BLOB長

  // その1;PKCS#12ファイル読み出し、メモリストアにインポートする。
  bRtn = decodeP12(&hMemoryStore);
  if (!bRtn) {
    goto Err;
  }

  // その2:インポートした証明書を検索し、コンテキストハンドルを取得する。
  bRtn = findCertificateInStore(hMemoryStore, &pContext);
  if (!bRtn) {
    goto Err;
  }

  // その3:鍵コンテナから鍵をエクスポートする。
  // 公開鍵をエクスポートする。
  dwBlobType = PUBLICKEYBLOB;
  bRtn = getKey(pContext, dwBlobType, pbKeyBlob, &dwBlobLength);
  if (!bRtn) {
    goto Err;
  }

  CryptMemFree(pbKeyBlob);
  pbKeyBlob = NULL;

  // 秘密鍵をエクスポートする。
  dwBlobType = PRIVATEKEYBLOB;
  bRtn = getKey(pContext, dwBlobType, pbKeyBlob, &dwBlobLength);
  if (!bRtn) {
    goto Err;
  }

Err:
  // 解放処理
  CryptMemFree(pbKeyBlob);
  CertFreeCertificateContext(pContext);
  CertCloseStore(hMemoryStore, CERT_CLOSE_STORE_CHECK_FLAG);

  return 0;
}

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です