對稱加密算法AES簡介及在OpenSSL中使用舉例

高級加密標準(AES, Advanced Encryption Standard)由美國國家標準和技術協會(NIST)於2000年公佈,它是一種對稱加密算法,用來替代DES。AES也稱爲Rijndael算法,是由兩個比利時密碼學家Vincent Rijmen和Joah Daemen開發的,他們的姓氏作爲這個加密的名字。嚴格來說,AES和Rijndael加密算法並不完全一樣。

關於DES的介紹可以參考:https://blog.csdn.net/fengbingchun/article/details/42273257

AES算法是對稱分組加密算法,可以加密也可以解密信息,加密將數據轉換成無法理解的形式,稱爲密文;解密是將密文轉換爲數據的原始形式,也即明文,其分塊長度固定爲128比特。AES算法密鑰長度有三個版本,分別是128比特、192比特和256比特三種。不同的密鑰長度會對子密鑰的個數和加密輪數產生影響,其中,使用128比特的加密輪次爲10輪,192比特密鑰對應12輪,256比特密鑰有14輪,所有這三種AES方案中除了最後一輪處理外,其它輪處理都是一樣的。對於加密,每輪包含四個步驟:(1).字節替換(ByteSub);(2).行位移(ShiftRow);(3).列混合(MixColumn);(4).輪密鑰加(AddRoundKey)。對於解密,每輪包含四個步驟:(1).逆字節替換(InvByteSub);(2).逆行移位(InvShiftRow);(3).逆列混合(InvMixColumn);(4).論密鑰加的逆變換(InvAddRoundKey)。AES算法的加解密過程如下圖所示:

AES算法中的輸入和輸出都是128bit的序列,這些序列都看作爲分組塊,其包含的位數稱爲分組塊長度。AES算法中的基本處理單元是一個字節。對於一個輸入、輸出或加密密鑰陣列,將序列以8bit爲單位劃分,來生成字節陣列。

AES算法加密模式:操作模式是指用分組密碼加密任意長度明文的方法,AES有5種基本操作模式,分別是電子密碼本(ECB)、密碼分組鏈接模式(CBC)、密碼反饋模式(CFB)、輸出反饋模式(OFB)以及計數器模式(CTR),其中前四種模式也被DES加密算法所使用。其它衍生的算法操作模式,都是建立在這五種模式基礎上的。實際中加密的明文長度很少是分組長度的倍數,如果要使用某些模式(如CBC)進行分組加密,需要在加密前對明文進行填充,填充的方案有多種。

(1). ECB:這種模式直接對明文分組進行加密,它將加密的數據分成若干組,每組的大小跟加密密鑰長度相同,然後每組都用相同的密鑰進行加密,具有簡單易行、可並行化及誤差不傳遞的優點。但這種模式無法實現保密性。

(2). CBC:是SSL/IPSec的標準,廣泛應用於對話中的加密傳輸,它克服了ECB模式安全性弱的缺點。在這種模式下,每個明文分組在加密前需要和一個初始向量(IV)做異或操作,然後做加密操作,得到密文分組。IV具有和密碼分組同樣的長度,它的選取必須隨機。在會話中,IV通常是以明文的形式和密文一起傳送的。在該模式下,密碼不能以並行的方式進行調用。使用CBC模式進行解密,只需要把密文傳給解密機,並把解密機的輸出和鏈接值進行異或即可。

(3). CTR:被設計用來解決CBC模式中遇到的無法並行化的問題。在這種模式中,可以像流密碼一樣對明文進行加密。在CTR模式中,其加密和解密的過程是一樣的。

(4). OFB:是使用分組密碼來產生僞隨機流,然後和明文進行異或操作。OFB模式的解密操作和加密操作一樣。

(5). CFB:同OFB模式一樣,CFB也是產生一個密鑰流,然後和明文進行異或,不同點在其產生僞隨機流/密鑰流的方式不同。

以上整理的內容主要摘自:

1. 《AES算法及其DSP實現》,哈爾濱工業大學,碩論,2008

2. 《AES算法的FPGA實現與分析》,天津大學,碩論,2012

3. 《針對AES加密算法的緩存攻擊研究》,中國科學院大學,碩論,2016

以下爲測試代碼(test_openssl_aes):

#include "funset.hpp"
#include <string.h>
#include <string>
#include <vector>
#include <memory>
#include <algorithm>
#include <openssl/des.h>
#include <openssl/rc4.h>
#include <openssl/md5.h>
#include <openssl/rsa.h>
#include <openssl/pem.h>
#include <openssl/aes.h>
#include <b64/b64.h>

//////////////////////////// AES ///////////////////////////////
namespace {

const unsigned char aes_key[] = {
    0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
    0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff,
    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
};

} // namespace

int test_openssl_aes()
{
	const char* cleartext = "中國北京12345$abcde%ABCDE!!!!";
	fprintf(stdout, "cleartext length: %d, contents: %s\n", strlen(cleartext), cleartext);

	const int key_bits = sizeof(aes_key) / sizeof(aes_key[0]) * 8;

	// encrypt
	AES_KEY enc_key;
	int ret = AES_set_encrypt_key(aes_key, key_bits, &enc_key); 
	if (ret != 0) return ret;

	char* cleartext_encode = b64_encode(reinterpret_cast<const unsigned char*>(cleartext), strlen(cleartext));
	std::shared_ptr<char> ptr1;
	ptr1.reset(cleartext_encode, [](char* p) { free(p); });
	int encoded_length = (strlen(ptr1.get()) + AES_BLOCK_SIZE - 1) / AES_BLOCK_SIZE * AES_BLOCK_SIZE;
	std::unique_ptr<unsigned char[]> cleartext_encode2(new unsigned char[encoded_length]);
	memset(cleartext_encode2.get(), 0, encoded_length);
	memcpy(cleartext_encode2.get(), ptr1.get(), strlen(ptr1.get()));

	std::unique_ptr<unsigned char[]> cleartext_encrypt(new unsigned char[encoded_length]);
	memset(cleartext_encrypt.get(), 0, encoded_length);
	int count = 1;
	while (encoded_length + AES_BLOCK_SIZE - 1 >= AES_BLOCK_SIZE * count) {
		const unsigned char* p1 = cleartext_encode2.get() + AES_BLOCK_SIZE * (count - 1);
		unsigned char* p2 = cleartext_encrypt.get() + AES_BLOCK_SIZE * (count - 1);
		AES_encrypt(p1, p2, &enc_key);
		++count;
	}

	fprintf(stdout, "cleartext encrypt: ");
	std::for_each(cleartext_encrypt.get(), cleartext_encrypt.get() + encoded_length, [](unsigned char v) { fprintf(stdout, "%02X", v); });
	fprintf(stdout, "\n");

	// decrypt
	AES_KEY dec_key;
	ret = AES_set_decrypt_key(aes_key, key_bits, &dec_key);
	if (ret != 0) return ret;

	std::unique_ptr<unsigned char[]> ciphertext_decrypt(new unsigned char[encoded_length]);
	memset(ciphertext_decrypt.get(), 0, encoded_length);
	count = 1;
	while (encoded_length + AES_BLOCK_SIZE - 1 >= AES_BLOCK_SIZE * count) {
		const unsigned char* p1 = cleartext_encrypt.get() + AES_BLOCK_SIZE * (count - 1);
		unsigned char* p2 = ciphertext_decrypt.get() + AES_BLOCK_SIZE * (count - 1);
		AES_decrypt(p1, p2, &dec_key);
		++count;
	}

	fprintf(stdout, "ciphertext decrypt: ");
	std::for_each(ciphertext_decrypt.get(), ciphertext_decrypt.get() + encoded_length, [](unsigned char v) { fprintf(stdout, "%02X", v); });
	fprintf(stdout, "\n");

	unsigned char* decrypt_decode = b64_decode(reinterpret_cast<const char*>(ciphertext_decrypt.get()), encoded_length);
	std::shared_ptr<unsigned char> ptr2;
	ptr2.reset(decrypt_decode, [](unsigned char* p) { free(p); });
	fprintf(stdout, "decrypt result: %s\n", ptr2.get());

	if (strcmp(cleartext, reinterpret_cast<char*>(ptr2.get())) != 0) {
		fprintf(stderr, "aes decrypt fail\n");
		return -1;
	}

	return 0;
}

執行結果如下:

GitHubhttps://github.com//fengbingchun/OpenSSL_Test

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章