上次我們講了Microsoft CryptoAPI的構成以及會話密鑰的使用。接下來我們將看一下公私密鑰對的使用、HASH算法、數字簽名等技術。
一、 公用密鑰加密技術
公用密鑰加密技術使用兩個不同的密鑰:公鑰和私鑰。私鑰必須安全的保管好不能被外人知道,而公鑰可以告訴任何人,只要他需要。通常公鑰是以數字證書的形式發布的。
用公私密鑰對中的一個密鑰加密的數據只能用密鑰對中的另一個密鑰才能解密。也就是說用用戶A的公鑰加密的數據只能用A的私鑰才能解密,同樣,用A的私鑰加密的數據只能用A的公鑰才能解密。
如果用私鑰簽名一個消息,那麼必須用與之對應的公鑰去驗證簽名的有效性。
不幸的是公用密鑰加密技術的效率非常低甚至只有對稱加密的千分之一,所以不適合對大量的數據進行加密。實際上,公用密鑰加密技術一般用來加密會話密鑰,而數據加密可以用對稱加密的方法。
好了,讓我們回到Microsoft CryptoAPI。我們知道一個CSP有一個密鑰庫,這個密鑰庫有一個或多個密鑰容器。而密鑰容器中有什麼呢?一般來說,一個密鑰容器中有兩對公私密鑰對,一對用來加密會話密鑰,而另一對用來進行數字簽名,也就是大家知道的key exchange key pair和signature key pair。
那麼,怎麼得到這些密鑰對呢?
if(CryptGetUserKey(
hCryptProv, // 我們已經得到的CSP句柄
AT_SIGNATURE, // 這裡想得到signature key pair
&hKey)) // 返回密鑰句柄
{
printf("A signature key is available.\n");
}
else //取signature key pair錯誤
{
printf("No signature key is available.\n");
if(GetLastError() == NTE_NO_KEY) //密鑰容器裡不存在signature key pair
{
// 創建 signature key pair.
printf("The signature key does not exist.\n");
printf("Create a signature key pair.\n");
if(CryptGenKey(
hCryptProv, //CSP句柄
AT_SIGNATURE, //創建的密鑰對類型為signature key pair
0, //key類型,這裡用默認值
&hKey)) //創建成功返回新創建的密鑰對的句柄
{
printf("Created a signature key pair.\n");
}
else
{
printf ("Error occurred creating a signature key.\n");
}
}
else
{
printf ("An error other than NTE_NO_KEY getting signature\key.\n");
}
} // end if
將參數AT_SIGNATURE換成AT_KEYEXCHANGE就可以得到key exchange key pair。
現在我們得到的僅僅是一個句柄,我們需要把這個key值存儲的磁盤或文件中,這樣才能傳給對方來進行解密。下面讓我們來看一個用於導出密鑰的API。
BOOL WINAPI CryptExportKey(
hKey:需要被導出的密鑰句柄
HCRYPTKEY hKey,
HCRYPTKEY hExpKey,
DWORD dwBlobType,
DWORD dwFlags,
BYTE* pbData,
DWORD* pdwDataLen
);
hExpKey:前面咱們提到公用密鑰加密技術的效率非常低所以公用密鑰加密技術
一般用來加密會話密鑰。這裡傳入的密鑰就是用來加密被導出的密鑰
的。也就是說,被導出的密鑰hKey的數據是經過這個密鑰hExpKey
加密的。如果為NULL表示不經過加密直接導出。
dwBlobType:被導出的密鑰類型,比如公鑰還是私鑰等
dwFlags:標志位
pbData:保存導出的數據,如果為NULL, pdwDataLen將返回導出數據的長度
pdwDataLen:輸入pbData緩沖區的大小,輸出導出數據的長度
下面的例子演示如何導出密鑰。
if(CryptExportKey(
hKey,
NULL,
PUBLICKEYBLOB, //導出公鑰
0,
NULL,
&dwBlobLen)) //返回密鑰數據長度
{
printf("Size of the BLOB for the public key determined. \n");
}
else
{
printf("Error computing BLOB length.\n");
exit(1);
}
//--------------------------------------------------------------------
// Allocate memory for the pbKeyBlob.
if(pbKeyBlob = (BYTE*)malloc(dwBlobLen))
{
printf("Memory has been allocated for the BLOB. \n");
}
else
{
printf("Out of memory. \n");
exit(1);
}
//--------------------------------------------------------------------
// Do the actual exporting into the key BLOB.
if(CryptExportKey(
hKey,
NULL,
PUBLICKEYBLOB,
0,
pbKeyBlob, //返回密鑰數據
&dwBlobLen)) //導出的密鑰數據的長度
{
printf("Contents have been written to the BLOB. \n");
}
else
{
printf("Error exporting key.\n");
exit(1);
}
如果要導出用公用密鑰加密技術加密的密鑰,只要把API的第二個參數傳入一個key exchange key pair句柄就可以了。
既然有了導出當然要有導入。
BOOL WINAPI CryptImportKey(
這個API比較簡單,這裡就不舉例說明了,在以後的例子裡會看到。
HCRYPTPROV hProv, //CSP句柄
BYTE* pbData, //要導入的密鑰數據
DWORD dwDataLen, //數據長度
HCRYPTKEY hPubKey, //如果數據是被加密的這裡輸入解密用的密鑰句柄
DWORD dwFlags, //標志位
HCRYPTKEY* phKey //導入後返回的密鑰句柄
);
二、 HASH
Hash簡單點講就是把任意一段數據經過某種算法生成一段唯一的固定長度的數據。也叫做摘要。為了確保數據A免受意外或者故意(惡意)的修改,往往用這段數據A產生一個hash數據一起發送出去,接收方可以通過相同的hash算法用這段接收到的數據A產生一個新的hash數據並與接收到的hash數據比較,來驗證數據A是否為真實完整的數據。
下面的API用來創建hash對象
BOOL WINAPI CryptCreateHash(
HCRYPTPROV hProv, //CSP句柄
ALG_ID Algid, //選擇hash算法,比如CALG_MD5等
HCRYPTKEY hKey, //HMAC 和MAC算法時有用
DWORD dwFlags, //保留,傳入0即可
HCRYPTHASH* phHash //返回hash句柄
);
if(CryptCreateHash(
hCryptProv,
CALG_MD5,
0,
0,
&hHash))
{
printf("An empty hash object has been created. \n");
}
else
{
printf("Error during CryptBeginHash!\n");
exit(1);
}
// Insert code that uses the hash object here.
//--------------------------------------------------------------------
// After processing, hHash must be released.
if(hHash)
CryptDestroyHash(hHash); //釋放句柄
我們已經得到hash對象了,下面就找點數據試試,咱也去哈一下,當然這裡可不是哈日哈韓的哈,更不是哈巴狗的哈,嘿嘿。Let’s go!!
哎呀!!不好意思,忘記了介紹一個API,看看先。
BOOL WINAPI CryptHashData(
HCRYPTHASH hHash, //hash對象
BYTE* pbData, //被hash的數據
DWORD dwDataLen, //數據的長度
DWORD dwFlags //微軟的CSP這個值會被忽略
);
下面代碼:
BYTE *pbBuffer= (BYTE *)"The data that is to be hashed.";
DWORD dwBufferLen = strlen((char *)pbBuffer)+1;
if(CryptHashData(
hHash,
pbBuffer,
dwBufferLen,
0))
{
printf("The data buffer has been added to the hash.\n");
}
else
{
printf("Error during CryptHashData.\n");
exit(1);
}
現在,pbBuffer裡的內容已經被hash了,然後我們需要導出哈希後的數據。
BYTE *pbHash;
BYTE *pbHashSize;
DWORD dwHashLen = sizeof(DWORD);
DWORD i;
if(!(pbHashSize =(BYTE *) malloc(dwHashLen)))
MyHandleError("Memory allocation failed.");
//下面的這次調用我沒搞清楚:( 我怎麼覺得沒有必要!!
if(CryptGetHashParam(
hHash,
HP_HASHSIZE, //取hash數據的大小
pbHashSize, //輸出hash數據大小的緩沖區
&dwHashLen, //緩沖區大小
0))
{
// It worked. Free pbHashSize.
free(pbHashSize);
}
else
{
MyHandleError("CryptGetHashParam failed to get size.");
}
if(CryptGetHashParam(
hHash,
HP_HASHVAL, //取hash值
NULL, //設為NULL,在dwHashLen返回需要的輸出緩沖區大小
&dwHashLen, //輸出緩沖區大小
0))
{
// It worked. Do nothing.
}
else
{
MyHandleError("CryptGetHashParam failed to get length.");
}
if(pbHash = (BYTE*)malloc(dwHashLen))
{
// It worked. Do nothing.
}
else
{
MyHandleError("Allocation failed.");
}
if(CryptGetHashParam(
hHash,
HP_HASHVAL, //取hash值
pbHash, //返回Hash數據
&dwHashLen, //hash數據長度
0))
{
// Print the hash value.
printf("The hash is: ");
for(i = 0 ; i < dwHashLen ; i++)
{
printf("%2.2x ",pbHash[i]);
}
printf("\n");
}
else
{
MyHandleError("Error during reading hash value.");
}
free(pbHash);
三、 數字簽名
發布一個純文本形式信息時,接收者可以用數字簽名來鑒別和驗證信息的發送者。對信息簽名並不改變這個信息,只是生成一個數字簽名串隨信息一起傳送,或單獨傳送。
一個數字簽名,就是一段被用發送者的私鑰加密的數據段,而接收者只有擁有發送者的公鑰才能解密這個數據段。表示如下:
由Message生成數字簽名有兩步。首先,對Message進行hash處理,產生hash數據。然後用簽名者A的私鑰對這個hash數據加密。具體如下:
驗證一個簽名需要上圖表示的Message和Digital signatures。首先跟生成時一樣對Message進行hash處理,產生hash數據。然後通過簽名者A的公鑰、Digital signatures以及剛生成的hash數據進行驗證。具體如下:
好了,你是否學會數字簽名了呢?很多技術名詞聽起來很唬人,其實本來是很簡單的!!嘿嘿。
隨文檔的例程幾乎將用到我們上面講的所有內容。