介紹如何在 C++ 語言中使用 Crypto++ 加密函式庫,以 RSA OAEP 搭配 SHA256 雜湊實作資料加密與解密。
在使用 Crypto++ 函式庫之前,請先確認系統上有安裝好該函式庫,Ubuntu Linux 可以參考 Ubuntu Linux 安裝、使用 Crypto++ 加密函式庫教學。
Crypto++ 將大部分的 RSA 加密與簽章功能都放在 rsa.h
標頭檔中,其中還包含了 RSAES(RSA Encryption Scheme)加密方案,以下是 RSAES
的實作細節。
在使用 RSA 加密之前,要先產生 RSA 金鑰,以下是以 Crypto++ 產生、儲存、載入與驗證 RSA 金鑰的基本範例:
#include <iostream> #include <cstdlib> #include "cryptlib.h" #include "rsa.h" #include "osrng.h" #include "files.h" int main(int argc, char* argv[]) { using namespace CryptoPP; // 建立隨機亂數產生器 AutoSeededRandomPool prng; try { // 產生 RSA 私鑰 RSA::PrivateKey rsaPrivKey; rsaPrivKey.GenerateRandomWithKeySize(prng, 3072); // 產生對應的 RSA 公鑰 RSA::PublicKey rsaPubKey(rsaPrivKey); // 儲存 RSA 私鑰 FileSink privKeyFile("rsa_priv.key"); rsaPrivKey.Save(privKeyFile); // 儲存 RSA 公鑰 FileSink pubKeyFile("rsa_pub.key"); rsaPubKey.Save(pubKeyFile); } catch(const Exception& e) { std::cerr << e.what() << std::endl; return EXIT_FAILURE; } try { // 從檔案載入 RSA 金鑰 RSA::PrivateKey rsaPrivKey2; FileSource privKeyFile2("rsa_priv.key", true); rsaPrivKey2.Load(privKeyFile2); // 驗證 RSA 私鑰 if(!rsaPrivKey2.Validate(prng, 3)) { std::cerr << "RSA 私鑰驗證失敗" << std::endl; return EXIT_FAILURE; } // 產生對應的 RSA 公鑰 RSA::PublicKey rsaPubKey2(rsaPrivKey2); } catch(const Exception& e) { std::cerr << e.what() << std::endl; return EXIT_FAILURE; } return EXIT_SUCCESS; }
這裡我們透過 AutoSeededRandomPool
使用作業系統所提供的隨機亂數產生器來產生長度為 3072
的 RSA 私鑰,並從私鑰產生對應的公鑰。
將這段程式碼儲存至 rsa_gen_key.cpp
,使用以下指令進行編譯與執行:
# 編譯 Crypto++ 應用程式 g++ -I/usr/include/crypto++ -o rsa_gen_key rsa_gen_key.cpp -lcryptopp # 執行應用程式 ./rsa_gen_key
執行之後就會產生一組 RSA 的私鑰與公鑰,分別儲存於 rsa_priv.key
與 rsa_pub.key
檔案中。
Crypto++ 在儲存金鑰時是採用較嚴格的 ASN.1 DER 格式,而在讀取金鑰時則是採用較為寬鬆的 ASN.1 BER 格式進行解析。我們可以使用 dumpasn1
工具來查看金鑰檔案的內容,這個工具在 Ubuntu Linux 中可以透過 apt
安裝:
# 安裝 dumpasn1 套件
sudo apt install dumpasn1
查看 RSA 私鑰與公鑰內容:
# 查看 RSA 私鑰內容 dumpasn1 rsa_priv.key # 查看 RSA 公鑰內容 dumpasn1 rsa_pub.key
最優非對稱加密填充(Optimal Asymmetric Encryption Padding,縮寫為 OAEP)是一種經常與 RSA 加密一起使用的填充方法,可讓 RSA 加密過程添加隨機性元素,增加 RSA 加密的安全性。
以下是拿前面產生的 RSA 公鑰與私鑰,使用 RSAES-OAEP 進行加密與解密的範例:
#include <iostream> #include <cstdlib> #include "cryptlib.h" #include "rsa.h" #include "osrng.h" #include "files.h" #include "assert.h" int main(int argc, char* argv[]) { using namespace CryptoPP; // 建立隨機亂數產生器 AutoSeededRandomPool prng; // 原始資料、加密資料、解密後資料 std::string plain="RSA Encryption", cipher, recovered; std::cout << "原始資料:" << plain << std::endl; try { // 從檔案載入 RSA 公鑰 RSA::PublicKey rsaPubKey; FileSource pubKeyFile("rsa_pub.key", true); rsaPubKey.Load(pubKeyFile); // 以 RSA 公鑰建立 RSAES 加密器 RSAES_OAEP_SHA_Encryptor encryptor(rsaPubKey); // 確認明文長度沒有超過上限值 assert(plain.size() <= encryptor.FixedMaxPlaintextLength()); // 以 RSAES 加密 StringSource ss1(plain, true, new PK_EncryptorFilter(prng, encryptor, new StringSink(cipher) ) // PK_EncryptorFilter ); // StringSource // 從檔案載入 RSA 私鑰 RSA::PrivateKey rsaPrivKey; FileSource privKeyFile("rsa_priv.key", true); rsaPrivKey.Load(privKeyFile); // 以 RSA 私鑰建立 RSAES 解密器 RSAES_OAEP_SHA_Decryptor decryptor(rsaPrivKey); // 以 RSAES 解密 StringSource ss2(cipher, true, new PK_DecryptorFilter(prng, decryptor, new StringSink(recovered) ) // PK_DecryptorFilter ); // StringSource std::cout << "解密資料:" << recovered << std::endl; } catch(const Exception& e) { std::cerr << e.what() << std::endl; return EXIT_FAILURE; } return EXIT_SUCCESS; }
這段程式碼會使用 RSA 公鑰進行加密,並以對應的 RSA 私鑰進行解密,將其儲存至 rsa_enc.cpp
之後,編譯方式跟前面的例子相同。
# 編譯 Crypto++ 應用程式 g++ -I/usr/include/crypto++ -o rsa_enc rsa_enc.cpp -lcryptopp # 執行應用程式 ./rsa_enc
RSA 演算法沒辦法加密太大的資料,我們可以使用 encryptor.FixedMaxPlaintextLength()
來取得明文長度的上限值,確保資料長度沒有超過上限。
Crypto++ 的 RSAES_OAEP_SHA_Encryptor
與 RSAES_OAEP_SHA_Decryptor
其實就是 RSAES-OAEP 搭配 SHA1 雜湊的加密與解密器,其定義位於 rsa.h
標頭檔案中:
DOCUMENTED_TYPEDEF(RSAES<OAEP<SHA1> >::Decryptor, RSAES_OAEP_SHA_Decryptor); DOCUMENTED_TYPEDEF(RSAES<OAEP<SHA1> >::Encryptor, RSAES_OAEP_SHA_Encryptor);
我們也可以自行抽換 OAEP 內部的雜湊演算法,例如更換為 SHA256 雜湊:
#include <iostream> #include <cstdlib> #include "cryptlib.h" #include "rsa.h" #include "osrng.h" #include "files.h" #include "assert.h" int main(int argc, char* argv[]) { using namespace CryptoPP; // 建立隨機亂數產生器 AutoSeededRandomPool prng; // 原始資料、加密資料、解密後資料 std::string plain="RSA Encryption", cipher, recovered; std::cout << "原始資料:" << plain << std::endl; try { // 從檔案載入 RSA 公鑰 RSA::PublicKey rsaPubKey; FileSource pubKeyFile("rsa_pub.key", true); rsaPubKey.Load(pubKeyFile); // 以 RSA 公鑰建立 RSAES 加密器(採用 SHA256) RSAES<OAEP<SHA256> >::Encryptor encryptor(rsaPubKey); // 確認明文長度沒有超過上限值 assert(plain.size() <= encryptor.FixedMaxPlaintextLength()); // 以 RSAES 加密 StringSource ss1(plain, true, new PK_EncryptorFilter(prng, encryptor, new StringSink(cipher) ) // PK_EncryptorFilter ); // StringSource // 從檔案載入 RSA 私鑰 RSA::PrivateKey rsaPrivKey; FileSource privKeyFile("rsa_priv.key", true); rsaPrivKey.Load(privKeyFile); // 以 RSA 私鑰建立 RSAES 解密器(採用 SHA256) RSAES<OAEP<SHA256> >::Decryptor decryptor(rsaPrivKey); // 以 RSAES 解密 StringSource ss2(cipher, true, new PK_DecryptorFilter(prng, decryptor, new StringSink(recovered) ) // PK_DecryptorFilter ); // StringSource std::cout << "解密資料:" << recovered << std::endl; } catch(const Exception& e) { std::cerr << e.what() << std::endl; return EXIT_FAILURE; } return EXIT_SUCCESS; }
在搭配不同的演算法時,可以參考 Security level 的建議,選擇相同安全等級的演算法,例如 3072 位元的 RSA 非對稱式加密可以搭配 256 位元的 SHA 雜湊。