介紹如何在 C++ 語言中使用 Crypto++ 加密函式庫,以 RSA OAEP 搭配 SHA256 雜湊實作資料加密與解密。
在使用 Crypto++ 函式庫之前,請先確認系統上有安裝好該函式庫,Ubuntu Linux 可以參考 Ubuntu Linux 安裝、使用 Crypto++ 加密函式庫教學。
Crypto++ 將大部分的 RSA 加密與簽章功能都放在 rsa.h 標頭檔中,其中還包含了 RSAES(RSA Encryption Scheme)加密方案,以下是 RSAES 的實作細節。
產生 RSA 金鑰
在使用 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
RSAES-OAEP 加密與解密
最優非對稱加密填充(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 雜湊。
