背景(只是個人感想,技術上不對后面的內容構成知識性障礙,可以skip):
最近,基于某些原因和需要,筆者需要去了解一下Crypto++庫,然后對一些數據進行一些加密解密的操作。
筆者之前沒接觸過任何加密解密方面的知識(當然,把每個字符的ASCII值加1之流對明文進行加密的“趣事”還是干過的,當時還很樂在其中。),甚至一開始連Crypto++的名字都沒有聽過,被BS了之后,就開始了Crypto++的入門探索過程。
最初,大概知道了要了解兩大類算法中的幾個算法——對稱加密算法:DES、AES(后來因為人品好的緣故也了解了下非對稱加密算法RSA,后文會詳述何謂“人品好”);散列算法(需要通過Hash運算):SHA-256。
起初,筆者以為這樣的知名算法在網上應該有很多現成的例子。筆者比較懶,對于自己不熟悉的東西,總希望找捷徑,直接找別人現(在已經寫)成可(編譯運)行的代碼然后施展ctrl + C,ctrl + V算法(咳,什么算法,是大法?。。。?/p>
However,發覺網上的例子不是稀缺,就是只有代碼沒有解釋。筆者覺得很難忍受這樣的“莫名其妙”(奇怪的是筆者容忍了windows了,盡管它不開源),遂決定從零開始……
……寫在代碼前……
一些前期工作——編譯cryptlib并使其可用:
本文不涉及這部分內容,因為已經有相對完善的資料:
http://www.cnblogs.com/cxun/archive/
總結了一點預備知識:
關于幾個算法的介紹,網上各大百科都有,筆者不再詳細Ctrl+C/V了。不過在寫代碼之前,即使復制修改人家代碼之前,也有必要了解一下幾個算法(除了名稱之外)的使用流程(不是算法具體的實現,汗?。?。
對稱加密:(AES、DES)
相對于與非對稱加密而言,加密、解密用的密匙相同。就像日常生活中的鑰匙,開門和鎖門都是同一把。
詳見:http://baike.baidu.com/view/119320.htm
非對稱加密:(RSA)
相對于上述的對稱加密而言,加密、解密用的密匙不同,有公匙和私匙之分。
詳見:http://baike.baidu.com/view/554866.htm
散列算法:(SHA系列,我們熟悉的MD5等)
用途:驗證信息有沒有被修改。
原理:對長度大的信息進行提煉(通過一個Hash函數),提煉過后的信息長度小很多,得到的是一個固定長度的值(Hash值)。對于兩個信息量很大的文件通過比較這兩個值,就知道這兩個文件是否完全一致(另外一個文件有沒有被修改)。從而避免了把兩個文件中的信息進行逐字逐句的比對,減少時間開銷。
形象地描述:鬼泣3里面維吉爾跟但丁除了發型之外都很像。怎么區分兩個雙生子?比較他們的DNA就好比是比較信息量很大的文件,然而直接看發型就好比是看Hash值。一眼就看出來了。
注:以上是筆者對幾個概念的,非常不嚴格的,非常主觀的,概括的描述,想要詳細了解,可以:
http://wenku.baidu.com/view/4fb8e0791711cc7931b716aa.html
幾個算法的介紹,選擇,比較。
……Code speaking……
平臺:WindowsXP
IDE以及工具:Visual Studio 2008 + Visual Assist
庫版本:Crypto++
庫的文檔(包括類和函數的接口列表):
http://www.cryptopp.com/docs/ref/index.html
對稱加密算法:
DES:
一開始筆者并沒有找到關于DES運用的很好的例程,或者說,筆者的搜索功力薄弱,未能找到非常完整的例程吧。
http://bbs.pediy.com/showthread.php?p=745389
筆者以下的代碼主要是參考上面URL的論壇回帖,但是作了些修改:
#include <iostream>2
#include <des.h>3

4
#pragma comment( lib, "cryptlib.lib" )5

6
using namespace std;7
using namespace CryptoPP;8

9
int main( void )10


{11
//主要是打印一些基本信息,方便調試:12
cout << "DES Parameters: " << endl;13
cout << "Algorithm name : " << DES::StaticAlgorithmName() << endl; 14
15
unsigned char key[ DES::DEFAULT_KEYLENGTH ];16
unsigned char input[ DES::BLOCKSIZE ] = "12345";17
unsigned char output[ DES::BLOCKSIZE ];18
unsigned char txt[ DES::BLOCKSIZE ];19

20
cout << "input is: " << input << endl;21

22
//可以理解成首先構造一個加密器23
DESEncryption encryption_DES;24

25
//回憶一下之前的背景,對稱加密算法需要一個密匙。加密和解密都會用到。26
//因此,設置密匙。27
encryption_DES.SetKey( key, DES::KEYLENGTH );28
//進行加密29
encryption_DES.ProcessBlock( input, output );30

31
//顯示結果32
//for和for之后的cout可有可無,主要為了運行的時候看加密結果33
//把字符串的長度寫成一個常量其實并不被推薦。34
//不過筆者事先知道字符串長,為了方便調試,就直接寫下。35
//這里主要是把output也就是加密后的內容,以十六進制的整數形式輸出。36
for( int i = 0; i < 5; i++ )37

{38
cout << hex << (int)output[ i ] << ends;39
}40
cout << endl;41

42
//構造一個加密器43
DESDecryption decryption_DES; 44

45
//由于對稱加密算法的加密和解密都是同一個密匙,46
//因此解密的時候設置的密匙也是剛才在加密時設置好的key47
decryption_DES.SetKey( key, DES::KEYLENGTH );48
//進行解密,把結果寫到txt中49
//decryption_DES.ProcessAndXorBlock( output, xorBlock, txt );50
decryption_DES.ProcessBlock( output, txt );51

52
//以上,加密,解密還原過程已經結束了。以下是為了驗證:53
//加密前的明文和解密后的譯文是否相等。54
if ( memcmp( input, txt, 5 ) != 0 )55

{56
cerr << "DES Encryption/decryption failed.\n";57
abort();58
}59
cout << "DES Encryption/decryption succeeded.\n";60
61
return 0;62
}63

數據準備;
構造加密器;
設置加密密匙;
加密數據; 顯示(非必要); 設置解密密匙(跟加密密匙是同一個key);
解密數據;
驗證與顯示(非必要); 由此可見,主要函數的調用流程就是這樣。但是文檔沒有詳細講,筆者當時打開下載回來的源文件時,就傻了眼。
猜想:
AES和以后的算法,是不是都是按照這些基本的套路呢?
AES:
在實際運用的時候,從代碼上看,AES跟DES非常相像。但是值得注意一點的是,AES取代了DES成為21世紀的加密標準。是因為以其密匙長度和高安全性獲得了先天優勢。雖然界面上看上去沒多大區別,但是破解難度遠遠大于DES。詳細情況,在之前的URL有提及過。
很幸運,筆者很快就找到了AES的使用例程,而且很詳細:
http://dev.firnow.com/course/3_program/c++/cppsl/2008827/138033.html
#include <iostream>2
#include <aes.h>3

4
#pragma comment( lib, "cryptlib.lib" )5

6
using namespace std; 7
using namespace CryptoPP;8

9
int main()10


{11

12
//AES中使用的固定參數是以類AES中定義的enum數據類型出現的,而不是成員函數或變量13
//因此需要用::符號來索引14
cout << "AES Parameters: " << endl;15
cout << "Algorithm name : " << AES::StaticAlgorithmName() << endl; 16

17
//Crypto++庫中一般用字節數來表示長度,而不是常用的字節數18
cout << "Block size : " << AES::BLOCKSIZE * 8 << endl;19
cout << "Min key length : " << AES::MIN_KEYLENGTH * 8 << endl;20
cout << "Max key length : " << AES::MAX_KEYLENGTH * 8 << endl;21

22
//AES中只包含一些固定的數據,而加密解密的功能由AESEncryption和AESDecryption來完成23
//加密過程24
AESEncryption aesEncryptor; //加密器 25

26
unsigned char aesKey[AES::DEFAULT_KEYLENGTH]; //密鑰27
unsigned char inBlock[AES::BLOCKSIZE] = "123456789"; //要加密的數據塊28
unsigned char outBlock[AES::BLOCKSIZE]; //加密后的密文塊29
unsigned char xorBlock[AES::BLOCKSIZE]; //必須設定為全零30

31
memset( xorBlock, 0, AES::BLOCKSIZE ); //置零32

33
aesEncryptor.SetKey( aesKey, AES::DEFAULT_KEYLENGTH ); //設定加密密鑰34
aesEncryptor.ProcessAndXorBlock( inBlock, xorBlock, outBlock ); //加密35

36
//以16進制顯示加密后的數據37

for( int i=0; i<16; i++ )
{38
cout << hex << (int)outBlock[i] << " ";39
}40
cout << endl;41

42
//解密43
AESDecryption aesDecryptor;44
unsigned char plainText[AES::BLOCKSIZE];45

46
aesDecryptor.SetKey( aesKey, AES::DEFAULT_KEYLENGTH );47
//細心的朋友注意到這里的函數不是之前在DES中出現過的:ProcessBlock,48
//而是多了一個Xor。其實,ProcessAndXorBlock也有DES版本。用法跟AES版本差不多。49
//筆者分別在兩份代碼中列出這兩個函數,有興趣的朋友可以自己研究一下有何差異。50
aesDecryptor.ProcessAndXorBlock( outBlock, xorBlock, plainText );51

52

53
for( int i=0; i<16; i++ ) 54

{ 55
cout << plainText[i]; 56
}57
cout << endl;58

59
return 0;60
}61

62

其實來到這里,都可以發現,加密解密的套路也差不多,至于之后筆者在誤打誤撞中找到的RSA,也只不過是在設置密匙的時候多了私匙和公匙的區別而已。筆者總覺得,有完整的例程對照學習,是一件很幸福的事情。
非對稱加密算法:
RSA:
小背景:
其實,筆者在一開始并沒有接到“了解RSA”的要求。不過由于筆者很粗心,在看AES的時候只記得A和S兩個字母,Google的時候就誤打誤撞Google了一個RSA。其實RSA方面的資料還是挺多的,因此它事實上是筆者第一個編譯運行成功的Crypto++庫中算法的應用實例。
http://www.cnblogs.com/cxun/archive/2008/07/30/743541.html
以下代碼主要是按照上述URL中提供的代碼寫成的,作為筆者的第一份有效學習資料,筆者認為作為調用者的我們,不用清楚算法實現的細節。只需要明白幾個主要函數的功用和調用的次序即可。
由以下代碼可以看出,其實RSA也離不開:數據準備、設置密匙(注意,有公匙和私匙)、加密解密這樣的套路。至于如何產生密匙,有興趣的朋友可以到Crypto++的主頁上下載源文件研究。作為入門和了解階段,筆者覺得:只需要用起來即可。
//version at Crypto++ 5.602
#include "randpool.h"3
#include "rsa.h"4
#include "hex.h"5
#include "files.h"6
#include <iostream>7

8
using namespace std;9
using namespace CryptoPP;10

11
#pragma comment(lib, "cryptlib.lib")12

13

14
//------------------------15

16
// 函數聲明17

18
//------------------------19

20
void GenerateRSAKey( unsigned int keyLength, const char *privFilename, const char *pubFilename, const char *seed );21
string RSAEncryptString( const char *pubFilename, const char *seed, const char *message );22
string RSADecryptString( const char *privFilename, const char *ciphertext );23
RandomPool & GlobalRNG();24

25
//------------------------26
// 主程序27
//------------------------28

29
void main( void )30

31


{32

char priKey[ 128 ] =
{ 0 };33

char pubKey[ 128 ] =
{ 0 };34

char seed[ 1024 ] =
{ 0 };35

36
// 生成 RSA 密鑰對37
strcpy( priKey, "pri" ); // 生成的私鑰文件38
strcpy( pubKey, "pub" ); // 生成的公鑰文件39
strcpy( seed, "seed" );40
GenerateRSAKey( 1024, priKey, pubKey, seed );41

42
// RSA 加解密43

char message[ 1024 ] =
{ 0 };44
cout<< "Origin Text:\t" << "Hello World!" << endl << endl;45
strcpy( message, "Hello World!" );46
string encryptedText = RSAEncryptString( pubKey, seed, message ); // RSA 公匙加密47
cout<<"Encrypted Text:\t"<< encryptedText << endl << endl;48
string decryptedText = RSADecryptString( priKey, encryptedText.c_str() ); // RSA 私匙解密49
}50

51

52

53
//------------------------54

55
// 生成RSA密鑰對56

57
//------------------------58

59
void GenerateRSAKey(unsigned int keyLength, const char *privFilename, const char *pubFilename, const char *seed)60


{61
RandomPool randPool;62
randPool.Put((byte *)seed, strlen(seed));63

64
RSAES_OAEP_SHA_Decryptor priv(randPool, keyLength);65
HexEncoder privFile(new FileSink(privFilename));66

67
priv.DEREncode(privFile);68
privFile.MessageEnd();69

70
RSAES_OAEP_SHA_Encryptor pub(priv);71
HexEncoder pubFile(new FileSink(pubFilename));72
pub.DEREncode(pubFile);73

74
pubFile.MessageEnd();75

76
return ;77
}78

79

80

81
//------------------------82

83
// RSA加密84

85
//------------------------86

87
string RSAEncryptString( const char *pubFilename, const char *seed, const char *message )88


{89
FileSource pubFile( pubFilename, true, new HexDecoder );90
RSAES_OAEP_SHA_Encryptor pub( pubFile );91

92
RandomPool randPool;93
randPool.Put( (byte *)seed, strlen(seed) );94

95
string result;96
StringSource( message, true, new PK_EncryptorFilter(randPool, pub, new HexEncoder(new StringSink(result))) );97
98
return result;99
}100

101

102

103
//------------------------104
// RSA解密105
//------------------------106

107
string RSADecryptString( const char *privFilename, const char *ciphertext )108


{109
FileSource privFile( privFilename, true, new HexDecoder );110
RSAES_OAEP_SHA_Decryptor priv(privFile);111

112
string result;113
StringSource( ciphertext, true, new HexDecoder(new PK_DecryptorFilter(GlobalRNG(), priv, new StringSink(result))) );114

115
return result;116
}117

118

119

120
//------------------------121

122
// 定義全局的隨機數池123

124
//------------------------125

126
RandomPool & GlobalRNG()127


{128
static RandomPool randomPool;129
return randomPool;130
}131

132

散列算法:
SHA-256
SHA-256主要是用來求一大段信息的Hash值,跟之前三個用于加密、解密的算法有所不同。用到SHA的場合,多半是為了校驗文件。
筆者的參考資料:http://hi.baidu.com/magic475/blog/item/19b37a8c1fa15a14b21bbaeb.html
請注意,筆者在實現的時候,稍微修改了一下兩個子函數的實現,以滿足筆者的需求。因此會與上述URL中的代碼有差異。
//http://hi.baidu.com/magic475/blog/item/19b37a8c1fa15a14b21bbaeb.html2
#include <iostream>3
#include <string.h>4

5
#include "sha.h"6
#include "secblock.h"7
#include "modes.h"8
#include "hex.h"9

10
#pragma comment( lib, "cryptlib.lib")11

12
using namespace std;13
using namespace CryptoPP;14

15
void CalculateDigest(string &Digest, const string &Message);16
bool VerifyDigest(const string &Digest, const string &Message);17

18
int main( void )19


{20
//main函數中注釋掉的,關于strMessage2的代碼,其實是筆者模擬了一下21
//通過求Hash值來對“大”量數據進行校驗的這個功能的運用。22
//注釋之后并不影響這段代碼表達的思想和流程。23
string strMessage( "Hello world" );24
string strDigest;25
//string strMessage2( "hello world" ); //只是第一個字母不同26
//string strDigest2;27

28
CalculateDigest( strDigest, strMessage ); //計算Hash值并打印一些debug信息29
cout << "the size of Digest is: " << strDigest.size() << endl;30
cout << "Digest is: " << strDigest << endl;31

32
//CalculateDigest( strDigest2, strMessage2 );33
//why put this function here will affect the Verify function?34
//作者在寫代碼的過程中遇到的上述問題。35
//如果把這行代碼的注釋取消,那么之后的運行結果就不是預料中的一樣:36
//即使strDigest也無法對應strMessage,筆者不知道為什么,希望高手指出,謝謝!37

38
bool bIsSuccess = false;39
bIsSuccess = VerifyDigest( strDigest, strMessage ); 40
//通過校驗,看看strDigest是否對應原來的message41
if( bIsSuccess )42

{43
cout << "sussessive verify" << endl;44
cout << "origin string is: " << strMessage << endl << endl;45
}46
else47

{48
cout << "fail!" << endl;49
}50

51
//通過strDigest2與strMessage進行校驗,要是相等,52
//就證明strDigest2是對應的strMessage2跟strMessage1相等。53
//否則,像這個程序中的例子一樣,兩個message是不相等的54

/**//*CalculateDigest( strDigest2, strMessage2 );55
bIsSuccess = VerifyDigest( strDigest2, strMessage );56
if( !bIsSuccess )57
{58
cout << "success! the tiny modification is discovered~" << endl;59
cout << "the origin message is: \n" << strMessage << endl;60
cout << "after modify is: \n" << strMessage2 << endl;61
}*/62
return 0;63
}64

65

66
//基于某些原因,以下兩個子函數的實現跟原來參考代碼中的實現有所區別,67
//詳細原因,筆者在CalculateDigest函數的注釋中寫明68
void CalculateDigest(string &Digest, const string &Message)69


{70
SHA256 sha256;71
int DigestSize = sha256.DigestSize();72
char* byDigest;73
char* strDigest;74

75
byDigest = new char[ DigestSize ];76
strDigest = new char[ DigestSize * 2 + 1 ];77

78
sha256.CalculateDigest((byte*)byDigest, (const byte *)Message.c_str(), Message.size());79
memset(strDigest, 0, sizeof(strDigest));80
//uCharToHex(strDigest, byDigest, DigestSize);81
//參考的代碼中有以上這么一行,但是貌似不是什么庫函數。82
//原作者大概是想把Hash值轉換成16進制數保存到一個string buffer中,83
//然后在主程序中輸出,方便debug的時候對照查看。84
//但是這并不影響計算Hash值的行為。85
//因此筆者注釋掉了這行代碼,并且修改了一下這個函數和后面的VerifyDigest函數,86
//略去原作者這部分的意圖,繼續我們的程序執行。87

88
Digest = byDigest;89

90
delete []byDigest;91
byDigest = NULL;92
delete []strDigest;93
strDigest = NULL;94

95
return;96
}97

98
bool VerifyDigest(const string &Digest, const string &Message)99


{100
bool Result;101
SHA256 sha256;102
char* byDigest;103

104
byDigest = new char[ sha256.DigestSize() ];105
strcpy( byDigest, Digest.c_str() );106

107
//HexTouChar(byDigest, Digest.c_str(), Digest.size());108
//為何注釋掉,請參看CalculateDigest函數的注釋109
Result = sha256.VerifyDigest( (byte*)byDigest, (const byte *)Message.c_str(), Message.size() );110

111
delete []byDigest;112
byDigest = NULL;113
return Result;114
}115

116

后記:
為什么寫這篇文章呢?因為筆者在搜索過程中覺得這方面的資料有點分散,因此想把它們集中起來,方便剛剛入門的朋友。
同時,也算是為自己留點學習資料吧。
鳴謝:
jingzhongrong
vczh
沒了這兩位,在這個宇宙、這個時間、這個維度肯定不會有這篇文章,哈哈!


