CryptoAPI編程
(1) 微軟加密服務體系
微軟加密服務體系CryptoAPI的結構如下圖所示,微軟加密服務體系包含三層結構和兩個接口,分別為應用程序層、操作系統層(OS)、加密服務提供者層(Cryptographic Service Provider,CSP),CryptoAPI接口和加密服務提供者接口(Cryptographic Service Provider Interface,CSPF)。
(2)CryptoAPI體系結構
CryptoAPI體系架構共由五大主要部分組成:基本加密函數、證書編/解碼函數、證書庫管理函數、簡單消息函數、底層消息函數。體系結構如下圖所系:
基本加密函數:用於選擇CSP、建立CSP連接、產生密鑰、交換及傳輸密鑰等操作。
證書編/解碼函數:用於數據加密、解密、哈希等操作。這類函數支持數據的加密/解密操作;計算哈希、創建和校驗數字簽名操作;實現證書、證書撤銷列表、證書請求和證書擴展等編碼和解碼操作。
證書庫管理函數:用於數字證書及證書庫管理等操作。這組函數用於管理證書、證書撤銷列表和證書信任列表的使用、存儲、獲取等。
簡單消息函數:用於消息處理,比如消息編碼/解碼、消息加/解密、數字簽名及簽名驗證等操作。它是把多個底層消息函數包裝在一起以完成某個特定任務,方便用戶的使用。
底層消息函數:底層消息函數對傳輸的PKCS#7數據進行編碼,對接受到的PKCS#7數據進行解碼,並且對接收到的消息進行解碼和驗證。它可以實現簡單消息函數可以實現的所有功能,且提供更大的靈活性,但一般會需要更多的函數調用。
(3)CryptoAPI基本功能
利用CryptoAPI,開發者可以給基於Windows的應用程序添加安全服務,包括:ASN.1編碼/解碼、數據加密/解密、身份認證、數字證書管理,同時支持PKI、對稱密碼技術等。
密鑰管理
在CryptoAPI中,支持兩種類型的密鑰:會話密鑰、公/私密鑰對。會話密鑰也成為對稱密鑰,用於對稱加密算法。為了保證密鑰的安全性,在CryptoAPI中,這些密鑰都保存在CSP內部,用戶可以通過CryptExpoetKey以加密密鑰快形式導出。公/私鑰對用於非對稱加密算法。非對稱加密算法主要用於加解密會話密鑰和數字簽名。在CryptoAPI中,一般來說,大多數CSP產生的密鑰容器包含兩對密鑰對,一對用於加密會話密鑰,稱為交換密鑰對,一對用於產生數字簽名,稱為簽名密鑰對。在CryptoAPI中,所有的密鑰都存儲在CSP中,CSP負責密鑰的創建,銷毀,導入導出等操作。
數據編碼/解碼
CryptoAPI采用的編碼方式為ASN.1,編碼規則為DER,表示發送方發送數據時先把數據抽象為ASN.1對象,然後使用DER編碼規則把ASN.1對象轉化為可傳輸的0、1串;接受方接受到數據後,利用DER解碼規則把0、1串轉化為ASN.1對象,然後把ASN.1對象轉化為具體應用支持的數據對象。
數據加/解密
在CryptoAPI中約定加密較大的數據塊時,采用對稱加密算法。通過其封裝好的加解密函數來實現數據解加密操作。
哈希與數字簽名
哈希與數字簽名一般用於數據的完整性驗證和身份鑒別。CryptoAPI中,通過其封裝好的哈希與數字簽名函數來實現相關操作。微軟公司提供的CSP產生的數字簽名遵循RSA標准(PKCS#6).
數字證書管理
數字證書主要用於安全通信中的身份鑒別。CryptoAPI中,對數字證書的使用管理函數分為證書與證書庫函數、證書驗證函數兩大部分。
在VC++中開發CryptoAPI應用程序,需要預先設置一些編譯環境。
1.需要包含以下頭文件:
#include <windows.h>
#include <wincrypt.h>
2.包含的靜態鏈接庫:
鏈接CryptoAPI函數必須有靜態庫Crypto32.lib的支持,部分CryptoAPI函數可能還需要靜態庫advapi32.lib及CryptUI.lib的支持。
3.假如在VC++6.0上編譯程序,則還需加上以下語句:
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0400
#endif
在不同的版本的windows操作系統下,可能需要定義不同的常量,具體查看wincrypt.h頭文件,根據wincrypt.h上不同的預編譯語句在自己的應用程序中進行不同定義。(我在VS 2008環境中編譯程序,不在需要自定義這部分)。在vs2008的wincrypt.h頭文件已經沒有這些相關的定義。)
注:部分的CryptoAPI函數在VC++6.0上並沒有定義,如CertGetNameString函數為CryptoAPI的證書管理函數,但是在VC++6.0下編譯時會報錯,查看相應的wincryp.h文件時會發現裡面沒有聲明該函數。但在VC++7.0以上的版本中則定義了這個函數。解決方法是可以將VC++7.0上的wincrypt.h、crypt32.lib、advapi32.lib三個文件覆蓋vc+6.0的相應文件。
以下介紹幾個編寫CryptoAPI應用程序常用到得函數。
1.BOOLEAN CRYPTFUNC CryptAcquireContext(
HCRYPTPROV* phProv, CSP句柄
LPCTSTR pszContainer, 密鑰容器名稱,指向密鑰容器的字符串指針
LPCTSTR pszProvider, 指向CSP名稱的字符串指針,如果為NULL,則使用默認的CSP
DWORD dwProvType, CSP類型
DWORD dwFlags 標志
);
這個函數是為了獲得CSP句柄,函數通過phProv參數返回獲得的CSP句柄。在CryptoAPI加密服務相關的所有操作都在CSP實現,CSP真正實行加密相關服務的獨立模塊,當應用程序需要加密相關服務時,比如:加解密操作、密鑰產生於管理等,必須先獲取某個CSP句柄。這時一般CryptoAPI編程的第一步。
2.BOOL CRYPTFUNC CryptGenKey(
HCRYPTPROV hProv, //CSP句柄
ALG_ID Algid, //算法標志ID值。創建會話密鑰時,它指定具體的加解密算法。指定算法時應注意具體的
// CSP是否支持此算法。創建公/私密鑰對時,參數應為AT_KEYEXCHANGE(交換密鑰對)
//或AT_SIGNATURE(簽名密鑰對)。
DWORD dwFlags, //說明創建密鑰的長度及其它屬性。
HCRYPTKEY* phKey //新創建密鑰句柄,函數通過這個參數返回創建密鑰句柄。
);
在CryptoAPI中,構造密鑰一般有兩種方法,一通過哈希值,而通過隨機數構造。上面這種就是通過隨機數創建的。下面介紹利用哈希值創建的函數。
BOOL CRYPTFUNC CryptDeriveKey(
HCRYPTPROV hProv,
ALG_ID Algid, //要產生密鑰的對稱加密算法
HCRYPTHASH hBaseData, //哈希句柄,函數根據這個哈希句柄創建密鑰。
DWORD dwFlags, //指定密鑰的類型。
HCRYPTKEY* phKey //密鑰句柄,函數通過這個參數返回創建的密鑰句柄。
);
這個函數通過輸入的哈希值hBaseData來創建一個密鑰,通過密鑰句柄phKey參數返回。注意:這個函數只能創建會話密鑰,不能用於創建公/私密鑰對。
3.BOOL CRYPTFUNC CryptCreateHash(
HCRYPTPROV hProv, //CSP句柄
ALG_ID Algid, //哈希算法標識符
HCRYPTKEY hKey, // 如果哈希算法是密鑰哈希,如HMACH或者MAC算法,就用此密鑰句柄傳遞密鑰。
//對於非密鑰算法,此參數為NULL。
DWORD dwFlags, //保留,必須為0
HCRYPTHASH* phHash //哈希句柄,函數通過這個參數返回創建的哈希對象句柄。
);
這個函數初始化一個哈希句柄,它創建並返回一個CSP哈希句柄。
4.BOOL WINAPI CryptHashData(
HCRYPTHASH hHash, //哈希句柄,創建的哈希值通過這個句柄返回
BYTE* pbData, //指向要加入到哈希句柄的數據指針
DWORD dwDataLen, // 數據長度
DWORD dwFlags //標志
);
這個函數是計算一段數據的哈希值並加入到指定的哈希句柄中。在使用這個函數前必須通過CrpytHashData函數創建了一個哈希句柄。
5.BOOL WINAPI CryptEncodeObject(
__in DWORD dwCertEncodingType, //使用的編碼類型。通常為 X509_ASN_ENCODING |
//PKCS_7_ASN_ENCODING
__in LPCSTR lpszStructType, //要編碼的結構體類型
__in const void* pvStructInfo, //欲編碼的結構體指針,要和lpszStructType類型一致
__out BYTE* pbEncoded, //編碼後結構體指針,當設置為NULL時用於獲取其長度
__in_out DWORD* pcbEncoded //編碼後的結構體長度
);
這個函數用於將pvStructInfo所指向的數據按照lpszStructType結構體類型編碼。
6.BOOL WINAPI CryptDecodeObject(
__in DWORD dwCertEncodingType,
__in LPCSTR lpszStructType,
__in const BYTE* pbEncoded,
__in DWORD cbEncoded,
__in DWORD dwFlags,
__out void* pvStructInfo,
__in_out DWORD* pcbStructInfo
);
這個函數是對上面編碼後的數據進行解碼,參數和上面編碼函數的參數差不多,具體可以查看MSDN幫助文檔。
1.CERT_RDN_ATTR 結構體
typedef struct _CERT_RDN_ATTR {
LPSTR pszObjId;
DWORD dwValueType;
CERT_RDN_VALUE_BLOB Value;
} CERT_RDN_ATTR,
*PCERT_RDN_ATTR;
pszObjId:對象標識符,用於標識證書屬性,具體可以查看MSDN中的解析,也可以查看wincrypt.h文件查看相應的定義。譬如szOID_STATE_OR_PROVINCE_NAME,表示省名。
dwValueType:對成員Value的解析,取值查看MSDN,當主要是初始化證書屬性時,Value的值主要是一些字符串時,該值可以為CERT_RDN_PRINTABLE_STRING,表示可以打印的字符串。
Value:一個結構體,在這裡初始化證書屬性。
typedef struct _CRYPTOAPI_BLOB {
DWORD cbData;
BYTE* pbData;
} ,其中cbData表示大小,pbData指向一個內存空間。
2.CERT_RDN 結構體:The CERT_RDN structure contains a relative distinguished name (RDN) consisting of an array of CERT_RDN_ATTR structures.
typedef struct _CERT_RDN {
DWORD cRDNAttr;
PCERT_RDN_ATTR rgRDNAttr;
} CERT_RDN,
*PCERT_RDN;
參數:cRDNAttr:rgRDNAttr數組元素的個數;rgRDNAttr:指向CERT_RDN_ATTR結構元素的數組地址。
3.CERT_NAME_INFO 結構體:The CERT_NAME_INFO structure contains subject or issuer names.The information is represented as an array of CERT_RDN structures.
typedef struct _CERT_NAME_INFO {
DWORD cRDN;
PCERT_RDN rgRDN;
} CERT_NAME_INFO,
*PCERT_NAME_INFO;
參數:同上差不多。
4.CERT_REQUEST_INFO 證書請求結構體:這個結構體包含證書請求的主體,主體公鑰,屬性塊等信息,這些信息都是經過編碼的。
typedef struct _CERT_REQUEST_INFO {
DWORD dwVersion;
CERT_NAME_BLOB Subject;
CERT_PUBLIC_KEY_INFO SubjectPublicKeyInfo;
DWORD cAttribute;
PCRYPT_ATTRIBUTE rgAttribute;
} CERT_REQUEST_INFO,
*PCERT_REQUEST_INFO;
參數:dwVersion:證書版本號,可以為CERT_V1等,根據屬性擴展情況,符合不同版本證書;Subject:證書主題;SubjectPublicKeyInfo:證書主題中的公鑰信息;cAttribute:rgAttribute數組元素個數,可以為0;rgAttribute:屬性參數數組,可以為NULL;
以上信息都是要經過編碼後的信息來填充的。
5.CryptSignAndEncodeCertificate函數,用來創建自簽名證書
BOOL WINAPI CryptSignAndEncodeCertificate(
__in HCRYPTPROV_OR_NCRYPT_KEY_HANDLE hCryptProvOrNCryptKey,
__in DWORD dwKeySpec,
__in DWORD dwCertEncodingType,
__in LPCSTR lpszStructType,
__in const void* pvStructInfo,
__in PCRYPT_ALGORITHM_IDENTIFIER pSignatureAlgorithm,
__in const void* pvHashAuxInfo,
__out PBYTE pbEncoded,
__in_out DWORD* pcbEncoded
);
參數:1,CSP句柄;2,指明公鑰是來自簽名公鑰還是交換公鑰,可以為AT_KEYEXCHANGE或者AT_SIGNATURE之一;3,指明編碼類型,可以為X509_ASN_ENCODING;4,結構體類型,和第5個參數配合起來使用,可以為X509_CERT_CRL_TO_BE_SIGNED或者X509_CERT_REQUEST_TO_BE_SIGNED或者X509_CERT_TO_BE_SIGNED或者X509_KEYGEN_REQUEST_TO_BE_SIGNED,意思可以查看MSDN。
6,簽名算法結構體,指明簽名算法,算法標識可以為szOID_RSA_MD5RSA 或者szOID_RSA_SHA1RSA 或者szOID_X957_SHA1DSA ;7,可以不用,設為NULL;8,簽名後數據的長度,當設為NULL時,可以用來求數據的長度;9,用於存放數據的內存空間。
(4)使用CryptoAPI創建一個自簽名證書
下面的 c + + 示例演示如何使用 CertCreateSelfSignCertificate API 來創建一個自簽名的證書。將計算機配置文件中創建私鑰/公鑰和證書將存儲該同一配置文件的受信任根 CA 存儲中:
#include "stdio.h" #include "conio.h" #include "windows.h" #include "wincrypt.h" #include "tchar.h" int SelfSignedCertificateTest() { // CREATE KEY PAIR FOR SELF-SIGNED CERTIFICATE IN MACHINE PROFILE HCRYPTPROV hCryptProv = NULL; HCRYPTKEY hKey = NULL; __try { // Acquire key container _tprintf(_T("CryptAcquireContext... ")); if (!CryptAcquireContext(&hCryptProv, _T("alejacma"), NULL, PROV_RSA_FULL, CRYPT_MACHINE_KEYSET)) { // Error _tprintf(_T("Error 0x%x\n"), GetLastError()); // Try to create a new key container _tprintf(_T("CryptAcquireContext... ")); if (!CryptAcquireContext(&hCryptProv, _T("alejacma"), NULL, PROV_RSA_FULL, CRYPT_NEWKEYSET | CRYPT_MACHINE_KEYSET)) { // Error _tprintf(_T("Error 0x%x\n"), GetLastError()); return 0; } else { _tprintf(_T("Success\n")); } } else { _tprintf(_T("Success\n")); } // Generate new key pair _tprintf(_T("CryptGenKey... ")); if (!CryptGenKey(hCryptProv, AT_SIGNATURE, 0x08000000 /*RSA-2048-BIT_KEY*/, &hKey)) { // Error _tprintf(_T("Error 0x%x\n"), GetLastError()); return 0; } else { _tprintf(_T("Success\n")); } } __finally { // Clean up if (hKey) { _tprintf(_T("CryptDestroyKey... ")); CryptDestroyKey(hKey); _tprintf(_T("Success\n")); } if (hCryptProv) { _tprintf(_T("CryptReleaseContext... ")); CryptReleaseContext(hCryptProv, 0); _tprintf(_T("Success\n")); } } // CREATE SELF-SIGNED CERTIFICATE AND ADD IT TO ROOT STORE IN MACHINE PROFILE PCCERT_CONTEXT pCertContext = NULL; BYTE *pbEncoded = NULL; HCERTSTORE hStore = NULL; HCRYPTPROV_OR_NCRYPT_KEY_HANDLE hCryptProvOrNCryptKey = NULL; BOOL fCallerFreeProvOrNCryptKey = FALSE; __try { // Encode certificate Subject //