C++使用OPENSSL进行RSA加密,java服务端解密

RSA是一种非对称加密。
加密和解密方式:公钥加密-私钥解密,私钥加密-公钥解密

背景

为了网络数据安全,Web端(Java)要求用RSA加密算法传数据,公钥加密私钥解密方式(RSA有公钥加密私钥解密、私钥加密公钥解密),公钥秘钥都由Java生成,格式都是pkcs#8,本文秘钥的长度为2048。
C++后端 进行RSA算法加密,Web端(Java)进行解密。
jdk1.8

示例代码

java示例代码

 /**
     * 生成RSA 公私钥, keySize 可选长度为1025,2048位.
     */
    public static Map<String,String> generateRsaKey(int keySize) {
        Map<String,String> result = new HashMap<>(2);
        try {
            KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");

            // 初始化密钥对生成器,密钥大小为1024 2048位
            keyPairGen.initialize(keySize, new SecureRandom());

            // 生成一个密钥对,保存在keyPair中
            KeyPair keyPair = keyPairGen.generateKeyPair();

            // 得到公钥字符串
            result.put("publicKey", new String(Base64.encodeBase64(keyPair.getPublic().getEncoded())));

            // 得到私钥字符串
            result.put("privateKey", new String(Base64.encodeBase64(keyPair.getPrivate().getEncoded())));

        } catch (GeneralSecurityException e) {
            e.printStackTrace();
        }
        return result;
    }
/**
     * RSA私钥解密
     * @param str  解密字符串
     * @param privateKey  私钥
     * @return 明文
     */
    public static String decrypt(String str, String privateKey) {
        //64位解码加密后的字符串
        byte[] inputByte;
        String outStr = "";
        try {
            inputByte = Base64.decodeBase64(str.getBytes(StandardCharsets.UTF_8));
            //base64编码的私钥
            byte[] decoded = Base64.decodeBase64(privateKey);
            RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance(KEY_ALGORITHM).generatePrivate(new PKCS8EncodedKeySpec(decoded));
            //RSA解密
            Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
            cipher.init(Cipher.DECRYPT_MODE, priKey);
            outStr = new String(cipher.doFinal(inputByte));
        } catch (NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException | InvalidKeySpecException | NoSuchAlgorithmException e) {
            throw new IllegalArgumentException("密码错误");
        }
        return outStr;
    }

    /**
     *  RSA公钥加密
     * @param str 需要加密的字符串
     * @param publicKey 公钥
     * @return 密文
     * @throws Exception 加密过程中的异常信息
     */
    public static String encrypt(String str, String publicKey){
        String outStr;
        try {
            //base64编码的公钥
            byte[] decoded = Base64.decodeBase64(publicKey);
            RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance(KEY_ALGORITHM).generatePublic(new X509EncodedKeySpec(decoded));
            //RSA加密
            Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, pubKey);
            outStr = Base64.encodeBase64String(cipher.doFinal(str.getBytes(StandardCharsets.UTF_8)));
        } catch (Exception e) {
            throw new IllegalArgumentException(e.getMessage());
        }
        return outStr;
    }

C++示例代码,公私钥的存放,可自行决定。

#ifndef RSAENCRYPTION_H
#define RSAENCRYPTION_H

#include 
#include "openssl/rsa.h"
#include "openssl/pem.h"
#include "openssl/err.h"
#include 

#define KEY_LENGTH  2048             // 密钥长度

/**
 * @brief GenerateRSAKey 制造密钥对:私钥和公钥
 * @param out_pub_key 公钥
 * @param out_pri_key 私钥
 */
void GenerateRSAKey(std::string & out_pub_key, std::string & out_pri_key)
{
    char *pri_key = nullptr; // 私钥
    char *pub_key = nullptr; // 公钥

    // 生成密钥对
    RSA *keypair = RSA_generate_key(KEY_LENGTH, RSA_3, nullptr, nullptr);

    BIO *pri = BIO_new(BIO_s_mem());
    BIO *pub = BIO_new(BIO_s_mem());

    // 生成私钥
    PEM_write_bio_RSAPrivateKey(pri, keypair, nullptr, nullptr, 0, nullptr, nullptr);
    // 注意------生成第1种格式的公钥
    //PEM_write_bio_RSAPublicKey(pub, keypair);
    // 注意------生成第2种格式的公钥(此处代码中使用这种)
    PEM_write_bio_RSA_PUBKEY(pub, keypair);

    // 获取长度
    auto pri_len = BIO_pending(pri); // 私钥长度
    auto pub_len = BIO_pending(pub); // 公钥长度

    // 密钥对读取到字符串
    pri_key = (char *)malloc(pri_len + 1);
    pub_key = (char *)malloc(pub_len + 1);

    BIO_read(pri, pri_key, pri_len);
    BIO_read(pub, pub_key, pub_len);

    pri_key[pri_len] = '\0';
    pub_key[pub_len] = '\0';

    out_pub_key = pub_key;
    out_pri_key = pri_key;

    // 释放内存
    RSA_free(keypair);
    BIO_free_all(pub);
    BIO_free_all(pri);

    free(pri_key);
    free(pub_key);
}

/**
 * @brief rsaPubEncrypt 公钥加密
 * @param clear_text 需要进行加密的明文
 * @param pub_key 共钥
 * @return 加密后的数据
 */
std::string rsaPubEncrypt(const std::string &clear_text, const std::string &pub_key)
{
    std::string encrypt_text;
    BIO *keybio = BIO_new_mem_buf((unsigned char *)pub_key.c_str(), -1);
    RSA* rsa = RSA_new();
    // 注意-----第1种格式的公钥
    //rsa = PEM_read_bio_RSAPublicKey(keybio, &rsa, NULL, NULL);
    // 注意-----第2种格式的公钥(这里以第二种格式为例)
    rsa = PEM_read_bio_RSA_PUBKEY(keybio, &rsa, nullptr, nullptr);

    // 获取RSA单次可以处理的数据块的最大长度
    int key_len = RSA_size(rsa);
    int block_len = key_len - 11;    // 因为填充方式为RSA_PKCS1_PADDING, 所以要在key_len基础上减去11

    // 申请内存:存贮加密后的密文数据
    char *sub_text = new char[key_len + 1];
    memset(sub_text, 0, key_len + 1);
    int ret = 0;
    int pos = 0;
    std::string sub_str;
    // 对数据进行分段加密(返回值是加密后数据的长度)
    while (pos < clear_text.length())
    {
        sub_str = clear_text.substr(pos, block_len);
        memset(sub_text, 0, key_len + 1);
        ret = RSA_public_encrypt(sub_str.length(), (const unsigned char*)sub_str.c_str(), (unsigned char*)sub_text, rsa, RSA_PKCS1_PADDING);
        if (ret >= 0) {
            encrypt_text.append(std::string(sub_text, ret));
        }
        pos += block_len;
    }

    // 释放内存
    BIO_free_all(keybio);
    RSA_free(rsa);
    delete[] sub_text;
    return encrypt_text;
}

/*
@brief : 私钥解密
@para  : cipher_text -[i] 加密的密文
         pub_key     -[i] 公钥
@return: 解密后的数据
**/
std::string rsaPriDecrypt(const std::string &cipher_text, const std::string &pri_key)
{
    std::string decrypt_text;
    RSA *rsa = RSA_new();
    BIO *keybio;
    keybio = BIO_new_mem_buf((unsigned char *)pri_key.c_str(), -1);

    rsa = PEM_read_bio_RSAPrivateKey(keybio, &rsa, nullptr, nullptr);
    if (rsa == nullptr) {
        unsigned long err = ERR_get_error(); //获取错误号
        char err_msg[1024] = { 0 };
        ERR_error_string(err, err_msg); // 格式:error:errId:库:函数:原因
        printf("err msg: err:%ld, msg:%s\n", err, err_msg);
        return std::string();
    }

    // 获取RSA单次处理的最大长度
    int key_len = RSA_size(rsa);
    char *sub_text = new char[key_len + 1];
    memset(sub_text, 0, key_len + 1);
    int ret = 0;
    std::string sub_str;
    int pos = 0;
    // 对密文进行分段解密
    while (pos < cipher_text.length()) {
        sub_str = cipher_text.substr(pos, key_len);
        memset(sub_text, 0, key_len + 1);
        ret = RSA_private_decrypt(sub_str.length(), (const unsigned char*)sub_str.c_str(), (unsigned char*)sub_text, rsa, RSA_PKCS1_PADDING);
        if (ret >= 0) {
            decrypt_text.append(std::string(sub_text, ret));
            printf("pos:%d, sub: %s\n", pos, sub_text);
            pos += key_len;
        }
    }
    // 释放内存
    delete[] sub_text;
    BIO_free_all(keybio);
    RSA_free(rsa);
    return decrypt_text;
}

#endif // RSAENCRYPTION_H

java生成的公私钥

公钥为:MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmOLK9RvwvZWirfa4uX/J6fneAeWfvV6ffKhsOdUelr+63UZEVQG9pbkdz0t6Qhn1jbXqH1wGLfEuCmCVvHvRBUKenNUQby7nfX80lodX+yUbXMWl101Tl2fbnuBXwfX0OyMYB9tRwDKxi1VK7/ruV9T0QuNX5ThQxzsCGOaDkizI1LM/1qNbpJd2uhuLTzXQRyWR6nnH4x9y/hDcHKNHg/gjFx0ZWd7et1/Gc3142G0GM2tUkThdqgwF1++VmZQi7b3DTFQ0BdzW8skI6cEpDisQpOiw5dyKrZT4z7a/C4zZ/BNKKsqgovtGJ35a9hSukJiRL2HCJqbVF4EKZMzSSQIDAQAB
私钥为:MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCY4sr1G/C9laKt9ri5f8np+d4B5Z+9Xp98qGw51R6Wv7rdRkRVAb2luR3PS3pCGfWNteofXAYt8S4KYJW8e9EFQp6c1RBvLud9fzSWh1f7JRtcxaXXTVOXZ9ue4FfB9fQ7IxgH21HAMrGLVUrv+u5X1PRC41flOFDHOwIY5oOSLMjUsz/Wo1ukl3a6G4tPNdBHJZHqecfjH3L+ENwco0eD+CMXHRlZ3t63X8ZzfXjYbQYza1SROF2qDAXX75WZlCLtvcNMVDQF3NbyyQjpwSkOKxCk6LDl3IqtlPjPtr8LjNn8E0oqyqCi+0Ynflr2FK6QmJEvYcImptUXgQpkzNJJAgMBAAECggEBAJHytzNkN96UEVenFtMmtbdh69i9v0+FHBVhpudSlz/rylRgNu07mzKwVE/GyvB9XZepmNXVAKUs/vmzGF51iKVP4qDvGSA6k4yPOcGVdJzw4H0gxV+SlwELiXHS0pP72un1Z4Rgj1s+SZUsmDwdvRgl2AReiVLt/tcicq8Yp3OLSTNhL7Rw7Hy1No4yUSIoAQ8bAn0+TTFk1VhquugktWe+2pgt8qy6wUly9xmlAynuKFiNHKHulWdT4HpxA31A2prxeBtK1ywYXGUfgVIfS+uyIfyR4zy5ou+XupAKbyuo4KUq4ijOoD0XW5NbZaDMBGgPKZcuNkTZYFipSOnd63ECgYEAzXUb7nBzFLKAC6ey6udr2utjI3FBStts2IR51DCTSlAtvv9C/UA+A2EroucjUhVUlo4nhiCJ1AYCIJNusKmkiW32//lkQXlNMH+A9ISagPA1LpKOfZ8JcSacQ8ZvmvViM+jdEAFFyQMu4oPDxvPKYFMy8tqNv4CdZ8e/ERDiJ4UCgYEAvn7v9sk/CGDY9XMDqYyAKifL5twLXZGienisTrAB8H23qkj6EWKC1aoggxEFxxku3/Tao+7/Qbfe7oKEl/fNnwpUw1ydY0v/Uc7SQaLsa8QYMNAX1sw6e3pEgi7whOmJS/FwfXVbzf9HpWOUol7h9kOifZtzXdkZzUhBNVz5APUCgYBEMf/gZ/C6yPjZZYNslv3kv0a7x+bNKwH46BfsB2eMPgrBH8cjekRbKNCsFJ5Tq4LVKkASNBCrkIb6OxV9jAFyIE7g7e2KEfI+C8RI5Q7Hh3OPjtO+/J/Mr+0kTeRTgeljo4JkwpdgPi88vlGoYtkkmdkKZOKx0sXsYY+Y4Yq/ZQKBgHMGyTVWfbY8bKWBZqO5kCZkU246Tq6YYuja9wiopPMnpoCvgjh/KIuGKA2ceMWpQjG/c0pr5Tw0n+ubah8tZb70CQzSXsL3v9sRLMqva10DUqwe61YrieDHipilaVyIe/wcdRV8iQvRatzGTZjh/EWsv3BjLn1ru3XGfIGRe1YFAoGAB+q0838j3hOfgUzL2QvWgXXBVck93Miw2MZbnisXH0xWoW51Hf+UHOZF4iGhcnoqQi6ijy1Z4N8NFWJRFy3CN1q/AIyzTtFSsvj08dC5+VwXZo+G5CJmHLfkmvFUJmFKOXo6fhi35TZby6eU8UAijJfRfxIWkgU6MhJrtjuJy28=

简单测试一下, 使用java生成的公私钥,在C++中使用OPENSSL进行RSA加解密测试。

void MainWindow::on_pushButton_clicked()
{
 
    //MIIB
    std::string pubKey = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmOLK9RvwvZWirfa4uX/J6fneAeWfvV6ffKhsOdUelr+63UZEVQG9pbkdz0t6Qhn1jbXqH1wGLfEuCmCVvHvRBUKenNUQby7nfX80lodX+yUbXMWl101Tl2fbnuBXwfX0OyMYB9tRwDKxi1VK7/ruV9T0QuNX5ThQxzsCGOaDkizI1LM/1qNbpJd2uhuLTzXQRyWR6nnH4x9y/hDcHKNHg/gjFx0ZWd7et1/Gc3142G0GM2tUkThdqgwF1++VmZQi7b3DTFQ0BdzW8skI6cEpDisQpOiw5dyKrZT4z7a/C4zZ/BNKKsqgovtGJ35a9hSukJiRL2HCJqbVF4EKZMzSSQIDAQAB\n-----END PUBLIC KEY-----";
	
    std::string priKey ="-----BEGIN RSA PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCY4sr1G/C9laKt9ri5f8np+d4B5Z+9Xp98qGw51R6Wv7rdRkRVAb2luR3PS3pCGfWNteofXAYt8S4KYJW8e9EFQp6c1RBvLud9fzSWh1f7JRtcxaXXTVOXZ9ue4FfB9fQ7IxgH21HAMrGLVUrv+u5X1PRC41flOFDHOwIY5oOSLMjUsz/Wo1ukl3a6G4tPNdBHJZHqecfjH3L+ENwco0eD+CMXHRlZ3t63X8ZzfXjYbQYza1SROF2qDAXX75WZlCLtvcNMVDQF3NbyyQjpwSkOKxCk6LDl3IqtlPjPtr8LjNn8E0oqyqCi+0Ynflr2FK6QmJEvYcImptUXgQpkzNJJAgMBAAECggEBAJHytzNkN96UEVenFtMmtbdh69i9v0+FHBVhpudSlz/rylRgNu07mzKwVE/GyvB9XZepmNXVAKUs/vmzGF51iKVP4qDvGSA6k4yPOcGVdJzw4H0gxV+SlwELiXHS0pP72un1Z4Rgj1s+SZUsmDwdvRgl2AReiVLt/tcicq8Yp3OLSTNhL7Rw7Hy1No4yUSIoAQ8bAn0+TTFk1VhquugktWe+2pgt8qy6wUly9xmlAynuKFiNHKHulWdT4HpxA31A2prxeBtK1ywYXGUfgVIfS+uyIfyR4zy5ou+XupAKbyuo4KUq4ijOoD0XW5NbZaDMBGgPKZcuNkTZYFipSOnd63ECgYEAzXUb7nBzFLKAC6ey6udr2utjI3FBStts2IR51DCTSlAtvv9C/UA+A2EroucjUhVUlo4nhiCJ1AYCIJNusKmkiW32//lkQXlNMH+A9ISagPA1LpKOfZ8JcSacQ8ZvmvViM+jdEAFFyQMu4oPDxvPKYFMy8tqNv4CdZ8e/ERDiJ4UCgYEAvn7v9sk/CGDY9XMDqYyAKifL5twLXZGienisTrAB8H23qkj6EWKC1aoggxEFxxku3/Tao+7/Qbfe7oKEl/fNnwpUw1ydY0v/Uc7SQaLsa8QYMNAX1sw6e3pEgi7whOmJS/FwfXVbzf9HpWOUol7h9kOifZtzXdkZzUhBNVz5APUCgYBEMf/gZ/C6yPjZZYNslv3kv0a7x+bNKwH46BfsB2eMPgrBH8cjekRbKNCsFJ5Tq4LVKkASNBCrkIb6OxV9jAFyIE7g7e2KEfI+C8RI5Q7Hh3OPjtO+/J/Mr+0kTeRTgeljo4JkwpdgPi88vlGoYtkkmdkKZOKx0sXsYY+Y4Yq/ZQKBgHMGyTVWfbY8bKWBZqO5kCZkU246Tq6YYuja9wiopPMnpoCvgjh/KIuGKA2ceMWpQjG/c0pr5Tw0n+ubah8tZb70CQzSXsL3v9sRLMqva10DUqwe61YrieDHipilaVyIe/wcdRV8iQvRatzGTZjh/EWsv3BjLn1ru3XGfIGRe1YFAoGAB+q0838j3hOfgUzL2QvWgXXBVck93Miw2MZbnisXH0xWoW51Hf+UHOZF4iGhcnoqQi6ijy1Z4N8NFWJRFy3CN1q/AIyzTtFSsvj08dC5+VwXZo+G5CJmHLfkmvFUJmFKOXo6fhi35TZby6eU8UAijJfRfxIWkgU6MhJrtjuJy28=\n-----END RSA PRIVATE KEY-----";

    std::string  srcText = "^djsa122..";
   auto dst =  rsaPubEncrypt(srcText, pubKey);
   auto ddst = rsaPriDecrypt(dst ,priKey);
 
//   QByteArray key = QByteArray::fromBase64(pubKey);

     qDebug()<<QByteArray::fromStdString(dst).toBase64() <<ddst.c_str();

//    qDebug()<

}

在这里插入图片描述
C++使用公钥进行加密,拿到java端进行解密测试。测试通过
在这里插入图片描述

OpenSSL加解密简单过程

使用OpenSSL库进行RSA加密和解密的基础过程
加密基础过程

  1. 调用OpenSSL库生成秘钥(非必要步骤,如果已经有秘钥对了,就不需要进行这步了,本文需要校对)
  2. 调用OpenSSL库对明文进行加密
  3. 对加密后密文进行BASE64转码(非必要步骤,一般开发过程中,为了传输or存贮方便,都会对密文进行BASE64编码)
    注意:OpenSSL的RSA加密接口,每次加密数据的最大长度是有限制的,所以对“较大数据”进行加密,需要循环对“较大数据”分段加密

解密基础过程

  1. 对BASE64内容进行BASE64解码
  2. 调用OpenSSL库对密文进行解密
    注意:OpenSSL的RSA解密接口,每次解密数据的最大长度是有限制的,所以对“较大数据”进行解密,需要循环对“较大数据”分段解密

注意事项

私钥和公钥格式

C++ OpenSSL中RSA秘钥(公钥和私钥)是有起止标识的 在BASE64编码中是以“==”结尾,并且每64个字节会有一个换行符(\n),这个与Java是不同的。Java中秘钥是没有起止标识,只有秘钥内容的,也没有换行符(\n)。
起止标识

  • 私钥格式
    起始标识:-----BEGIN RSA PRIVATE KEY-----
    结束标识:-----END RSA PRIVATE KEY-----
  • 公钥格式
    (据我所知)公钥的起止标识有两种。
    第一种
    起始标识:-----BEGIN RSA PUBLIC KEY-----
    结束标识:-----END RSA PUBLIC KEY-----
    第二种
    起始标识:-----BEGIN PUBLIC KEY-----
    结束标识:-----END PUBLIC KEY-----

java给的公钥是base64字符串,但是真正加密的时候用的pkcs#8格式的秘钥。公钥秘钥都由Java生成,java是不带的起止符换行符这些,所以在上述测试中自行补全了起止符,不然会报错崩溃。

所以C++代码中一定要补全起止符。本文只在起止符处添加了换行符测试通过,具体以实际测试结果为准。
如果测试不通过,可在起止标识结束位置有换行,每64字节有换行等进行尝试。

单次加密数据的最大长度

单次加密数据的最大长度(block_len),由RSA秘钥模长RSA_size()和填充模式有关

  1. 填充模式:RSA_PKCS1_PADDING, block_len=RSA_size() - 11

  2. 填充模式:RSA_PKCS1_OAEP_PADDING,block_len=RSA_size() - 41

  3. 填充模式:RSA_NO_PADDING(不填充),block_len=RSA_size()

  调用加密接口时,如果传入的加密数据的长度大于block_len,则加密接口将返回错误 

本文使用的是第一种 RSA_PKCS1_PADDING

公钥的使用

公钥的类型 和 生成公钥RSA对象指针的接口必须是一一对应的,否则将操作失败

 第1种格式的公钥(-----BEGIN RSA PUBLIC KEY----- / -----END RSA PUBLIC KEY-----),对应的接口如下:
      生成公钥:PEM_write_bio_RSAPublicKey()
      生成公钥RSA对象指针:PEM_read_bio_RSAPublicKey()

 第2种格式的公钥(-----BEGIN PUBLIC KEY----- / -----END PUBLIC KEY-----),对应的接口如下:
      生成公钥:PEM_write_bio_RSA_PUBKEY()
      生成公钥RSA对象指针:PEM_read_bio_RSA_PUBKEY()

本文使用的是第2种格式,java解密测试通过。

你可能感兴趣的:(java,java,c++,开发语言)