带有 EVP 方法的 OpenSSL RSA Encryption/Decryption

OpenSSL RSA Encryption/Decryption with EVP Methods

我使用 Openssl EVP (OpenSSL Ref) 方法创建了一个 rsa class。

rsa.h

#ifndef RSA_RSA_H
#define RSA_RSA_H

#include <string>
#include <stdexcept>
#include <sstream>
#include <iostream>

#include <openssl/evp.h>
#include <openssl/pem.h>
#include <openssl/err.h>

#include "utils.h"

#define PADDING RSA_PKCS1_OAEP_PADDING
#define LOGGING

namespace tools
{

class CryptoRSA
{

private:
    int _keysize;
    char *_rsaPrivateKeyStr, *_privateKeyStr, *_publicKeyStr;
    unsigned char* _encryptedKey, *_iv;

    EVP_PKEY *_pkey;
    EVP_CIPHER_CTX *rsaEncryptContext, *rsaDecryptContext;

    static std::string getOpenSSLError();

    bool generateRSAKey();

    bool loadKeyFromFile(const std::string &filepath);
    bool loadKeyFromStr(const std::string &str);

public:

    explicit CryptoRSA(int keysize);
    explicit CryptoRSA(const std::string &key);
    virtual ~CryptoRSA();

    inline unsigned char* getRSAIV() const { return _iv; }
    inline unsigned char* getRSAEncryptedKey() const { return _encryptedKey; }
    inline EVP_PKEY *getEvpPkey() { return _pkey; }
    inline int getEvpPkeySize(EVP_PKEY *key) { return EVP_PKEY_size(key); }

    char *getRSAPrivateKeyStr();
    char *getPrivateKeyStr();
    char *getPublicKeyStr();

    int encryptEVP(EVP_PKEY *key, const unsigned char *message, size_t messageLength, unsigned char **encryptedMessage, unsigned char **encryptedKey, size_t *encryptedKeyLength, unsigned char **iv);
    int decryptEVP(EVP_PKEY *key, unsigned char *encryptedMessage, size_t encryptedMessageLength, unsigned char *encryptedKey, size_t encryptedKeyLength, unsigned char *iv, unsigned char **decryptedMessage);


};

} // namespace tools

#endif //RSA_RSA_H

和rsa.cpp

#include "rsa.h"

namespace tools
{

CryptoRSA::CryptoRSA(int keysize) :  _keysize(keysize), _pkey(nullptr)
{
    // Initalize contexts
    rsaEncryptContext = EVP_CIPHER_CTX_new();
    rsaDecryptContext = EVP_CIPHER_CTX_new();


    // Generate the RSA Key
    if ( !generateRSAKey() )
    {
        #ifdef LOGGING
        std::cerr << "Error at generating the RSA key!" << std::endl;
        #endif
        throw std::runtime_error("Error at generating the RSA key!");
    }
}

CryptoRSA::CryptoRSA(const std::string &key) : _keysize(-1), _pkey(nullptr)
{
    // Initalize contexts
    rsaEncryptContext = EVP_CIPHER_CTX_new();
    rsaDecryptContext = EVP_CIPHER_CTX_new();

    // Load pkey from file or string
    if ( !loadKeyFromFile(key) )
    {
        #ifdef LOGGING
        std::cerr << "Could not load key from file/string!" << std::endl;
        #endif
    }

}

CryptoRSA::~CryptoRSA()
{
    EVP_PKEY_free(_pkey);

    EVP_CIPHER_CTX_free(rsaEncryptContext);
    EVP_CIPHER_CTX_free(rsaDecryptContext);
}

std::string CryptoRSA::getOpenSSLError()
{
    char *buf;
    BIO *bio = BIO_new(BIO_s_mem());
    ERR_print_errors(bio);

    size_t len = static_cast<size_t >(BIO_get_mem_data(bio, &buf));
    std::string err(buf, len);
    BIO_free(bio);

    return err;
}

bool CryptoRSA::generateRSAKey()
{

    EVP_PKEY_CTX *context = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL);

    if(EVP_PKEY_keygen_init(context) <= 0)
    {
        return false;
    }

    if(EVP_PKEY_CTX_set_rsa_keygen_bits(context, _keysize) <= 0)
    {
        return false;
    }

    if(EVP_PKEY_keygen(context, &_pkey) <= 0)
    {
        return false;
    }

    EVP_PKEY_CTX_free(context);

    return true;
}

bool CryptoRSA::loadKeyFromFile(const std::string &filepath)
{

    if ( existsFile(filepath) )
    {
        if ( loadKeyFromStr(readBinFile(filepath)) )
        {
            #ifdef LOGGING
            std::cerr << "Loaded successfully rsa key from file " << filepath << std::endl;
            #endif
            return true;
        }
    } else
    {
        if ( loadKeyFromStr(filepath) )
        {
            #ifdef LOGGING
            std::cerr << "Loaded successfully rsa key from string!" << std::endl;
            #endif
            return true;
        }
    }

    return false;
}

bool CryptoRSA::loadKeyFromStr(const std::string &str)
{

    std::string fLine;
    std::istringstream f(str);
    std::getline(f, fLine);

    BIO *bioPrivate = BIO_new(BIO_s_mem());
    BIO_write(bioPrivate, str.c_str(), static_cast<int>(str.length()));

    if (fLine == "-----BEGIN RSA PRIVATE KEY-----")
    {

        _pkey = PEM_read_bio_PrivateKey(bioPrivate, nullptr, nullptr, nullptr);


    } else if (fLine == "-----BEGIN PUBLIC KEY-----")
    {

        _pkey = PEM_read_bio_PUBKEY(bioPrivate, nullptr, nullptr, nullptr);

    } else
    {
        #ifdef LOGGING
        std::cerr << "Unsupported file provided with file header: " << fLine << std::endl;
        #endif
        BIO_free(bioPrivate);
        return false;
    }

    BIO_free(bioPrivate);

    return true;
}

char* CryptoRSA::getRSAPrivateKeyStr()
{
    RSA *rsaPrivateKey = EVP_PKEY_get0_RSA(_pkey);

    BIO *bioPrivate = BIO_new(BIO_s_mem());
    PEM_write_bio_RSAPrivateKey(bioPrivate, rsaPrivateKey, nullptr, nullptr, 0, nullptr, nullptr);

    BIO_flush(bioPrivate);
    BIO_get_mem_data(bioPrivate, &_rsaPrivateKeyStr);

    return _rsaPrivateKeyStr;
}

char* CryptoRSA::getPrivateKeyStr()
{

    BIO *bioPrivate = BIO_new(BIO_s_mem());
    PEM_write_bio_PrivateKey(bioPrivate, _pkey, NULL, NULL, 0, 0, NULL);

    BIO_flush(bioPrivate);
    BIO_get_mem_data(bioPrivate, &_privateKeyStr);

    return _privateKeyStr;
}

char* CryptoRSA::getPublicKeyStr()
{

    BIO *bioPublic = BIO_new(BIO_s_mem());
    PEM_write_bio_PUBKEY(bioPublic, _pkey);

    BIO_flush(bioPublic);
    BIO_get_mem_data(bioPublic, &_publicKeyStr);

    return _publicKeyStr;
}

int CryptoRSA::encryptEVP(EVP_PKEY *key, const unsigned char *message, size_t messageLength, unsigned char **encryptedMessage,
                          unsigned char **encryptedKey, size_t *encryptedKeyLength, unsigned char **iv)
{

    // Allocate memory for everything
    size_t encryptedMessageLength = 0;
    size_t blockLength = 0;

    *encryptedKey = (unsigned char*)malloc(EVP_PKEY_size(key));
    *iv = (unsigned char*)malloc(EVP_MAX_IV_LENGTH);

    *encryptedMessage = (unsigned char*)malloc(messageLength + EVP_MAX_IV_LENGTH);

    if(!EVP_SealInit(rsaEncryptContext, EVP_aes_256_cbc(), encryptedKey, (int*)encryptedKeyLength, *iv, &key, 1))
    {
        #ifdef LOGGING
        std::cerr << "Error during EVP_SealInit in RSA encrypt: " << getOpenSSLError() << std::endl;
        #endif
        return -1;
    }

    if(!EVP_SealUpdate(rsaEncryptContext, *encryptedMessage + encryptedMessageLength, (int*)&blockLength, (const unsigned char*)message, (int)messageLength))
    {
        #ifdef LOGGING
        std::cerr << "Error during EVP_SealUpdate in RSA encrypt: " << getOpenSSLError() << std::endl;
        #endif
        return -1;
    }
    encryptedMessageLength += blockLength;

    if(!EVP_SealFinal(rsaEncryptContext, *encryptedMessage + encryptedMessageLength, (int*)&blockLength))
    {
        #ifdef LOGGING
        std::cerr << "Error during EVP_SealFinal in RSA encrypt: " << getOpenSSLError() <<std::endl;
        #endif
        return -1;
    }
    encryptedMessageLength += blockLength;

    return (int)encryptedMessageLength;
}

int CryptoRSA::decryptEVP(EVP_PKEY *key, unsigned char *encryptedMessage, size_t encryptedMessageLength, unsigned char *encryptedKey,
                          size_t encryptedKeyLength, unsigned char *iv, unsigned char **decryptedMessage)
{

    // Allocate memory for everything
    size_t decryptedMessageLength = 0;
    size_t blockLength = 0;

    *decryptedMessage = (unsigned char*)malloc(encryptedMessageLength + EVP_MAX_IV_LENGTH);

    // Decrypt it!
    if(!EVP_OpenInit(rsaDecryptContext, EVP_aes_256_cbc(), encryptedKey, getEvpPkeySize(key), iv, key))
    {
        #ifdef LOGGING
        std::cerr << "Error during EVP_OpenInit in RSA decrypt: " << getOpenSSLError() << std::endl;
        #endif
        return -1;
    }

    if(!EVP_OpenUpdate(rsaDecryptContext, (unsigned char*)*decryptedMessage + decryptedMessageLength, (int*)&blockLength, encryptedMessage, (int)encryptedMessageLength))
    {
        #ifdef LOGGING
        std::cerr << "Error during EVP_OpenUpdate in RSA decrypt: " << getOpenSSLError() << std::endl;
        #endif
        return -1;
    }
    decryptedMessageLength += blockLength;

    if(!EVP_OpenFinal(rsaDecryptContext, (unsigned char*)*decryptedMessage + decryptedMessageLength, (int*)&blockLength))
    {
        #ifdef LOGGING
        std::cerr << "Error during EVP_OpenFinal in RSA decrypt: " << getOpenSSLError() << std::endl;
        #endif
        return -1;
    }
    decryptedMessageLength += blockLength;

    return (int)decryptedMessageLength;
}


} // namespace tools

main.cpp

#include <iostream>
#include <memory>
#include <cstring>

#include "rsa.h"

bool writeBinFile(const std::string &filepath, const char* content, long contentLength, bool append=false)
{
    std::fstream out;
    if (append)
    {
        out.open(filepath, std::ios::out | std::ios::app | std::ios::binary);
    } else
    {
        out.open(filepath, std::ios::out | std::ios::binary);
    }

    if (out.is_open())
    {
        out.write(content, contentLength);
        return true;
    } else
    {
        return false;
    }
}

void encrypt_decrypt_with_evp(std::string msg_to_encrypt)
{
    std::unique_ptr<tools::CryptoRSA> cryptoRSA = std::unique_ptr<tools::CryptoRSA>(new tools::CryptoRSA(2048));

    unsigned char *encryptedMessage = nullptr;
    char *decryptedMessage = nullptr;
    unsigned char *encryptedKey;
    unsigned char *iv;
    size_t encryptedKeyLength;

    std::string priv = cryptoRSA->getRSAPrivateKeyStr();
    std::string pub = cryptoRSA->getPublicKeyStr();

    // Save RSA KeyPair
    writeBinFile("RSAPrivateKey.pem", priv.c_str(), priv.length());
    writeBinFile("PublicKey.pem", pub.c_str(), pub.length());

    // Start RSA Encryption
    int encryptedMessageLength = cryptoRSA->encryptEVP(cryptoRSA->getEvpPkey(), (const unsigned char*)msg_to_encrypt.c_str(), msg_to_encrypt.size()+1,
                                                       &encryptedMessage, &encryptedKey, &encryptedKeyLength, &iv);

    if(encryptedMessageLength == -1)
    {
        std::cerr << "Encryption failed" << std::endl;
        exit(1);
    }

    std::cout << "Encrypted message: " << encryptedMessage << std::endl;
    writeBinFile("enc_msg.bin", reinterpret_cast<const char *>(encryptedMessage), encryptedMessageLength);


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    // Start RSA Decryption
    int decryptedMessageLength = cryptoRSA->decryptEVP(cryptoRSA->getEvpPkey(), encryptedMessage, (size_t)encryptedMessageLength,
                                                       encryptedKey, encryptedKeyLength, iv, (unsigned char**)&decryptedMessage);

    if(decryptedMessageLength == -1)
    {
        std::cerr << "Decryption failed" << std::endl;
        exit(1);
    }

    std::cout << "Decrypted message: " << decryptedMessage << std::endl;

}

int main(int argc, char* argv[])
{

    encrypt_decrypt_with_evp("abcdef");


    return 0;
}

使用 class 实现的 RSA 加密和解密完全可以正常工作。 我想要实现的是:

  1. 用我实现的解决方案加密像 abcdef 这样的消息,并将内容保存在文件中 enc_msg.bin
  2. 使用openssl命令行界面(例如:openssl rsautl)解密消息

或者:

  1. 使用openssl命令行接口
  2. 加密消息abcdef
  3. 用我实现的解决方案解密创建的文件

如您所见,EVP_SealInit 使用带有 encryptedKey 和 IV 的 EVP_aes_256_cbc。但是我没有找到将这些参数提供给 openssl rsautl cli 的可能性。

有谁知道如何解密使用 EVP 方法创建的消息(使用 Openssl cli)(EVP_SealInit、EVP_SealUpdate、EVP_SealFinal)?

非常感谢您的帮助!

在@Topaco 的帮助下,我找到了解决方案。这是一个Hybrid Encryption

  1. 我还保存了 EncryptedKey 和 IV 到 rsa_ek.binrsa_iv.txt 文件。

  2. 使用openssl rsautl cli

    解密rsa_ek.bin
openssl rsautl -decrypt -inkey RSAPrivateKey.pem -in rsa_ek.bin -out rsa_ek.txt
  1. 使用 AES-256-CBC 密码
  2. 解密 enc_msg.bin 文件
openssl enc -aes-256-cbc -d -in enc_msg.bin -K $(xxd -p -c 256 rsa_ek.txt) -iv $(xxd -p -c 256 rsa_iv.txt) -out enc_msg.dec.txt