EternalWindows
PKCS #7 / EnvelopedData型と暗号化

暗号化データをPKCS #7形式でエンコードする場合、そのエンコードされたデータはEnvelopedData型と呼ばれます。 EnvelopedData型に含まれる主な内容は、暗号化データ、受取人の情報(SerialNumberとIssuerとPublicKeyAlgorithm)、 公開鍵で暗号化されたセッション鍵の3つです。 受取人というのは、このEnvelopedData型のデータを受け取るアプリケーションのことで、 そのようなアプリケーションは事前に自身の証明書を公開しておくことになります。 これにより、データを証明書に含まれる公開鍵で暗号化することができ、 それを複合化できるのは証明書に関連する秘密鍵を持つ受取人だけとなります。 受取人の証明書は、CryptSignMessageで作成された署名データなどから取得することができます。

より厳密に言うと、データの暗号化に使われる鍵はセッション鍵です。 これには、公開鍵よりセッション鍵の方が暗号化が高速であるという理由もありますが、 複数の受取人にデータを送る際の効率性を上げる狙いもあります。 ある1つのデータがあり、それを複数の受取人に送ることになった場合、 受取人の数だけデータを受取人の公開鍵で暗号化するのは大変ですから、 データをセッション鍵で暗号化し、そのセッション鍵を受取人の数だけ暗号化して添付するようにしているのです。 次に示すCryptEncryptMessageは、これらの作業を全て行います。

BOOL WINAPI CryptEncryptMessage(
  PCRYPT_ENCRYPT_MESSAGE_PARA pEncryptPara,
  DWORD cRecipientCert,
  PCCERT_CONTEXT rgpRecipientCert[ ],
  const BYTE *pbToBeEncrypted,
  DWORD cbToBeEncrypted,
  BYTE *pbEncryptedBlob,
  DWORD *pcbEncryptedBlob
);

pEncryptParaは、暗号化情報を含むCRYPT_ENCRYPT_MESSAGE_PARA構造体のアドレスを指定します。 cRecipientCertは、rgpRecipientCertの要素数を指定します。 rgpRecipientCertは、暗号化されたデータを受け取る受取人の証明書コンテキストを指定します。 pbToBeEncryptedは、暗号化したいデータを指定します。 cbToBeEncryptedは、暗号化したいデータのサイズを指定します。 pbEncryptedBlobは、暗号化されたデータを受け取るバッファを指定します。 pcbEncryptedBlobは、データを受け取るバッファのサイズを指定します。 pbEncryptedBlobをNULLに指定している場合は、データのサイズが返ります。

CRYPT_ENCRYPT_MESSAGE_PARA構造体のアドレスを指定します。

typedef struct _CRYPT_ENCRYPT_MESSAGE_PARA {
  DWORD                       cbSize;
  DWORD                       dwMsgEncodingType;
  HCRYPTPROV_LEGACY           hCryptProv;
  CRYPT_ALGORITHM_IDENTIFIER  ContentEncryptionAlgorithm;
  void                        *pvEncryptionAuxInfo;
  DWORD                       dwFlags;
  DWORD                       dwInnerContentType;
} CRYPT_ENCRYPT_MESSAGE_PARA, *PCRYPT_ENCRYPT_MESSAGE_PARA;

cbSizeは、構造体のサイズをバイト単位で指定します。 dwMsgEncodingTypeは、エンコーディングタイプを指定します。 PKCS #7形式にエンコードするので、PKCS_7_ASN_ENCODINGを指定することになります。 hCryptProvは、暗号化するためのCSPのハンドルを指定します。 0を指定した場合、デフォルトのRSAプロバイダーが利用されます。 Windows Vista以降では、0を指定するようにします。 ContentEncryptionAlgorithmは、暗号化アルゴリズムを表すCRYPT_ALGORITHM_IDENTIFIER構造体のアドレスを指定します。 pvEncryptionAuxInfoは、暗号化アルゴリズムを補足するための情報を含む構造体のアドレスを指定します。 指定する構造体は、暗号化アルゴリズムによって異なりますが、基本的にはNULLを指定することになります。 dwFlagsとdwInnerContentTypeは、基本的に0を指定します。

今回のプログラムは、CryptEncryptMessageを呼び出してデータの暗号化を行います。 受取人の証明書は、前々節で作成したsign.datより取得し、 そこに含まれる証明書の公開鍵は、AT_KEYEXCHANGEの用途であるものとします。

#include <windows.h>

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

PCCERT_CONTEXT GetRecipientContext(LPTSTR lpszFileName);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	CRYPT_ENCRYPT_MESSAGE_PARA encryptPara;
	CRYPT_ALGORITHM_IDENTIFIER encryptAlgorithm;
	PCCERT_CONTEXT             pRecipientContext;
	LPCBYTE                    lpData = (LPBYTE)TEXT("sample-data");
	DWORD                      dwDataSize = sizeof(TEXT("sample-data"));
	BOOL                       bResult;
	LPBYTE                     lpEncryptedBlob;
	DWORD                      dwEncryptedBlobSize;
	HANDLE                     hFile;
	DWORD                      dwWriteByte;

	pRecipientContext = GetRecipientContext(TEXT("sign.dat"));
	if (pRecipientContext == NULL) {
		MessageBox(NULL, TEXT("受取人の証明書の取得に失敗しました。"), NULL, MB_ICONWARNING);
		return 0;
	}

	ZeroMemory(&encryptAlgorithm, sizeof(CRYPT_ALGORITHM_IDENTIFIER));
	encryptAlgorithm.pszObjId = szOID_RSA_RC4;

	encryptPara.cbSize                     = sizeof(CRYPT_ENCRYPT_MESSAGE_PARA);
	encryptPara.dwMsgEncodingType          = PKCS_7_ASN_ENCODING;
	encryptPara.hCryptProv                 = 0;
	encryptPara.ContentEncryptionAlgorithm = encryptAlgorithm;
	encryptPara.pvEncryptionAuxInfo        = NULL;
	encryptPara.dwFlags                    = 0;
	encryptPara.dwInnerContentType         = 0;

	bResult = CryptEncryptMessage(&encryptPara, 1, &pRecipientContext, lpData, dwDataSize, NULL, &dwEncryptedBlobSize);
	if (!bResult) {
		MessageBox(NULL, TEXT("データの暗号化に失敗しました。"), NULL, MB_ICONWARNING);
		CertFreeCertificateContext(pRecipientContext);
		return 0;
	}
	
	lpEncryptedBlob = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwEncryptedBlobSize);

	bResult = CryptEncryptMessage(&encryptPara, 1, &pRecipientContext, lpData, dwDataSize, lpEncryptedBlob, &dwEncryptedBlobSize);
	if (!bResult) {
		MessageBox(NULL, TEXT("データの暗号化に失敗しました。"), NULL, MB_ICONWARNING);
		HeapFree(GetProcessHeap(), 0, lpEncryptedBlob);
		CertFreeCertificateContext(pRecipientContext);
		return 0;
	}

	MessageBox(NULL, TEXT("データを暗号化しました。"), TEXT("OK"), MB_OK);

	hFile = CreateFile(TEXT("encrypt.dat"), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
	WriteFile(hFile, lpEncryptedBlob, dwEncryptedBlobSize, &dwWriteByte, NULL);
	CloseHandle(hFile);
	
	HeapFree(GetProcessHeap(), 0, lpEncryptedBlob);
	CertFreeCertificateContext(pRecipientContext);
	
	return 0;
}

PCCERT_CONTEXT GetRecipientContext(LPTSTR lpszFileName)
{
	CRYPT_VERIFY_MESSAGE_PARA verifyPara;
	LPBYTE                    lpData;
	DWORD                     dwDataSize;
	BOOL                      bResult;
	LPBYTE                    lpSignedBlob;
	DWORD                     dwSignedBlobSize;
	HANDLE                    hFile;
	DWORD                     dwReadByte;
	PCCERT_CONTEXT            pSignerContext;

	hFile = CreateFile(lpszFileName, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if (hFile == INVALID_HANDLE_VALUE)
		return NULL;

	dwSignedBlobSize = GetFileSize(hFile, NULL);
	lpSignedBlob = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwSignedBlobSize);
	ReadFile(hFile, lpSignedBlob, dwSignedBlobSize, &dwReadByte, NULL);
	CloseHandle(hFile);	
	
	verifyPara.cbSize                   = sizeof(CRYPT_VERIFY_MESSAGE_PARA);
	verifyPara.dwMsgAndCertEncodingType = X509_ASN_ENCODING | PKCS_7_ASN_ENCODING;
	verifyPara.hCryptProv               = 0;
	verifyPara.pfnGetSignerCertificate  = NULL;
	verifyPara.pvGetArg                 = NULL;

	bResult = CryptVerifyMessageSignature(&verifyPara, 0, lpSignedBlob, dwSignedBlobSize, NULL, &dwDataSize, NULL);
	if (!bResult)
		return NULL;

	lpData = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwDataSize);
 
	bResult = CryptVerifyMessageSignature(&verifyPara, 0, lpSignedBlob, dwSignedBlobSize, lpData, &dwDataSize, &pSignerContext);
	if (!bResult) {
		HeapFree(GetProcessHeap(), 0, lpSignedBlob);
		return NULL;
	}
	
	HeapFree(GetProcessHeap(), 0, lpSignedBlob);

	return pSignerContext;
}

まずは、証明書の取得をローカルの証明書ストアではなく、 sign.datより取得する理由を明確にしておきましょう。 ローカルの証明書で暗号化を行った場合、 その暗号化データを複合化するアプリケーションは、 暗号化を行ったアプリケーションと同一のコンピュータ上で実行する必要性が生じます。 これは、ローカルの証明書に関連する秘密鍵が、 同じローカルコンピュータ上にしか存在しないためです。 これでは、自分で暗号化して自分で暗号化データを受け取っていることになるため、 別コンピュータからの暗号化も行えるように、 証明書を外部から取得する必要があります。 sign.datを対象にしているのは、受取人が証明書の公開を署名を通じて行えるからであり、 この署名は前々節で作成していますから、それを利用するのが効率的です。 次に、受取人の証明書を取得するGetRecipientContextの一部を示します。

bResult = CryptVerifyMessageSignature(&verifyPara, 0, lpSignedBlob, dwSignedBlobSize, lpData, &dwDataSize, &pSignerCert);

GetRecipientContextの内部は、基本的に前節と同様にCryptVerifyMessageSignatureを呼び出すコードとなっています。 しかし、今回は署名されたデータから署名者の証明書を取得する必要がありますから、 CryptVerifyMessageSignatureの最終引数には証明書コンテキストのアドレスを指定します。 この署名者の証明書が受取人の証明書となります。

CryptEncryptMessageの呼び出しに関わるコードを次に示します。

ZeroMemory(&encryptAlgorithm, sizeof(CRYPT_ALGORITHM_IDENTIFIER));
encryptAlgorithm.pszObjId = szOID_RSA_RC4;

encryptPara.cbSize                     = sizeof(CRYPT_ENCRYPT_MESSAGE_PARA);
encryptPara.dwMsgEncodingType          = PKCS_7_ASN_ENCODING;
encryptPara.hCryptProv                 = 0;
encryptPara.ContentEncryptionAlgorithm = encryptAlgorithm;
encryptPara.pvEncryptionAuxInfo        = NULL;
encryptPara.dwFlags                    = 0;
encryptPara.dwInnerContentType         = 0;

bResult = CryptEncryptMessage(&encryptPara, 1, &pRecipientCert, lpData, dwDataSize, NULL, &dwEncryptedBlobSize);

encryptAlgorithmはCRYPT_ALGORITHM_IDENTIFIER型であり、暗号化アルゴリズムを設定しておくことになります。 ここでは、共通鍵暗号にRC4を使うszOID_RSA_RC4を指定しています。 encryptParaはCRYPT_ENCRYPT_MESSAGE_PARA型であり、CryptEncryptMessageの呼び出しに必要となります。 dwMsgEncodingTypeにはPKCS_7_ASN_ENCODINGを指定し、ContentEncryptionAlgorithmには 先に初期化した暗号化アルゴリズムを代入しています。 CryptEncryptMessageの第2引数の1は、暗号化データの受取人が1人ということであり、 2人以上の場合は第3引数が証明書の配列となるでしょう。 このCryptEncryptMessageの呼び出しによって、暗号化データのサイズが取得されるため、 後はその分のメモリを確保し、2回目のCryptEncryptMessageで暗号化データを取得することになります。


戻る