Microsoft CryptoAPI (CAPI) 是 Windows 內建的加密服務接口,通過 **CSP (Cryptographic Service Provider)** 提供算法實現。本文系統講解 CAPI 核心對象模型、會話密鑰生成/導入導出/加密解密、公私鑰對管理、哈希計算、數字簽名驗證、密鑰存儲持久化,附完整文件加密解密示例代碼。
| 句柄類型 | 代表 | 獲取方式 |
|---|---|---|
HCRYPTPROV | CSP 上下文 (提供者容器) | CryptAcquireContext |
HCRYPTKEY | 密鑰對象 (會話/公鑰/私鑰) | CryptGenKey/CryptGetUserKey/CryptImportKey |
HCRYPTHASH | 哈希對象 | CryptCreateHash |
// 提供者
#define MS_ENHANCED_PROV "Microsoft Enhanced Cryptographic Provider v1.0"
#define MS_STRONG_PROV "Microsoft Strong Cryptographic Provider" // 高強度
#define PROV_RSA_FULL 1 // RSA 全功能
#define PROV_RSA_AES 24 // RSA + AES
// 常用算法 ID (ALG_ID)
#define CALG_3DES 0x00006603 // 3DES 對稱加密
#define CALG_AES_256 0x00006610 // AES-256
#define CALG_RSA_KEYX 0x0000a400 // RSA 密鑰交換
#define CALG_RSA_SIGN 0x0000a401 // RSA 簽名
#define CALG_SHA1 0x00008004 // SHA-1 (已弱)
#define CALG_SHA_256 0x0000800c // SHA-256
#define CALG_MD5 0x00008003 // MD5 (不可用於安全)
// 1. 獲取 CSP 句柄
HCRYPTPROV hProv;
CryptAcquireContext(&hProv, NULL, MS_ENHANCED_PROV, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT); // 無持久化容器
// 2. 生成會話密鑰 (如 AES-256)
HCRYPTKEY hSessionKey;
CryptGenKey(hProv, CALG_AES_256, CRYPT_EXPORTABLE, &hSessionKey);
// 3. 導出會話密鑰 (用公鑰加密,傳給對方)
// 3.1 獲取對方公鑰句柄 (或從證書導入)
HCRYPTKEY hPubKey; // 已有
// 3.2 導出為 SIMPLEBLOB (含加密的會話密鑰)
DWORD blobLen = 0;
CryptExportKey(hSessionKey, hPubKey, SIMPLEBLOB, 0, NULL, &blobLen);
BYTE* blob = new BYTE[blobLen];
CryptExportKey(hSessionKey, hPubKey, SIMPLEBLOB, 0, blob, &blobLen);
// 發送 blob 給對方...
// 4. 對方導入會話密鑰 (用私鑰解密)
HCRYPTKEY hImportedSessionKey;
CryptImportKey(hProv, blob, blobLen, hPrivKey, 0, &hImportedSessionKey);
// 5. 加密數據
BYTE* data = ...; DWORD dataLen = ...;
DWORD bufLen = dataLen + 16; // 預留填充
BYTE* buf = new BYTE[bufLen];
memcpy(buf, data, dataLen);
CryptEncrypt(hImportedSessionKey, 0, TRUE, 0, buf, &dataLen, bufLen);
// buf 前 dataLen 字節為密文
// 6. 解密數據
CryptDecrypt(hImportedSessionKey, 0, TRUE, 0, buf, &dataLen);
// buf 前 dataLen 字節為明文
// 7. 清理
CryptDestroyKey(hSessionKey);
CryptDestroyKey(hImportedSessionKey);
CryptReleaseContext(hProv, 0);
delete[] blob; delete[] buf;
// 創建/獲取持久化密鑰容器
CryptAcquireContext(&hProv, L"MyAppContainer", MS_ENHANCED_PROV, PROV_RSA_FULL, CRYPT_NEWKEYSET); // 首次創建
// 後續打開
CryptAcquireContext(&hProv, L"MyAppContainer", MS_ENHANCED_PROV, PROV_RSA_FULL, 0);
// 獲取密鑰對句柄
HCRYPTKEY hKeyEx, hSign;
CryptGetUserKey(hProv, AT_KEYEXCHANGE, &hKeyEx); // 密鑰交換對
CryptGetUserKey(hProv, AT_SIGNATURE, &hSign); // 簽名對
// 若不存在則生成
if (!hKeyEx) CryptGenKey(hProv, AT_KEYEXCHANGE, CRYPT_EXPORTABLE, &hKeyEx);
if (!hSign) CryptGenKey(hProv, AT_SIGNATURE, 0, &hSign);
// 計算文件 SHA-256 哈希
HCRYPTHASH hHash;
CryptCreateHash(hProv, CALG_SHA_256, 0, 0, &hHash);
while (ReadFile(hFile, buf, sizeof(buf), &read, NULL) && read) {
CryptHashData(hHash, buf, read, 0);
}
BYTE hashVal[32]; DWORD hashLen = 32;
CryptGetHashParam(hHash, HP_HASHVAL, hashVal, &hashLen, 0);
CryptDestroyHash(hHash);
// 數字簽名 (用私鑰簽名哈希)
CryptCreateHash(hProv, CALG_SHA_256, 0, 0, &hHash);
CryptHashData(hHash, data, dataLen, 0);
DWORD sigLen = 0;
CryptSignHash(hHash, AT_SIGNATURE, NULL, 0, NULL, &sigLen);
BYTE* sig = new BYTE[sigLen];
CryptSignHash(hHash, AT_SIGNATURE, NULL, 0, sig, &sigLen);
CryptDestroyHash(hHash);
// 驗證簽名 (用公鑰)
CryptCreateHash(hProv, CALG_SHA_256, 0, 0, &hHash);
CryptHashData(hHash, data, dataLen, 0);
BOOL ok = CryptVerifySignature(hHash, sig, sigLen, hPubKey, NULL, 0);
CryptDestroyHash(hHash);
// 加密文件
bool EncryptFile(const wstring& inPath, const wstring& outPath, const wstring& password) {
HCRYPTPROV hProv; CryptAcquireContext(&hProv, NULL, MS_ENHANCED_PROV, PROV_RSA_AES, CRYPT_VERIFYCONTEXT);
HCRYPTHASH hHash; CryptCreateHash(hProv, CALG_SHA_256, 0, 0, &hHash);
CryptHashData(hHash, (BYTE*)password.c_str(), password.size()*2, 0); // 簡易密碼哈希
HCRYPTKEY hKey; CryptDeriveKey(hProv, CALG_AES_256, hHash, CRYPT_EXPORTABLE, &hKey);
CryptDestroyHash(hHash);
HANDLE hIn = CreateFile(inPath.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
HANDLE hOut = CreateFile(outPath.c_str(), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
BYTE buf[4096]; DWORD read, written;
bool first = true, last = false;
while (ReadFile(hIn, buf, sizeof(buf), &read, NULL) && read) {
if (!ReadFile(hIn, NULL, 0, &read, NULL)) last = true; // 檢查是否最後一塊
DWORD len = read;
CryptEncrypt(hKey, 0, last, 0, buf, &len, sizeof(buf));
WriteFile(hOut, buf, len, &written, NULL);
}
CryptDestroyKey(hKey); CryptReleaseContext(hProv, 0);
CloseHandle(hIn); CloseHandle(hOut);
return true;
}
// 解密文件 (對稱操作,將 CryptEncrypt 換為 CryptDecrypt)
| 陷阱 | 說明 | 對策 |
|---|---|---|
| CRYPT_VERIFYCONTEXT 容器不持久化 | 密鑰生成後進程結束即丟失 | 持久化需指定容器名 + 刪除 CRYPT_VERIFYCONTEXT |
| 會話密鑰未導出屬性 | 無法 CryptExportKey | CryptGenKey 時加 CRYPT_EXPORTABLE |
| 緩衝區大小不足 | CryptEncrypt 可能擴充 (填充/分組) | 分配 dataLen + blockSize,分塊處理大文件 |
| 密碼直接做鑰材 | 極不安全,無鹽、無迭代 | 用 CryptDeriveKey + PBKDF2 (CAPI 2.0+) 或改用 CNG |
| SHA-1/MD5/3DES 已弱 | 不符合合規要求 | 強制 AES-256 + SHA-256/384/512 |
CryptoAPI 是 Windows 系統級加密基石。掌握「容器→提供者→密鑰→哈希/加密/簽名」對象鏈、Blob 格式導入導出、密鑰派生與持久化,能在無第三方依賴下構建合規的數據保護方案。