基於VC++2010實現雜湊簽名與驗證
數字簽名即如何給一個計算機檔案進行簽字。數字簽字可以用對稱演算法實現,也可以用公鑰演算法實現。但前者除了檔案簽字者和檔案接受者雙方,還需要第三方認證,較麻煩;通過公鑰加密演算法的實現方法,由於用祕密金鑰加密的檔案,需要靠公開金鑰來解密,因此這可以作為數字簽名,簽名者用祕密金鑰加密一個簽名(可以包括姓名、證件號碼、簡訊息等資訊),接收人可以用公開的、自己的公開金鑰來解密,如果成功,就能確保資訊來自該公開金鑰的所有人。
公鑰密碼體制實現數字簽名的基本原理很簡單,假設A要傳送一個電子檔案給B,A、B雙方只需經過下面三個步驟即可:
1. A用其私鑰加密檔案,這便是簽字過程
2. A將加密的檔案送到B
3. B用A的公鑰解開A送來的檔案
這樣的簽名方法是符合可靠性原則的。即:
簽字是可以被確認的,
簽字是無法被偽造的,
簽字是無法重複使用的,
檔案被簽字以後是無法被篡改的,
簽字具有無可否認性,
數字簽名就是通過一個單向函式對要傳送的報文進行處理得到的用以認證報文來源並核實報文是否發生變化的一個字母數字串。用這幾個字串來代替書寫簽名或印章,起到與書寫簽名或印章同樣的法律效用。國際社會已開始制定相應的法律、法規,把數字簽名作為執法的依據。
數字簽名的實現方法
實現數字簽名有很多方法,目前數字簽名採用較多的是公鑰加密技術,如基於RSA Data Security公司的PKCS(Public Key Cryptography Standards)、DSA(Digital Signature Algorithm)、x.509、PGP(Pretty Good Privacy)。1994年美國標準與技術協會公佈了數字簽名標準(DSS)而使公鑰加密技術廣泛應用。同時應用雜湊演算法(Hash)也是實現數字簽名的一種方法。
非對稱金鑰密碼演算法進行數字簽名
演算法的含義:
非對稱金鑰密碼演算法使用兩個金鑰:公開金鑰和私有金鑰,分別用於對資料的加密和解密,即如果用公開金鑰對資料進行加密,只有用對應的私有金鑰才能進行解密;如果用私有金鑰對資料進行加密,則只有用對應的公開金鑰才能解密。
使用公鑰密碼演算法進行數字簽名通用的加密標準有: RSA,DSA,Diffie-Hellman等。
簽名和驗證過程:
傳送方(甲)首先用公開的單向函式對報文進行一次變換,得到數字簽名,然後利用私有金鑰對數字簽名進行加密後附在報文之後一同發出。
接收方(乙)用傳送方的公開金鑰對數字簽名進行解密交換,得到一個數字簽名的明文。傳送方的公鑰可以由一個可信賴的技術管理機構即認證中心(CA)釋出的。
接收方將得到的明文通過單向函式進行計算,同樣得到一個數字簽名,再將兩個數字簽名進行對比,如果相同,則證明簽名有效,否則無效。
這種方法使任何擁有傳送方公開金鑰的人都可以驗證數字簽名的正確性。由於傳送方私有金鑰的保密性,使得接受方既可以根據結果來拒收該報文,也能使其無法偽造報文簽名及對報文進行修改,原因是數字簽名是對整個報文進行的,是一組代表報文特徵的定長程式碼,同一個人對不同的報文將產生不同的數字簽名。這就解決了銀行通過網路傳送一張支票,而接收方可能對支票數額進行改動的問題,也避免了傳送方逃避責任的可能性。
對稱金鑰密碼演算法進行數字簽名
演算法含義
對稱金鑰密碼演算法所用的加密金鑰和解密金鑰通常是相同的,即使不同也可以很容易地由其中的任意一個推匯出另一個。在此演算法中,加、解密雙方所用的金鑰都要保守祕密。由於計算機速度而廣泛應用於大量資料如檔案的加密過程中,如RD4和DES,用IDEA作數字簽名是不提倡的。
使用分組密碼演算法數字簽名通用的加密標準有:DES,Tripl-DES,RC2,RC4,CAST等。
簽名和驗證過程
Lamport發明了稱為Lamport-Diffle的對稱演算法:利用一組長度是報文的位元數(n)兩倍的金鑰A,來產生對簽名的驗證資訊,即隨機選擇2n個數B,由簽名金鑰對這2n個數B進行一次加密交換,得到另一組2n個數C。
傳送方從報文分組M的第一位開始,依次檢查M的第I位,若為0時,取金鑰A的第i位,若為1則取金鑰A的第i+1位;直至報文全部檢查完畢。所選取的n個金鑰位形成了最後的簽名。
接受方對簽名進行驗證時,也是首先從第一位開始依次檢查報文M,如果M的第i位為0時,它就認為簽名中的第i組資訊是金鑰A的第i位,若為1則為金鑰A的第i+1位;直至報文全部驗證完畢後,就得到了n個金鑰,由於接受方具有傳送方的驗證資訊C,所以可以利用得到的n個金鑰檢驗驗證資訊,從而確認報文是否是由傳送方所傳送。
這種方法由於它是逐位進行簽名的,只有有一位被改動過,接受方就得不到正確的數字簽名,因此其安全性較好,其缺點是:簽名太長(對報文先進行壓縮再簽名,可以減少簽名的長度);簽名金鑰及相應的驗證資訊不能重複使用,否則極不安全。
結合對稱與非對稱演算法的改進
對稱演算法與非對稱演算法各有利弊,所以結合各自的優缺點進行改進,可以用下面的模組進行說明:
Hash演算法進行數字簽名
Hash演算法也稱作雜湊演算法或報文摘要,Hash演算法將在數字簽名演算法中詳細說明。
Hash演算法數字簽字通用的加密標準有: SHA-1,MD5等。
數字簽名演算法
數字簽名的演算法很多,應用最為廣泛的三種是: Hash簽名、DSS簽名、RSA簽名。這三種演算法可單獨使用,也可綜合在一起使用。數字簽名是通過密碼演算法對資料進行加、解密變換實現的,常用的HASH演算法有MD2、MD5、SHA-1,用DES演算法、RSA演算法都可實現數字簽名。但或多或少都有缺陷,或者沒有成熟的標準。
Hash簽名
Hash簽名是最主要的數字簽名方法,也稱之為數字摘要法(digital digest)、數字指紋法(digital finger print)。它與RSA數字簽名是單獨的簽名不同,該數字簽名方法是將數字簽名與要傳送的資訊緊密聯絡在一起,它更適合於電子商務活動。將一個商務合同的個體內容與簽名結合在一起,比合同和簽名分開傳遞,更增加了可信度和安全性。下面我們將詳細介紹Hash簽名中的函式與演算法。
單向函式
單向函式的概念是公開金鑰密碼的核心。儘管它本身並不是一個協議,但對大多數協議來說卻是一個基本結構模組。
單向函式的概念是計算起來相對容易,但求逆卻非常困難。也就是說,已知x,我們很容易計算f(x)。但已知f(x),卻難於計算出x。在這裡,"難"定義成:即使世界上所有的計算機都用來計算,從f(x)計算出x也要花費數百萬年的時間。
打碎盤子就是一個很好的單向函式的例子。把盤子打碎成數千片碎片是很容易的事情,然而,要把所有這些碎片再拼成為一個完整的盤子,卻是非常困難的事情。
這聽起來很好,但事實上卻不能證實它的真實性。如果嚴格地按數學定義,我們不能證明單向函式的存在性,同時也還沒有實際的證據能夠構造出單向函式。即使這樣,還是有很多函式看起來和感覺像單向函式:我們能夠有效地計算它們,且至今還不知道有什麼辦法能容易地求出它們的逆。例如,在有限域中x2是很容易計算的,但計算x1/2卻難得多。所以我們假定也儘量構造單向函式存在。
陷門單向函式是有一個祕密陷門的一類特殊單向函式。它在一個方向上易於計算而反方向卻難於計算。但是,如果你知道那個祕密,你也能很容易在另一個方向計算這個函式。也就是說, 已知x,易於計算f(x),而已知f(x),卻難於計算x。然而,有一些祕密資訊y,一旦給出f(x)和y,就很容易計算x。
拆開表是很好的單向陷門函式的例子。很容易把表拆成數百片小片,把這些小片組裝成能夠工作的表是非常困難的。然而,通過祕密資訊(表的裝配指令),就很容易把表還原。
單向Hash函式
單向Hash函式有很多名字:壓縮函式、縮短函式、訊息摘要、指紋、密碼校驗和、資訊完整性檢驗(DIC)、操作檢驗碼(MDC)。不管你怎麼叫,它是現代密碼學的中心。單向Hash函式是許多協議的另一個結構模組。
Hash函式長期以來一直在電腦科學中使用,無論從數學上或別的角度看,Hash函式就是把可變輸入長度串(叫做預對映,Pre-image)轉換成固定長度(經常更短)輸出串(叫做hash值)的一種函式。簡單的Hash函式就是對預對映的處理,並且返回由所有輸入位元組異或組成的一位元組。
這兒的關鍵就是採集預對映的指紋:產生一個值,這個值能夠指出候選預對映是否與真實的預對映有相同的值。因為Hash函式是典型的多到一的函式,我們不能用它們來確定兩個串一定相同,但我們可用它來得到準確性的合理保證。
單向Hash函式是在一個方向上工作的Hash函式,從預對映的值很容易計算其Hash值,但要產生一個預對映的值
關於Hash簽名與認證的Vc++實現,請參考下列程式碼與程式碼註釋
[cpp] view plain copy print?- #ifndef _WIN32_WINNT
- #define _WIN32_WINNT 0x0400
- #endif
- #include <stdio.h>
- #include <windows.h>
- #include <wincrypt.h>
- #define MY_ENCODING_TYPE (PKCS_7_ASN_ENCODING | X509_ASN_ENCODING)
- //函式申明
- void HandleError(char *s);
- HCRYPTPROV GetCryptProv();
- void main(void)
- {
- //-------------------------------------------------------------------
- // 變數申明及初始化
- HCRYPTPROV hProv;
- BYTE *pbBuffer= (BYTE *)"The data that is to be hashed and signed.";
- DWORD dwBufferLen = strlen((char *)pbBuffer)+1;
- HCRYPTHASH hHash;
- HCRYPTKEY hKey;
- HCRYPTKEY hPubKey;
- BYTE
- BYTE *pbSignature;
- DWORD dwSigLen;
- DWORD dwBlobLen;
- LPTSTR szDescription = "Test Data Description";
- //-------------------------------------------------------------------
- // 獲取CSP控制代碼
- hProv = GetCryptProv();
- //-------------------------------------------------------------------
- // 獲取金鑰對,其中的私鑰用於建立數字簽名,公鑰將用於驗證簽名。
- if(CryptGenKey(
- hProv,
- AT_SIGNATURE,
- 0,
- &hKey))
- {
- printf("簽名金鑰對已經被獲取./n");
- }
- else
- {
- HandleError("建立簽名金鑰出錯./n");
- }
- //-------------------------------------------------------------------
- // 匯出金鑰對的公鑰,確定金鑰塊資料長度
- if(CryptExportKey(
- hKey,
- NULL,
- PUBLICKEYBLOB,
- 0,
- NULL,
- &dwBlobLen))
- {
- printf("公鑰塊的資料長度已經確定. /n");
- }
- else
- {
- HandleError("計算金鑰資料塊長度出錯.");
- }
- //-------------------------------------------------------------------
- // 分配空間
- if(pbKeyBlob = (BYTE*)malloc(dwBlobLen))
- {
- printf("已經為此資料塊分配了記憶體空間. /n");
- }
- else
- {
- HandleError("所需記憶體不夠. /n");
- }
- //-------------------------------------------------------------------
- // 匯出金鑰對的公鑰
- if(CryptExportKey(
- hKey,
- NULL,
- PUBLICKEYBLOB,
- 0,
- pbKeyBlob,
- &dwBlobLen))
- {
- printf("公鑰塊中的資料已經寫入到匯出塊中. /n");
- }
- else
- {
- HandleError("Error during CryptExportKey.");
- }
- //-------------------------------------------------------------------
- // 建立雜湊控制代碼
- if(CryptCreateHash(
- hProv,
- CALG_MD5,
- 0,
- 0,
- &hHash))
- {
- printf("雜湊控制代碼已經建立. /n");
- }
- else
- {
- HandleError("Error during CryptCreateHash.");
- }
- //-------------------------------------------------------------------
- // 計算輸入資料的雜湊值
- if(CryptHashData(
- hHash,
- pbBuffer,
- dwBufferLen,
- 0))
- {
- printf("緩衝區中的資料已經進行了雜湊處理./n");
- }
- else
- {
- HandleError("Error during CryptHashData.");
- }
- //-------------------------------------------------------------------
- // 確定簽名信息的長度
- dwSigLen= 0;
- if(CryptSignHash(
- hHash,
- AT_SIGNATURE,
- szDescription,
- 0,
- NULL,
- &dwSigLen))
- {
- printf("簽名信息的長度 %d 找到./n",dwSigLen);
- }
- else
- {
- HandleError("Error during CryptSignHash.");
- }
- //-------------------------------------------------------------------
- // 分配空間
- if(pbSignature = (BYTE *)malloc(dwSigLen))
- {
- printf("給此簽名訊息分配了記憶體空間./n");
- }
- else
- {
- HandleError("所需記憶體不夠.");
- }
- //-------------------------------------------------------------------
- // 對雜湊資料進行數字簽名
- if(CryptSignHash(
- hHash,
- AT_SIGNATURE,
- szDescription,
- 0,
- pbSignature,
- &dwSigLen))
- {
- printf("對雜湊資料已經進行了數字簽名./n");
- }
- else
- {
- HandleError("Error during CryptSignHash.");
- }
- //-------------------------------------------------------------------
- // 銷燬雜湊控制代碼
- if(hHash)
- CryptDestroyHash(hHash);
- printf("雜湊控制代碼已經被銷燬./n");
- printf("這個程式的簽名階段已經完成./n/n");
- //-------------------------------------------------------------------
- // 下面是程式的第二階段:驗證數字簽名。
- // 一般的,這段程式應該是另一使用者在另外一個應用程式中執行,
- // 其雜湊值、數字簽名訊息、公鑰都應該從檔案或網路中獲取。
- // 這裡僅是演示如何進行簽名驗證。
- //-------------------------------------------------------------------
- // 從金鑰塊資料中匯入公鑰到CSP。
- if(CryptImportKey(
- hProv,
- pbKeyBlob,
- dwBlobLen,
- 0,
- 0,
- &hPubKey))
- {
- printf("公鑰已經被匯入./n");
- }
- else
- {
- HandleError("匯入公鑰失敗.");
- }
- //-------------------------------------------------------------------
- // 建立雜湊控制代碼
- if(CryptCreateHash(
- hProv,
- CALG_MD5,
- 0,
- 0,
- &hHash))
- {
- printf("雜湊控制代碼已經被重新建立. /n");
- }
- else
- {
- HandleError("Error during CryptCreateHash.");
- }
- //-------------------------------------------------------------------
- // 計算輸入資料的雜湊值
- if(CryptHashData(
- hHash,
- pbBuffer,
- dwBufferLen,
- 0))
- {
- printf("新的雜湊值已經計算出./n");
- }
- else
- {
- HandleError("Error during CryptHashData.");
- }
- //-------------------------------------------------------------------
- // 驗證數字簽名
- if(CryptVerifySignature(
- hHash,
- pbSignature,
- dwSigLen,
- hPubKey,
- szDescription,
- 0))
- {
- printf("此數字簽名已經被驗證./n");
- }
- else
- {
- printf("簽名未生效!/n");
- }
- //-------------------------------------------------------------------
- // 釋放記憶體
- if(pbSignature)
- free(pbSignature);
- if(hHash)
- CryptDestroyHash(hHash);
- if(hProv)
- CryptReleaseContext(hProv, 0);
- } // End of main
- //獲取加密提供者控制代碼
- HCRYPTPROV GetCryptProv()
- {
- HCRYPTPROV hCryptProv; // 加密服務提供者控制代碼
- //獲取加密提供者控制代碼
- if(CryptAcquireContext(
- &hCryptProv, // 加密服務提供者控制代碼
- NULL, // 金鑰容器名,這裡使用登陸使用者名稱
- MS_DEF_PROV, // 加密服務提供者,"Microsoft Base Cryptographic Provider v1.0"
- PROV_RSA_FULL, // 加密服務提供者型別,可以提供加密和簽名等功能
- 0)) // 標誌
- {
- printf("加密服務提供者控制代碼獲取成功!/n");
- }
- else
- {
- //重新建立一個新的金鑰集
- if(!CryptAcquireContext(&hCryptProv, NULL, MS_DEF_PROV, PROV_RSA_FULL, CRYPT_NEWKEYSET))
- {
- HandleError("重新建立一個新的金鑰集出錯!");
- }
- }
- return hCryptProv;
- }
- // HandleError:錯誤處理函式,列印錯誤資訊,並退出程式
- void HandleError(char *s)
- {
- printf("程式執行發生錯誤!/n");
- printf("%s/n",s);
- printf("錯誤程式碼為: %x./n",GetLastError());
- printf("程式終止執行!/n");
- exit(1);
- }
本文作者專著《Visual C++2010開發權威指南》即將推出,敬請關注,Visual C++2010最近技術,Windows7開發最新技術!