对称加密算法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, Key0xee, 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 bytes0x4d, 0x23, 0xc3, 0xce, 0xc3, 0x34, 0xb4, 0x9b, 0xdb, 0x37, 0x0c, 0x43,0x7f, 0xec, 0x78, 0xde
};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 modeEVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), nullptr, nullptr, nullptr);// Set IV length if default 96 bits is not appropriateEVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, sizeof(gcm_iv), nullptr);// Initialise key and IVEVP_EncryptInit_ex(ctx, nullptr, nullptr, gcm_key, gcm_iv);// Zero or more calls to specify any AADint outlen;EVP_EncryptUpdate(ctx, nullptr, &outlen, gcm_aad, sizeof(gcm_aad));unsigned char outbuf[1024];// Encrypt plaintextEVP_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 GCMEVP_EncryptFinal_ex(ctx, outbuf, &outlen);// Get tagEVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, 16, outbuf);memcpy(tag, outbuf, 16);// Clean upEVP_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 cipherEVP_DecryptInit_ex(ctx, EVP_aes_256_gcm(), nullptr, nullptr, nullptr);// Set IV length, omit for 96 bitsEVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, sizeof(gcm_iv), nullptr);// Specify key and IVEVP_DecryptInit_ex(ctx, nullptr, nullptr, gcm_key, gcm_iv);int outlen;// Zero or more calls to specify any AADEVP_DecryptUpdate(ctx, nullptr, &outlen, gcm_aad, sizeof(gcm_aad));unsigned char outbuf[1024];// Decrypt plaintextEVP_DecryptUpdate(ctx, outbuf, &outlen, ciphertext, length);// Output decrypted blocklength = outlen;std::unique_ptr<unsigned char[]> plaintext(new unsigned char[length]);memcpy(plaintext.get(), outbuf, length);// Set expected tag valueEVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, 16, (void*)tag);// Finalise: note get no output for GCMint 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;
}} // namespaceint test_openssl_aes_gcm()
{/* reference:https://github.com/openssl/openssl/blob/master/demos/evp/aesgcm.chttps://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;}
}
在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/bashg++ -o test_aesgcm aesgcm.c -L ../../ -lcrypto -lssl -I../../include
g++ -o test_aesgcm2 aesgcm2.cpp -std=c++11 -L ../../ -lcrypto -lssl -I../../includeg++ -o test_aesccm aesccm.c -L ../../ -lcrypto -lssl -I../../include
執行結果如下:注意如果在Windows上執行aesgcm2.cpp,需要額外的 #include <openssl/applink.c>?
GitHub:https://github.com//fengbingchun/OpenSSL_Test
總結
以上是生活随笔為你收集整理的对称加密算法AES之GCM模式简介及在OpenSSL中使用举例的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: GitHub/GitLab/Gitee中
- 下一篇: C和C++安全编码笔记:整数安全