CBC和CTR模式下的AES


實驗內容:

在本次實驗中,需要實現兩個加密/解密系統,一個在密文分組鏈接模式(CBC)下使用AES,另一個在計數器模式(CTR)中使用AES。

實驗環境:

VS2019、C++、 Crypto++

實驗過程:

1、安裝Crypto++

1.1官網下載Crypto++

官網地址:https://www.cryptopp.com/

1.2解壓編譯,生成.lib文件

解壓后,用vs打開里面的.sln工程文件,會得到四個工程。

將cryptlib項目設為啟動項,選中cryptlib,選擇Debug x64模式,按下ctrl + B生成cryptlib。

1.3配置工程環境

新建工程,右鍵工程選擇屬性,選擇VC++目錄,設置包含目錄和庫目錄。庫目錄就是頭文件所在目錄,庫目錄就是剛生成的.lib所在目錄。
選擇鏈接器->輸入->附加依賴項,輸入剛才的生成的.lib文件完整名字。

選擇c/c++ ->代碼生成 -> 運行庫,選擇多線程調試(/MTd)

2、CBC模式下的AES原理

EBC和CBC模式都是分塊加密,經常要對plaintext進行填充,使之滿足16字節的整數倍。一般EBC模式下,如果采用相同的內容和相同的秘鑰,結果密文是相同的,這樣是不安全的。CBC引入向量IV的概念,加密過程除了提供key和plaintext還需要提供IV,這個IV大小為16個字節。密文中前16字節,就是IV。IV會參與第一塊的加密,之后就使用上一塊加密的結果代替IV。這樣做的好處就是使得相同的內容相同的秘鑰,加密的結果可能不同。

加密過程:

先將plaintext填充為16字節的整數倍,然后將plaintext等分為n份。第一次加密時,將IV與P1進行異或操作得到結果,然后將這個結果進行AES加密,得到C1。將C1鏈接到密文中去,並將C1代替IV,參與下一次的異或操作。然后重復上述操作,直到所有的block都進行加密。

解密過程:

選讀取密文的前16個字節,這16個字節就是IV。然后將去除IV的plaintext等分為n份。
先取出C1(第一塊密文),將C1通過AES解密得到結果T1, 然后將T1與IV異或的得到第一塊明文。再用C1替換IV參與下一次解密運算,重復上述操作。

單獨處理最后一塊,這里最后一塊采用的PKCS7的填充方式,這種方式填充最后幾個字節表示填充了多少個字節,獲取最后一個字節的數字,只需要在明文結果后面刪除這個長度的字節就得到真正的明文。

3、CBC模式下AES加密解密實現

3.1 CBC_AES解密代碼

void CBCdecrypto(const string& key,const string& ciphertext, string& plaintext)
{
string vi = ciphertext.substr(0, AES::BLOCKSIZE);//AES::BLOCKSIZE =16
string text = ciphertext.substr(AES::BLOCKSIZE, ciphertext.size() - AES::BLOCKSIZE);//前16為vi后16為填充

size_t groupNumber = text.size() / AES::BLOCKSIZE;

AESDecryption cryptor;
cryptor.SetKey((byte*)key.c_str(),key.size());
for (size_t i = 0 ; i<groupNumber;i++) 
{
	//獲取每一次的分組密文
	string block = text.substr(i * AES::BLOCKSIZE, AES::BLOCKSIZE);

	byte temp[AES::BLOCKSIZE];
	memset(temp, 0x30, AES::BLOCKSIZE);

	cryptor.ProcessBlock((byte*)block.c_str(), temp);

	for (int j = 0; j < AES::BLOCKSIZE; j++) {
		plaintext.push_back((byte)vi[j] ^ temp[j]);
	}

	vi = block;
}
string paddingBlock = plaintext.substr((groupNumber - 1) * AES::BLOCKSIZE, AES::BLOCKSIZE);
int paddingNum = (byte)paddingBlock[AES::BLOCKSIZE - 1];

for (int i = 0; i < paddingNum; i++) {
	if (plaintext.back() != paddingNum) {
		cout << "密文出錯" << endl;
		exit(0);
	}
	plaintext.pop_back();
}

}

3.2 CBC_AES加密代碼

string CBCencrypto(string hexKey, string hexVI, string plaintext) {
string key;
hex_to_str(hexKey, key);

string VI;
hex_to_str(hexVI, VI);

string outstr;
outstr.clear();
outstr += VI;

//填充plaintext 使之成為16字節的整數倍
int paddingNum = AES::BLOCKSIZE - (plaintext.size() % AES::BLOCKSIZE);

for (int i = 0; i < paddingNum; i++) {
	if (i == paddingNum - 1) 
	{
		plaintext += (char)paddingNum;//最后一個byte表示填充多少個字節
	}
	else {
		plaintext += (char)(15);//其他填充為0x0F
	}
}

//獲取多少個組
int groupNumber = plaintext.size() / AES::BLOCKSIZE;
AESEncryption encryptor((byte*)key.c_str(), AES::MIN_KEYLENGTH);
for (int i = 0; i < groupNumber;i++) 
{
	string Pi = plaintext.substr(i * AES::BLOCKSIZE, AES::BLOCKSIZE);
	//想讓IV 與 Pi異或
	byte temp[AES::BLOCKSIZE];
	memset(temp, 0x30, AES::BLOCKSIZE);
	for (int j = 0; j < AES::BLOCKSIZE; j++) 
	{
		temp[j] = (byte)Pi[j] ^ VI[j];
	}
	//然后將temp進行aes
	byte tempr[AES::BLOCKSIZE];
	encryptor.ProcessBlock(temp, tempr);
	//將tempr添加到outstr上
	string PiResult;
	PiResult.clear();
	for (int j = 0; j < AES::BLOCKSIZE;j++) {
		PiResult += tempr[j];
	}
	outstr += PiResult;

	//更新IV,參與下次運算
	VI = PiResult;
}
//還需要將outstr 轉成十六進制的情況
string hexOutstr;
hexOutstr.clear();
for (int i = 0; i < outstr.size(); i++) 
{
	string stemp;
	char2hexs(outstr[i], stemp);
	hexOutstr += stemp;
}
return hexOutstr;

}

4、CTR模式下的AES原理

CTR有一個計數器counter,一般為16字節,前后兩次的加密與加密結果無關。每次加密counter加一,所以加密速度更快,但是安全性比CBC模式稍低點。而且CTR加密不需要填充,類似流模式。密文的前16個字節為counter。

加密過程:

先選取counter,如果沒有16字節就填充,現將counter通過AES進行加密,得到結果T1,
然后將T1與明文的第一分組進行異或得到結果C1,將C1鏈接到密文上,然后將counter+1進行下一輪加密。

對最后一個非整塊的明文單獨處理,處理方法與上面類似,只是長度按照剩余塊的長度處理。

解密過程:

選讀取密文中前16個字節作為counter,然后將去除counter的密文按照16個字節等分,最后一個非整16字節的單獨處理。

現將counter進行AES解密得到Ti然后,將Ti與密文塊Ci進行異或得到明文Pi,將Pi鏈接到輸出明文上,counter+1進行下一輪解密。
對后面非整16的塊單獨處理,處理方法類似。

5、CTR模式下AES加密解密實現

5.1 CTR_AES 解密代碼

void CTRdecrypto(const string& key, const string& ciphertext, string& plaintext)
{

// 密文的前 16 個字節為計數器的初始值
string counter = ciphertext.substr(0, AES::BLOCKSIZE);
string text = ciphertext.substr(AES::BLOCKSIZE, ciphertext.length() - AES::BLOCKSIZE);
int groupNumber = text.length() / AES::BLOCKSIZE;

AESEncryption aesEncryptor;
aesEncryptor.SetKey((byte*)key.c_str(), key.length());
byte aesResult[AES::BLOCKSIZE];
for (int i = 0; i <groupNumber; i++) {
	string ciphertextBlock = text.substr(i * AES::BLOCKSIZE, AES::BLOCKSIZE);
	memset(aesResult, 0x30, AES::BLOCKSIZE);
	aesEncryptor.ProcessBlock((byte*)counter.c_str(), aesResult);
	// 密文和 AES 加密結果異或,得到明文
	for (int j = 0; j < AES::BLOCKSIZE; j++) {
		plaintext.push_back(aesResult[j] ^ (byte)ciphertextBlock[j]);
	}
	// 計數器自增
	counter = counterIncrement(counter, 1);
}

int residueLen = text.length() - groupNumber * AES::BLOCKSIZE;
string residueCiphertext = text.substr(groupNumber * AES::BLOCKSIZE, residueLen);
memset(aesResult, 0, AES::BLOCKSIZE);
aesEncryptor.ProcessBlock((byte*)counter.c_str(), aesResult);
for (int j = 0; j < residueLen; j++) {
	 plaintext.push_back(aesResult[j] ^ (byte)residueCiphertext[j]);
}

}

5.2 CTR_AES加密代碼

string CTRencrypto(string hexKey, string counter, string plaintext)
{
string key;
hex_to_str(hexKey, key);

string outstr;
outstr.clear();
outstr += counter;
//CTR獲取多少個整數 的16bytes
int num = plaintext.size() / AES::BLOCKSIZE;
AESEncryption encryptor((byte*)key.c_str(), AES::MIN_KEYLENGTH);
byte temp[AES::BLOCKSIZE];
for (int i = 0; i < num; i++) {
	memset(temp, 0x30, AES::BLOCKSIZE);
	encryptor.ProcessBlock((byte*)counter.c_str(), temp);
	string block = plaintext.substr(i * AES::BLOCKSIZE, AES::BLOCKSIZE);
	for (int j = 0; j < AES::BLOCKSIZE; j++) {
		outstr.push_back(temp[j]^block[j]);
	}
	counter = counterIncrement(counter, 1);
}

/*
*處理最后一個非整塊的block
*/
int len = plaintext.size() - (num * AES::BLOCKSIZE);
string lastBlock = plaintext.substr(num * AES::BLOCKSIZE - 1, len);
memset(temp, 0x30, AES::BLOCKSIZE);
encryptor.ProcessBlock((byte*)counter.c_str(), temp);
for (int i = 0; i < len; i++) {
	outstr.push_back(lastBlock[i] ^ temp[i]);
}

/*
*將輸出轉換正十六進制
*/
string hexOutstr;
hexOutstr.clear();
for (int i = 0; i < outstr.size(); i++)
{
	string stemp;
	char2hexs(outstr[i], stemp);
	hexOutstr += stemp;
}
return hexOutstr;

}

6、實驗結果

對老師給出的test.txt的解密結果如下:

附:

1、其他代碼解釋

Class Exercise_3
hexKeys 和hexCiphertexts為十六進制的秘鑰和密文組。
keys和ciphertexts為轉換成byte數組的秘鑰和密文組。
plaintexts 是解密后的原文組。
key_path和cipher_path分別為秘鑰和密文的存放路徑。
modeVec是存放加載的秘鑰密文需要解碼的方式,這里有定義

方法解釋:
bool decrypto() 統一對讀取的秘鑰密文處理,得到所有的明文。
void printPlaintexts() 打印所有的結果信息。
bool init() 主要實現加載秘鑰和密文並轉換數據格式。
bool load_keys()加載秘鑰。
bool load_ciphers() 加載密文。
void changeDataFormal() 改變秘鑰密文格式。
其他頂層函數
void hex_to_str(const string& stringData, string& str)將十六進制字符串轉成byte字符串。
void char2hexs(char ch, string& s) 將一個char類型轉成字符串類型。
string counterIncrement(string counter, int n) counter的自增操作。
string CBC_AESEncryptStr(string sKey, string sIV, const char* plainText) CBC模式的調庫實現。

2、文件目錄格式

keys.txt與ciphertexts.txt中數據以空格分隔。

完整代碼地址

  • 上述代碼有個錯誤的地方,單獨的 byte表示范圍是在-128 到127 ,不能表示我們要的范圍0 - 255 ,應該換成 unsigned char

最后一個問題,在由字符串轉十六進制的那里有錯

對於一個字符轉十六進制 直接用 int temp = (int)ch這種方式轉會有正有負
這時候可以將它與oxff 相與,就為正了。
還有就是用 stringstream這種方式轉,比如0x03,他會轉成0x3.所以這個時候可以先設置長度為二 比如 ss<<hex<<setfill('0'); ss<<setw(2),也可以像我這樣實現,效果是一樣的

void char2hexs(char ch, string& s)
{
	s.clear();
	stringstream ss;
	ss.clear();
	int temp = (int)ch;
	temp = temp & 0xff;
	ss << hex << temp;
	s = ss.str();
	if (s.size() == 1) {
		s = '0' + s;
		}
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM