C/C++

C++ 語言使用 Crypto++ 實作 RSA-OAEP 搭配 SHA256 加密教學與範例

介紹如何在 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.keyrsa_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_EncryptorRSAES_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 雜湊。

參考資料

Share
Published by
Office Guide

Recent Posts

Python 使用 PyAutoGUI 自動操作滑鼠與鍵盤

本篇介紹如何在 Python ...

1 年 ago

Ubuntu Linux 以 WireGuard 架設 VPN 伺服器教學與範例

本篇介紹如何在 Ubuntu ...

1 年 ago

Linux 網路設定 ip 指令用法教學與範例

本篇介紹如何在 Linux 系...

1 年 ago

Linux 以 Cryptsetup、LUKS 加密 USB 隨身碟教學與範例

介紹如何在 Linux 系統中...

1 年 ago