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

#include "stdafx.h"
#include <Windows.h>
#include <WinCrypt.h>

#define	CERT_SUBJ_STR	L"電子証明書の所有者名に変えてください"

int _tmain(int argc, _TCHAR* argv[])
{
	HCRYPTPROV	hProv;
	HCERTSTORE	hStore;

	//	CSPハンドルの取得
	if(!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, NULL))
	{
		if(!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_NEWKEYSET))
		{
			fprintf(stderr, "CryptAcquireContext error\n");
			return 1;
		}
	}

	//	証明書ストアーのオープン
	hStore = CertOpenSystemStore(
				hProv,
				"MY");
	if(!hStore)
	{
		fprintf(stderr, "CertOpenSystemStore error\n");
		return 2;
	}

	//	自分の電子証明書の取り出し
	PCCERT_CONTEXT	pcCert;
	char	name[100];

	pcCert = NULL;
	while(pcCert = CertFindCertificateInStore(
						hStore,
						X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
						0,
						CERT_FIND_SUBJECT_STR,
						CERT_SUBJ_STR,
						pcCert))
	{
		CertNameToStr(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, &pcCert->pCertInfo->Subject, CERT_SIMPLE_NAME_STR, name, 100);
		printf("Name: %s\n", name);
		break;
	}
	if(!pcCert)
	{
		fprintf(stderr, "指定された電子証明書が見つかりませんでした。");
		return 3;
	}

	//	暗号化の準備
	PCCERT_CONTEXT				recipients[1];		//	受取人の電子証明書
	CRYPT_ALGORITHM_IDENTIFIER	encAlg;				//	暗号化のアルゴリズム
	CRYPT_ENCRYPT_MESSAGE_PARA	encPara;				//	暗号化のパラメーター

	recipients[0] = pcCert;							//	受取人リストに電子証明書をセットする
	memset(&encAlg, 0, sizeof(encAlg));
	encAlg.pszObjId = szOID_RSA_RC4;				//	暗号化の方式として、RC4を設定

	memset(&encPara, 0, sizeof(encPara));
	encPara.cbSize = (DWORD)sizeof(encPara);
	encPara.dwMsgEncodingType = X509_ASN_ENCODING | PKCS_7_ASN_ENCODING;
	encPara.hCryptProv = hProv;						//	CSPのハンドル
	encPara.ContentEncryptionAlgorithm = encAlg;	//	データを暗号化するアルゴリズム

	//	暗号化
	BYTE	*pbCont = (BYTE*)"このデータを暗号化します。";	//	暗号化されるデータ
	DWORD	cbCont = (DWORD)strlen((char*)pbCont) + 1;		//	データのサイズ
	BYTE	*pbEncBlob;								//	暗号化データの格納域
	DWORD	cbEncBlob;								//	暗号化データのサイズ

	if(!CryptEncryptMessage(
				&encPara,		//	暗号化パラメーター
				1,				//	受取人の数
				recipients,		//	受取人の証明書アレー
				pbCont,			//	暗号化されるデータ
				cbCont,			//	そのサイズ
				NULL,			//	暗号化データのサイズがわからないのでNULLを指定
				&cbEncBlob))	//	暗号化データのサイズが戻される
	{
		fprintf(stderr, "暗号化に失敗しました。\n");
		return 4;
	}

	//	暗号化データの格納領域を確保してから再度暗号化を実施
	pbEncBlob = new BYTE [cbEncBlob];
	if(!CryptEncryptMessage(
				&encPara,		//	暗号化パラメーター
				1,				//	受取人の数
				recipients,		//	受取人の証明書アレー
				pbCont,			//	暗号化されるデータ
				cbCont,			//	そのサイズ
				pbEncBlob,		//	暗号化データの格納域ポインター
				&cbEncBlob))	//	暗号化データのサイズが戻される
	{
		fprintf(stderr, "暗号化に失敗しました。\n");
		return 5;
	}

	printf("Data size = %d\n", cbEncBlob);

	//	復号の準備
	HCERTSTORE					certStores[1];
	CRYPT_DECRYPT_MESSAGE_PARA	decPara;
	DWORD		cbDecMsg;
	BYTE		*pbDecMsg;

	certStores[0] = hStore;
	memset(&decPara, 0, sizeof(decPara));
	decPara.cbSize = sizeof(decPara);
	decPara.dwMsgAndCertEncodingType = X509_ASN_ENCODING | PKCS_7_ASN_ENCODING;
	decPara.cCertStore = 1;
	decPara.rghCertStore = certStores;

	//	復号
	if(!CryptDecryptMessage(
					&decPara,	//	パラメーター
					pbEncBlob,	//	暗号化データの格納ポインター
					cbEncBlob,	//	暗号化データのサイズ
					NULL,		//	データの大きさがわからないのでNULL
					&cbDecMsg,	//	大きさが戻る
					NULL))		//	暗号化されたデータに含まれている電子証明書
	{
		fprintf(stderr, "復号に失敗しました。\n");
		return 6;
	}

	//	復号したデータを格納する領域を確保して再度復号
	pbDecMsg = new BYTE [cbDecMsg];
	if(!CryptDecryptMessage(
					&decPara,
					pbEncBlob,
					cbEncBlob,
					pbDecMsg,	//	確保した格納域を指定
					&cbDecMsg,
					NULL))
	{
		fprintf(stderr, "復号に失敗しました。\n");
		return 7;
	}

	printf("復号したデータは、「%s」です。\n", pbDecMsg);

	//	後始末
	delete [] pbDecMsg;
	delete [] pbEncBlob;
	CertFreeCertificateContext(pcCert);
	CertCloseStore(hStore, 0);
	CryptReleaseContext(hProv, 0);

	return 0;
}