RSA数字签名算法-Cpp-OpenSSL

前言

RSA加密与签名是很使用率非常高的一套算法。这次工作中有需求制作一个C++版的RSA加密与签名的Demo。这里记录一下踩过的坑吧。
如果你正好也需要Cpp的RSA算法,希望能给你提供点帮助。

进入正题

本次笔者使用的是OpenSSL工具包中的RSA算法。

适用场景:两台服务器之间数据传输交互验证。

Demo工程GitHub链接

1、公钥,私钥注意事项

  • 本示例使用 emersonfxbx.openssl.v140.desktop.x86 NuGet库

    与其他高级语言不同的是,这里的RSA算法是基于OpenSSL 1.0.1e版本。所以需要使用密钥格式为 PKCS#1 的密钥对(只对私钥有要求,公钥PKCS#1与PKCS#8通用)。
    推荐一个密钥对生成网站程默的博客.

  • 密钥字符串需要注意格式,如代码所示(每64位一次换行):

//来自另外一台服务器的公钥,用于验证签名,此值不变。                                                     -------公钥使用 pkcs#8  
const std::string & _publicKey =
    "-----BEGIN PUBLIC KEY-----\n"\
    "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCOlUoW84bA+8b23PBM7OWWO3+o\n"\
    "wISx42v7bWDH8c6rUa0zeeh2bh43sJrmoNdwMWkRNKgeHxw8BITXg/IV2j8x9Z2/\n"\
    "+uROYoFLgcIDnxotgumylfxDZo/RxFyhpl8Rx4/zlj6quSOXoMNvkXpWKLhty8+g\n"\
    "Wlsv2othCFU9Ym4cQQIDAQAB\n"\
    "-----END PUBLIC KEY-----\n";

//开发者私钥,由开发者自行生成,用于解密回调数据,请将此私钥修改为开发者自已的私钥。 --------私钥使用 pkcs#1
const std::string & _privateKey =
    "-----BEGIN RSA PRIVATE KEY-----\n"\
    "MIICXAIBAAKBgQCLF/ZGuuNwGpS+YRFPr1kMEz+hO7HesleruuyoepVnex4cLP1V\n"\
    "PDVUN6l1VgO6bJk3ToHWGme+MYIyduQwVafm6mJcL3gj5k6UymXFyZCp/+n1B1u3\n"\
    "6NQQLP4/HvW/HhGBwFXhhGTdkzLUhrZE2Xfz6NbwsMdEkDq9nhcXxAC96wIDAQAB\n"\
    "AoGAPUd0R9sEYppDV9CZ+NpOx+QfD2CmT2+Q8maq5tsCwZFbRZyIi6m38P+I19nq\n"\
    "UJKRue0LhJEjjYZwTt1UUPsbuhTYGNpVHzCsie1QVkBX19tlRrhjETisCQF8QSiT\n"\
    "DXnhmaqNXAUGchnCntp85viCXPxHmj0m0ymU5/ctr4CBXIkCQQDq64bdPet2Ky+1\n"\
    "FoFNm/mYk6UlUnkBz+z1akvWYQn3H9fmhOf1gPQknPoHo7A6LZGaL+K/qPTW/7ts\n"\
    "3+qFDuotAkEAl5Mnwd+grqqn0nIW/A6TxZqMBJlqxpbjcfqL+TvEprVXJMFI+ufb\n"\
    "NVrpuljDVfHSZk2NYYtCVy0cK4tLlcZPdwJAIbOsY20QrKFBdN9HqZSo2CTGWnZc\n"\
    "edAUlJitTJIbVeKxnJaQmH3piJ8kl5f6Hj6PVulrxEc+6OFDSDlPcctT+QJAdz53\n"\
    "mpg5qu/q0y6aUnWNX3m0CbJARDdUe8il8c9JZ/Vlty6wIWPiGlmJYuaN1cFGyuDc\n"\
    "Bw8tg7OjY8ZUEmJPBQJBAOao0jpWLKD/9HMM79k5v8Yhm+v5M58B1qtoguhED+Id\n"\
    "UKM5FzeT04lfQtOx10DhkOaS4Rb7/h05hz+in/AQXSo=\n"\
    "-----END RSA PRIVATE KEY-----\n";

2、RSA算法核心部分

RSACrypto.h

#pragma once

#include <string>


class RSACrypto
{
public:
    RSACrypto(
        const std::string& public_key,
        const std::string& private_key) :
        m_strPublicKey(public_key),
        m_strPrivateKey(private_key),
        m_PublickeyRsa(nullptr), m_PrivatekeyRsa(nullptr)
    {

    }

    virtual ~RSACrypto();

    bool InitRsa();

    void Release();


    bool EncryptByPublicKey(const std::string& src, std::string& encrypted);
    bool DecryptByPublicKey(const std::string& encrypted, std::string& decrypted);


    bool EncryptByPrivateKey(const std::string& src, std::string& encrypted);
    bool DecryptByPrivateKey(const std::string& encrypted, std::string& decrypted);


    bool SignByPrivateKey(const std::string &src, std::string& sign);
    bool VerifyByPublicKey(const std::string &src, const std::string& sign);

private:
    bool InitPrivateRSA();
    bool InitPublicRSA();
private:

    const std::string & m_strPublicKey;
    const std::string & m_strPrivateKey;

    RSA *m_PublickeyRsa;
    RSA *m_PrivatekeyRsa;
};

RSACrypto.Cpp

#include <openssl/rsa.h>
#include <openssl/pem.h>
#include <openssl/bio.h>
#include <vector>
#include <algorithm>

#include "RSACrypto.h"

RSACrypto::~RSACrypto()
{
    Release();
}

bool  RSACrypto::InitPrivateRSA()
{
    BIO* keybio = nullptr;

    keybio = BIO_new_mem_buf((char*)m_strPrivateKey.data(), -1);
    if (keybio == NULL)
    {
        printf("Failed to create private key BIO\n");
        return false;
    }

    m_PrivatekeyRsa = PEM_read_bio_RSAPrivateKey(keybio, &m_PrivatekeyRsa, NULL, NULL);
    if (!m_PrivatekeyRsa)
    {
        printf("Failed to create private RSA\n");
        BIO_set_close(keybio, BIO_CLOSE);
        BIO_free(keybio);
        return false;
    }

    BIO_set_close(keybio, BIO_CLOSE);
    BIO_free(keybio);
    return true;
}

bool  RSACrypto::InitPublicRSA()
{
    BIO* keybio = nullptr;

    keybio = BIO_new_mem_buf((char*)m_strPublicKey.data(), -1);
    if (keybio == NULL)
    {
        printf("Failed to create public key BIO\n");
        return false;
    }
    m_PublickeyRsa = PEM_read_bio_RSA_PUBKEY(keybio, &m_PublickeyRsa, NULL, NULL);
    if (!m_PublickeyRsa)
    {
        printf("Failed to create public RSA\n");
        BIO_set_close(keybio, BIO_CLOSE);
        BIO_free(keybio);
        return false;
    }

    BIO_set_close(keybio, BIO_CLOSE);
    BIO_free(keybio);
    return true;
}

bool RSACrypto::InitRsa()
{
    if (!InitPrivateRSA())
    {
        return false;
    }

    if (!InitPublicRSA())
    {
        return false;
    }

    return true;
}

void RSACrypto::Release()
{
    if (m_PublickeyRsa != nullptr)
    {
        RSA_free(m_PublickeyRsa); m_PublickeyRsa = nullptr;
    }

    if (m_PrivatekeyRsa != nullptr)
    {
        RSA_free(m_PrivatekeyRsa); m_PrivatekeyRsa = nullptr;
    }
}

bool RSACrypto::EncryptByPublicKey(const std::string & src, std::string & encrypted)
{
    std::string result;
    const int keysize = RSA_size(m_PublickeyRsa);
    std::vector<unsigned char> block(keysize);
    const int chunksize = keysize - RSA_PKCS1_PADDING_SIZE;
    int inputlen = src.length();

    for (int i = 0; i < inputlen; i += chunksize)
    {
        auto resultsize = RSA_public_encrypt(std::min(chunksize, inputlen - i), (uint8_t*)&src[i], &block[0], (RSA*)m_PublickeyRsa, RSA_PKCS1_PADDING);
        if (resultsize == -1)
        {
            return false;
        }
        encrypted.append((char*)block.data(), resultsize);
    }

    return true;
}

bool RSACrypto::DecryptByPublicKey(const std::string & encrypted, std::string & decrypted)
{
    const int keysize = RSA_size(m_PublickeyRsa);
    std::vector<unsigned char> block(keysize);

    int inputlen = encrypted.length();

    for (int i = 0; i < (int)encrypted.length(); i += keysize)
    {
        int flen = std::min(keysize, inputlen - i);

        auto resultsize = RSA_public_decrypt(flen, (uint8_t*)&encrypted[i], &block[0], m_PublickeyRsa, RSA_PKCS1_PADDING);

        if (resultsize == -1)
        {
            return false;
        }

        decrypted.append((char*)block.data(), resultsize);
    }
    return true;
}

bool RSACrypto::EncryptByPrivateKey(const std::string & src, std::string & encrypted)
{
    std::string result;
    const int keysize = RSA_size(m_PrivatekeyRsa);
    std::vector<unsigned char> block(keysize);
    const int chunksize = keysize - RSA_PKCS1_PADDING_SIZE;
    int inputlen = src.length();

    for (int i = 0; i < (int)src.length(); i += chunksize)
    {
        int flen = std::min<int>(chunksize, inputlen - i);

        std::fill(block.begin(), block.end(), 0);

        auto resultsize = RSA_private_encrypt(flen, (uint8_t*)&src[i], &block[0], m_PrivatekeyRsa, RSA_PKCS1_PADDING);
        if (resultsize == -1)
        {
            return false;
        }

        encrypted.append((char*)block.data(), resultsize);
    }
    return true;
}

bool RSACrypto::DecryptByPrivateKey(const std::string & encrypted, std::string & decrypted)
{
    const int keysize = RSA_size(m_PrivatekeyRsa);
    std::vector<unsigned char> block(keysize);

    for (int i = 0; i < (int)encrypted.length(); i += keysize)
    {
        auto resultsize = RSA_private_decrypt(std::min<int>(keysize, encrypted.length() - i), (uint8_t*)&encrypted[i], &block[0], m_PrivatekeyRsa, RSA_PKCS1_PADDING);
        if (resultsize == -1)
        {
            return false;
        }
        decrypted.append((char*)block.data(), resultsize);
    }

    return true;
}



bool RSACrypto::SignByPrivateKey(const std::string &src, std::string& sign)
{
    EVP_MD_CTX* rsa_sign_ctx = EVP_MD_CTX_create();
    EVP_PKEY* pri_key = EVP_PKEY_new();

    auto clean = [pri_key, rsa_sign_ctx] {
        EVP_PKEY_free(pri_key);
        EVP_MD_CTX_cleanup(rsa_sign_ctx);
    };

    //EVP_PKEY_assign_RSA(pri_key, m_PrivatekeyRsa);
    EVP_PKEY_set1_RSA(pri_key, m_PrivatekeyRsa);
    if (EVP_DigestSignInit(rsa_sign_ctx, NULL, EVP_sha1(), NULL, pri_key) <= 0)
    {
        clean();
        return false;
    }

    if (EVP_DigestSignUpdate(rsa_sign_ctx, src.data(), src.length()) <= 0)
    {
        clean();
        return false;
    }

    size_t sign_len;
    if (EVP_DigestSignFinal(rsa_sign_ctx, NULL, &sign_len) <= 0)
    {
        clean();
        return false;
    }

    sign.resize(sign_len);
    if (EVP_DigestSignFinal(rsa_sign_ctx, (unsigned char*)sign.data(), &sign_len) <= 0)
    {
        clean();
        return false;
    }

    sign.resize(sign_len);
    clean();
    return true;
}

bool RSACrypto::VerifyByPublicKey(const std::string &src, const std::string& sign)
{
    EVP_PKEY* pub_key = EVP_PKEY_new();
    //EVP_PKEY_assign_RSA(pub_key, m_PublickeyRsa);
    EVP_PKEY_set1_RSA(pub_key, m_PublickeyRsa);
    EVP_MD_CTX* rsa_verify_ctx = EVP_MD_CTX_create();

    auto clean = [pub_key, rsa_verify_ctx] {
        EVP_PKEY_free(pub_key);
        EVP_MD_CTX_destroy(rsa_verify_ctx);
    };

    if (EVP_DigestVerifyInit(rsa_verify_ctx, NULL, EVP_sha1(), NULL, pub_key) <= 0)
    {
        clean();
        return false;
    }

    if (EVP_DigestVerifyUpdate(rsa_verify_ctx, src.data(), src.length()) <= 0)
    {
        clean();
        return false;
    }

    if (EVP_DigestVerifyFinal(rsa_verify_ctx, (unsigned char*)sign.data(), sign.length()) <= 0)
    {
        clean();
        return false;
    }

    clean();
    return true;
}

3、RSA加密与签名示例

...

//要加密的数据源
std::string _message = "hello,world";

//初始化RSA算法
RSACrypto* _RSACrypto = new RSACrypto(_publicKey, _privateKey);
_RSACrypto->InitRsa();
std::string _utf8Message;
std::string _encrypted;
std::string _encryptedBase64;
std::string verify_data;

//加密示例******************************************************************************************************

//公钥加密
//===========================================================================================
Encoder _encoder = Encoder();
//1、 src =  UTF8.encode编码(消息体)
_utf8Message = _encoder.AnsiStringToUTF8String(_message);
//2、 _encrypted =  RSA公钥加密(src)
bool success = _RSACrypto->EncryptByPublicKey(_utf8Message, _encrypted);
//3、 base64Str =   base64.encode编码(_encrypted )
_encryptedBase64 = OpenSSL_Base64Encode(_encrypted.data(), _encrypted.length(), false);
//4、 最终verify_data = UrlEncode编码(base64Str )
verify_data = _encoder.UrlEncode(_encryptedBase64);
//===========================================================================================

//私钥签名
//===========================================================================================
std::string _sign;
//1、 _sign = RSA私钥签名(消息体)
bool success2 = _RSACrypto->SignByPrivateKey(_message, _sign);
//2、 base64Str =   base64编码(_sign )
std::string _signBase64 = OpenSSL_Base64Encode(_sign.data(), _sign.length(), false);
//3、 最终verify_sign = UrlEncode编码(_signBase64 )
auto verify_sign = _encoder.UrlEncode(_signBase64);
//===========================================================================================

//得到 最终的 验证登录状态 url  : finalStr
std::string finalStr = "https://api.mguwp.com/user/verifySignin?verify_data=" + verify_data + "&verify_sign=" + verify_sign + "\n";
cout << "Url:\n" << finalStr << endl;
OutputDebugStringA(LPCSTR(finalStr.data()));

//加密示例******************************************************************************************************

...

4、RSA解密与签名验证示例

...

//回调数据示例,两个参数$param_data,$sign
std::string param_data = "etRzkAmBx5dbb%2BzggET0Z5rJ6FG8Jsh2hFFHD173og3vr%2F7bwPoueJFytrDFT7g13AkXmpz3vr9sEXFymsueG9UMMPELgJQW0gKLkhj96pA%2BNQLAN53H2%2F%2Fi8Z1Rderj%2Fes59T%2FK7bNQnHS%2Fc1LK3rduAkRFmSdm%2FLACNxHIUXpzXPZtyKRxRb7BuxJX%2B31ytaVrrDRseB9NPD9DFK4TuAMJPZqSZqNFWfzZXcyqTg2a9YVYsW6NCIFoBYv5G7%2F%2FQAFcqNQpXjCUb9ISdLHRffq1fvcqYwwdDAU8FkcGXdOQ9wNjixPj3x67GkJLaYAtwzYnCDLGMIl3T%2F%2B%2FM%2B66qw%3D%3D";
std::string sign = "cPniGrltgyofwkEeQBVdADTOuOV5rNGi55VM%2FAwd%2BPRySCMqrU0DrE90gi36Q14O00A7x8DbYl2mD9wOu%2F2ZxsaSoIf5CHHjIEEL0xhuFqAA05zP3qvD9D5m8f3ru%2F3oGgRfCbjAOr%2BHHbhZcwcuBwSlGSbJEuVGd6Ia%2ByqbvWQ%3D";

RSACrypto* _RSACrypto = new RSACrypto(_publicKey, _privateKey);
_RSACrypto->InitRsa();
std::string _urlDecodeStr;
std::string _decryptedBase64;
std::string _decrypted;
Encoder _encoder = Encoder();

//解密示例******************************************************************************************************
//使用开发者私钥解密
//===========================================================================================
//1、_urlDecodeStr = UrlDecode解码(param_data)
_urlDecodeStr = _encoder.UrlDecode(param_data);
//2、_decryptedBase64 = Base64Decode解码(_urlDecodeStr)
_decryptedBase64 = OpenSSL_Base64Decode(const_cast<char*>(_urlDecodeStr.c_str()), _urlDecodeStr.length(), false);
OutputDebugStringW(LPCWSTR(_decryptedBase64.data()));
//3、_decrypted = 使用开发者私钥进行RSA解密
bool success = _RSACrypto->DecryptByPrivateKey(_decryptedBase64, _decrypted);
//4、 得到解密后的 param_data 数据 _decrypted
cout << "param_data = \n" << _decrypted << endl;
//===========================================================================================

//使用公钥验证签名
//===========================================================================================
std::string _sign;
std::string _signDeBase64;
//1、 _sign = UrlDecode解码(sign)
_sign = _encoder.UTF8UrlDecode(sign);
OutputDebugStringA(LPCSTR(_sign.data()));
//2、 _signBase64 = Base64Decode解码(_sign)
_signDeBase64 = OpenSSL_Base64Decode(const_cast<char*>(_sign.c_str()), _sign.length(), false);
OutputDebugStringA(LPCSTR("\n"));
OutputDebugStringW(LPCWSTR(_signDeBase64.data()));
//3、 使用公钥验证签名。 参数1:私钥解密后的数据,参数2:转码后的 sign
bool success2 = _RSACrypto->VerifyByPublicKey(_decrypted, _signDeBase64);
//3、 最终验证结果 = success2
cout << "验证结果:0为失败,1为成功 = \n" << success2 << endl;
//===========================================================================================
//解密示例******************************************************************************************************

...

Thanks

程默的博客

来自上海鼹鼠网络的一位大佬

RSA公钥文件解密密文的原理分析

用OpenSSL 做Base64 编解码(C++)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,558评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,002评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,024评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,144评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,255评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,295评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,068评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,478评论 1 305
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,789评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,965评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,649评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,267评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,982评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,800评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,847评论 2 351

推荐阅读更多精彩内容