2012-03-13

CryptoAPI(CAPI)によるRSA-SHA2署名  [by miyachi]

2011-04-25に「.NET FrameworkによるRSA-SHA2署名(完結編)」を投稿していましたが、ICカード対応でCryptoAPIでRSA-SHA2署名を実装する必要があり調査してみました。基本的には.NET Framework編と同じ理屈で動作したので、CryptoAPI(CAPI)対応編としてまとめます。

詳しくは.NET Framework編をお読み頂きたいのですが、暗号プロバイダとして標準のPROV_RSA_FULL(1)からSHA-2に対応したPROV_RSA_AES(24)に切り替える必要があります。

ではPROV_RSA_AESで証明書に関連付いた秘密鍵を使う方法をソースコードとして以下にまとめます。証明書(PCCERT_CONTEXT) pcCert は適当な方法で用意して下さい。また以下ではエラー判定は行なっていませんし開放もちゃんとやっていませんのでエッセンスのみと考えてご覧下さい。

// 秘密鍵に関連付いた証明書の取得
PCCERT_CONTEXT pcCert = MyFindCert(Thumbprint); // ダミーです

// 標準のプロバイダ(PROV_RSA_FULL)の取得
HCRYPTPROV hProv = NULL;
CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, NULL);

// 個人証明書ストアの取得
HCERTSTORE hStore = NULL;
hStore = CertOpenSystemStore(hProv, "MY");

// 証明書を個人証明書ストアから再取得
PCCERT_CONTEXT pcCert2;
pcCert2 = CertFindCertificateInStore(
hStore, X509_ASN_ENCODING|PKCS_7_ASN_ENCODING,
0, CERT_FIND_EXISTING, pcCert, NULL);
// 入れ替え
CertFreeCertificateContext(pcCert);
pcCert = pcCert2;

// 秘密鍵の取得
BOOL hasPrivateKey = FALSE;
DWORD dwKeySpec = 0;
BOOL callerFreeProv = FALSE;
hasPrivateKey = CryptAcquireCertificatePrivateKey(
pcCert, 0, NULL, &hProv, &dwKeySpec, &callerFreeProv);

// 秘密鍵の鍵プロバイダ情報のサイズ取得
PCRYPT_KEY_PROV_INFO pKeyProvInfo = NULL;
DWORD dwSize = 0;
CertGetCertificateContextProperty(
pcCert, CERT_KEY_PROV_INFO_PROP_ID, NULL, &dwSize);
pKeyProvInfo = (PCRYPT_KEY_PROV_INFO)HeapAlloc(
GetProcessHeap(), 0, dwSize);

// 秘密鍵の鍵プロバイダ情報の取得
CertGetCertificateContextProperty(
pcCert, CERT_KEY_PROV_INFO_PROP_ID, pCryptKeyProvInfo, &dwSize);

// RSA-SHA2対応のプロバイダ(PROV_RSA_AES)の取得
HCRYPTPROV hProv2 = NULL;
CryptAcquireContextW(
&hProv2, pKeyProvInfo->pwszContainerName, NULL, PROV_RSA_AES, 0);
CryptReleaseContext(hProv, 0);
hProv = hProv2;

// ハッシュ生成(SHA-256)
HCRYPTHASH hHash = NULL;
CryptCreateHash(hProv, CALG_SHA_256, 0, 0, &hHash);

// 署名対象のハッシュ計算(std::vector<unsigned char> dataが署名対象)
CryptHashData(hHash, &data[0], (DWORD)data.size(), 0);

// 署名値サイズの取得と領域確保
CryptSignHash(hHash, dwKeySpec, NULL, 0, NULL, &dwSize);
std::vector<unsigned char> pbData;
pbData.resize(dwSize);

// 署名値の計算
CryptSignHash(hHash, dwKeySpec, NULL, 0, &pbData[0], &dwSize);

色々余計なことをしているかもしれませんこれで動作しました。ちょっとソースが長くてすみません。やっぱり.NETの方が楽ですねw ポイントとしては普通にPROV_RSA_FULLの秘密鍵を取得して、そこから秘密鍵の鍵プロバイダ情報をCertGetCertificateContextProperty()で取得し、pwszContainerNameを使ってPROV_RSA_AESの暗号プロバイダハンドルを取得しています。

なおWindows XPではSP3以降が必須となりますのでご注意下さい。検索してもCAPIでRSA-SHA2署名の情報があまり無かったので参考になれば幸いです。それにしてもRSA-SHA2問題は2010年問題とも言われていましたがもう2012年ですね(^^;
2012-03-13 13:23:06 - miyachi - - [PKI/暗号] -