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 <QObject>
#include "openssl/rsa.h"
#include "openssl/pem.h"
#include "openssl/err.h"
#include <string>
#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()<<pubKey.c_str()<<QByteArray::fromStdString(dst).toBase64()<<ddst.c_str();
}
C++使用公钥进行加密,拿到java端进行解密测试。测试通过
OpenSSL加解密简单过程
使用OpenSSL库进行RSA加密和解密的基础过程
加密基础过程
- 调用OpenSSL库生成秘钥(非必要步骤,如果已经有秘钥对了,就不需要进行这步了,本文需要校对)
- 调用OpenSSL库对明文进行加密
- 对加密后密文进行BASE64转码(非必要步骤,一般开发过程中,为了传输or存贮方便,都会对密文进行BASE64编码)
注意:OpenSSL的RSA加密接口,每次加密数据的最大长度是有限制的,所以对“较大数据”进行加密,需要循环对“较大数据”分段加密
解密基础过程
- 对BASE64内容进行BASE64解码
- 调用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解密测试通过。
更多推荐
所有评论(0)