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

AES(Advanced Encryption Standard)即高級加密標準,由美國國家標準和技術協會(NIST)於2000年公佈,它是一種對稱加密算法。關於AES的更多介紹可以參考:https://blog.csdn.net/fengbingchun/article/details/100139524

AES的GCM(Galois/Counter Mode)模式本質上是AES的CTR模式(計數器模式)加上GMAC(Galois Message Authentication Code, 伽羅華消息認證碼)進行哈希計算的一種組合模式。GCM可以提供對消息的加密和完整性校驗,另外,它還可以提供附加消息的完整性校驗。

OpenSSL中的EVP接口支持執行經過身份驗證的加密和解密的功能,以及將未加密的關聯數據附加到消息的選項。這種帶有關聯數據的身份驗證加密(AEAD, Authenticated-Encryption with Associated-Data)方案通過對數據進行加密來提供機密性,並通過在加密數據上創建MAC標籤來提供真實性保證。

Key:對稱密鑰,它的長度可以爲128、192、256bits,用來加密明文的密碼。

IV(Initialisation Vector):初始向量,它的選取必須隨機。通常以明文的形式和密文一起傳送。它的作用和MD5的”加鹽”有些類似,目的是防止同樣的明文塊,始終加密成同樣的密文塊。

AAD(Additional Authenticated Data):附加身份驗證數據。AAD數據不需要加密,通常以明文形式與密文一起傳遞給接收者。

Mac tag(MAC標籤):將確保數據在傳輸和存儲過程中不會被意外更改或惡意篡改。該標籤隨後在解密操作期間使用,以確保密文和AAD未被篡改。在加密時,Mac tag由明文、密鑰Key、IV、AAD共同產生。

以下爲測試代碼:

namespace {

static const unsigned char gcm_key[] = { // 32 bytes, Key
	0xee, 0xbc, 0x1f, 0x57, 0x48, 0x7f, 0x51, 0x92, 0x1c, 0x04, 0x65, 0x66,
	0x5f, 0x8a, 0xe6, 0xd1, 0x65, 0x8b, 0xb2, 0x6d, 0xe6, 0xf8, 0xa0, 0x69,
	0xa3, 0x52, 0x02, 0x93, 0xa5, 0x72, 0x07, 0x8f
};

static const unsigned char gcm_iv[] = { // 12 bytes, IV(Initialisation Vector)
	0x99, 0xaa, 0x3e, 0x68, 0xed, 0x81, 0x73, 0xa0, 0xee, 0xd0, 0x66, 0x84
};

// Additional Authenticated Data(AAD): it is not encrypted, and is typically passed to the recipient in plaintext along with the ciphertext
static const unsigned char gcm_aad[] = { // 16 bytes
	0x4d, 0x23, 0xc3, 0xce, 0xc3, 0x34, 0xb4, 0x9b, 0xdb, 0x37, 0x0c, 0x43,
	0x7f, 0xec, 0x78, 0xde
};

#ifdef _MSC_VER
std::unique_ptr<unsigned char[]> aes_gcm_encrypt(const char* plaintext, int& length, unsigned char* tag)
{
	EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
	// Set cipher type and mode
	EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), nullptr, nullptr, nullptr);
	// Set IV length if default 96 bits is not appropriate
	EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN/*EVP_CTRL_AEAD_SET_IVLEN*/, sizeof(gcm_iv), nullptr);
	// Initialise key and IV
	EVP_EncryptInit_ex(ctx, nullptr, nullptr, gcm_key, gcm_iv);
	// Zero or more calls to specify any AAD
	int outlen;
	EVP_EncryptUpdate(ctx, nullptr, &outlen, gcm_aad, sizeof(gcm_aad));
	unsigned char outbuf[1024];
	// Encrypt plaintext
	EVP_EncryptUpdate(ctx, outbuf, &outlen, (const unsigned char*)plaintext, strlen(plaintext));
	length = outlen;
	std::unique_ptr<unsigned char[]> ciphertext(new unsigned char[length]);
	memcpy(ciphertext.get(), outbuf, length);
	// Finalise: note get no output for GCM
	EVP_EncryptFinal_ex(ctx, outbuf, &outlen);
	// Get tag
	EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG/*EVP_CTRL_AEAD_GET_TAG*/, 16, outbuf);
	memcpy(tag, outbuf, 16);
	// Clean up
	EVP_CIPHER_CTX_free(ctx);

	return ciphertext;
}

std::unique_ptr<unsigned char[]> aes_gcm_decrypt(const unsigned char* ciphertext, int& length, const unsigned char* tag)
{
	EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
	// Select cipher
	EVP_DecryptInit_ex(ctx, EVP_aes_256_gcm(), nullptr, nullptr, nullptr);
	// Set IV length, omit for 96 bits
	EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN/*EVP_CTRL_AEAD_SET_IVLEN*/, sizeof(gcm_iv), nullptr);
	// Specify key and IV
	EVP_DecryptInit_ex(ctx, nullptr, nullptr, gcm_key, gcm_iv);
	int outlen;
	// Zero or more calls to specify any AAD
	EVP_DecryptUpdate(ctx, nullptr, &outlen, gcm_aad, sizeof(gcm_aad));
	unsigned char outbuf[1024];
	// Decrypt plaintext
	EVP_DecryptUpdate(ctx, outbuf, &outlen, ciphertext, length);
	// Output decrypted block
	length = outlen;
	std::unique_ptr<unsigned char[]> plaintext(new unsigned char[length]);
	memcpy(plaintext.get(), outbuf, length);
	// Set expected tag value
	EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG/*EVP_CTRL_AEAD_SET_TAG*/, 16, (void*)tag);
	// Finalise: note get no output for GCM
	int rv = EVP_DecryptFinal_ex(ctx, outbuf, &outlen);
	// Print out return value. If this is not successful authentication failed and plaintext is not trustworthy.
	fprintf(stdout, "Tag Verify %s\n", rv > 0 ? "Successful!" : "Failed!");
	EVP_CIPHER_CTX_free(ctx);
	return plaintext;
}
#endif

} // namespace

int test_openssl_aes_gcm()
{
#ifdef _MSC_VER
	/* reference:
		https://github.com/openssl/openssl/blob/master/demos/evp/aesgcm.c
		https://wiki.openssl.org/index.php/EVP_Authenticated_Encryption_and_Decryption
	*/
	fprintf(stdout, "Start AES GCM 256 Encrypt:\n");
	const char* plaintext = "1234567890ABCDEFG!@#$%^&*()_+[]{};':,.<>/?|";
	fprintf(stdout, "src plaintext: %s, length: %d\n", plaintext, strlen(plaintext));
	int length = 0;
	std::unique_ptr<unsigned char[]> tag(new unsigned char[16]);
	std::unique_ptr<unsigned char[]> ciphertext = aes_gcm_encrypt(plaintext, length, tag.get());
	fprintf(stdout, "length: %d, ciphertext: ", length);
	for (int i = 0; i < length; ++i)
		fprintf(stdout, "%02x ", ciphertext.get()[i]);
	fprintf(stdout, "\nTag: ");
	for (int i = 0; i < 16; ++i)
		fprintf(stdout, "%02x ", tag.get()[i]);
	fprintf(stdout, "\n");

	fprintf(stdout, "\nStart AES GCM 256 Decrypt:\n");
	std::unique_ptr<unsigned char[]> result = aes_gcm_decrypt(ciphertext.get(), length, tag.get());
	fprintf(stdout, "length: %d, decrypted plaintext: ", length);
	for (int i = 0; i < length; ++i)
		fprintf(stdout, "%c", result.get()[i]);
	fprintf(stdout, "\n");

	if (strncmp(plaintext, (const char*)result.get(), length) == 0) {
		fprintf(stdout, "decrypt success\n");
		return 0;
	} else {
		fprintf(stderr, "decrypt fail\n");
		return -1;
	}
#else
	fprintf(stderr, "this test only support windows\n");
	return -1;
#endif
}

在Windows上執行結果如下:

以上是在OpenSSL較老的版本1.0.1g上執行的,在最新版1.1.1g上以上代碼也同樣可以執行。

在Windows上編譯1.1.1g版本源碼執行命令如下:no-asm選項爲不開啓彙編模式,編譯release則將debug-VC-WIN64A改爲VC-WIN64即可。

perl Configure debug-VC-WIN64A no-asm --prefix=D:\DownLoad\openssl\win64_debug
nmake
nmake install

在Linux上編譯1.1.1g版本源碼執行命令如下:

./config --prefix=/home/sensetime/Downloads/openssl/install
make
make insall

在1.1.1g源碼的demos/evp目錄下給出了gcm的示例aesgcm.c,這裏將以上測試代碼新加爲aesgcm2.cpp,編譯腳本如下:

#! /bin/bash

g++ -o test_aesgcm aesgcm.c -L ../../ -lcrypto -lssl -I../../include
g++ -o test_aesgcm2 aesgcm2.cpp -std=c++11 -L ../../ -lcrypto -lssl -I../../include

g++ -o test_aesccm aesccm.c -L ../../ -lcrypto -lssl -I../../include

執行結果如下:注意如果在Windows上執行aesgcm2.cpp,需要額外的 #include <openssl/applink.c> 

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

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