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>