0%

Node-RSA, Crypto.js和Crypto++服务端与客户端协同加密

  1. 环境说明
  2. Node-RSA的安装与使用
    1. 生成公钥私钥.pem文件
    2. 读取.pem文件,RSA加密解密
  3. Crypto.js的安装与使用
    1. 生成随机的密钥
    2. AES加密与解密
  4. Crypto++的编译与使用
    1. 基本编译过程
    2. 创建C++程序引入Crypto++进行测试
    3. 使用动态链接库的方式引入Crypto++
    4. MD还是MT?生成不同运行库的方法
    5. 使用Crypto++分别完成AES和RSA加密解密
      1. 生成RSA公钥私钥对,进行加密解密
      2. AES加密与解密
  5. C++与JS之间互通的加密和解密函数
    1. C++实现的AES加密解密函数
      1. 1. 16进制字符串 转 字节数组
      2. 2. 字节数组 转 16进制字符串
      3. 3. 16进制编码:字符串 转为 16进制字符串
      4. 4. 16进制解码:16进制字符串 转为 字符串
      5. 5. AES 加密
      6. 6. AES 解密
      7. 调用结果
    2. JS实现的AES加密解密函数
      1. 1. 16进制随机字符串生成
      2. 2. AES 加密
      3. 3. AES 解密
      4. 调用结果
    3. C++实现的RSA加密解密函数
      1. 1. 生成RSA密钥对
      2. 2. 保存和读取RSA密钥对
      3. 3. RSA加密解密
      4. 调用示例
    4. JS实现的RSA加密解密函数
  6. C++与JS的配合使用的AES+RSA双重加密
    1. 加密说明示例
    2. 纯C++实现
    3. 纯JS实现
    4. C++加密,JS解密
    5. JS加密,C++加密

环境说明

客户端:C++

服务端:Node.js

需要在客户端与服务端进行加密通讯,首先考虑使用RSA算法进行加密,该算法要求数据长度不能超过密钥长度,因此通常用来加密一些较短的数据,考虑使用AES对数据进行加密,使用RSA对AES密钥进行加密,以此充分利用算法的特性,实现加密过程。

Node-RSA的安装与使用

https://www.npmjs.com/package/node-rsa

安装

1
npm i node-rsa -S

生成公钥私钥.pem文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const fs = require('fs');
const NodeRSA = require('node-rsa');
const path = require('path');

try {
// 生成RSA对
const key = new NodeRSA({ b: 2048 });

// 输出为pem文件
const outDir = path.join(__dirname, 'rsa');
if (!fs.existsSync(outDir)) {
fs.mkdirSync(outDir);
}

fs.writeFileSync(path.join(outDir, 'pub.pem'), key.exportKey('pkcs8-public-pem'));
fs.writeFileSync(path.join(outDir, 'pri.pem'), key.exportKey('pkcs8-private-pem'));
} catch (e) {
console.error(e);
}

代码首先生成了一个长度为2048位的密钥,接着在指定文件夹下分别输出公钥和私钥文件,输出的格式为pkcs8,pem表示加密格式为base64。

读取.pem文件,RSA加密解密

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const fs = require('fs');
const NodeRSA = require('node-rsa');
const path = require('path');

try {
const keyDir = path.join(__dirname, 'rsa');
const pubKey = new NodeRSA(fs.readFileSync(path.join(keyDir, 'pub.pem')), 'pkcs8-public-pem', { encryptionScheme: 'pkcs1' });
const priKey = new NodeRSA(fs.readFileSync(path.join(keyDir, 'pri.pem')), 'pkcs8-private-pem', { encryptionScheme: 'pkcs1' });

const msg = JSON.stringify({
text: 'Hello World',
time: Date.now()
});

const encMsg = pubKey.encrypt(msg, 'base64', 'utf8');
console.log(encMsg);

const decMsg = priKey.decrypt(encMsg, 'utf8');
console.log(decMsg);
} catch (e) {
console.error(e);
}

Crypto.js的安装与使用

1
npm i crypto-js -S

生成随机的密钥

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 生成随机的16进制密钥
* @param {Number} len 密钥长度
* @returns 密钥字符串
*/
const randomKey = (len) => {
let str = '';

for (let i = 0; i < len; i++) {
str += Math.floor(Math.random() * 16).toString(16);
}

return str;
}

AES加密与解密

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
try {
const msg = JSON.stringify({
text: 'Hello World!',
time: Date.now()
});
const key = randomKey(8);
console.log('key: %s', key);

const enc = CryptoJS.AES.encrypt(msg, key).toString();
console.log(enc);

const dec = CryptoJS.AES.decrypt(enc, key).toString(CryptoJS.enc.Utf8);
console.log(dec);
} catch (e) {
console.error(e)
}

Crypto++的编译与使用

Crypto++是基于C++实现的一个加密算法库,我们用它来实现AES和RSA加密解密流程。

基本编译过程

下载地址:Cryptopp.com

编译器:VS2019

下载后解压,解压后先把所有的.h.cpp文件拷贝到一个目录下,放在include/cryptopp下。

使用VS打开目录下的cryptest.sln文件,里面包含了4个项目,其中我们需要用到的有两个:cryptdllcryptlib,分别对应动态库(lib+dll)和静态库(lib)。在“生成-批生成”中分别选择对应项目的32位和64位的Debug和Release版本,总共四个,全都勾上,点击【生成】,全程大约花费3-5min,时间和CPU性能有关系。

image-20210602214757182

生成的结果保存在.sln文件同一目录下的Win32x64文件夹下,需要把里面我们需要用到的东西取出放到特定的文件夹下。创建两个文件夹shared和static,在每个文件夹下创建四个目录,分别对应32位和64位下的Debug和Release库文件,对应的拷贝关系如下所示:

  • Win32/Output/Debug/* -> static/debug-v142
  • Win32/Output/Release/* -> static/release-v142
  • x64/Output/Debug/* -> static/debug-x64-v142
  • x64/Output/Release/* -> static/release-x64-v142
  • Win32/DLL_Output/Debug/* -> shared/debug-v142
  • Win32/DLL_Output/Release/* -> shared/release-v142
  • x64/DLL_Output/Debug/* -> shared/debug-x64-v142
  • x64/DLL_Output/Release/* -> shared/release-x64-v142

使用shared和static区分动态库和静态库,shared存放的一般包括.dll和.lib文件,static存放的只有一个.lib文件。拷贝的内容为对应文件夹下的.dll和.lib文件,其他的文件如.pdb, .ilk, .exp可选复制。这边的v142标识了编译器的版本,对应MSVC2019。

这里有一段用于复制的powershell脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
mkdir out/md/static/debug-v142;
copy Win32/Output/Debug/* out/md/static/debug-v142;
mkdir out/md/static/release-v142;
copy Win32/Output/Release/* out/md/static/release-v142;
mkdir out/md/static/debug-x64-v142;
copy x64/Output/Debug/* out/md/static/debug-x64-v142;
mkdir out/md/static/release-x64-v142;
copy x64/Output/Release/* out/md/static/release-x64-v142;
mkdir out/md/shared/debug-v142;
copy Win32/DLL_Output/Debug/* out/md/shared/debug-v142;
mkdir out/md/shared/release-v142;
copy Win32/DLL_Output/Release/* out/md/shared/release-v142;
mkdir out/md/shared/debug-x64-v142;
copy x64/DLL_Output/Debug/* out/md/shared/debug-x64-v142;
mkdir out/md/shared/release-x64-v142;
copy x64/DLL_Output/Release/* out/md/shared/release-x64-v142;
pause;

创建C++程序引入Crypto++进行测试

在VS2019中新建工程,在工程中引入Crypto++。在“项目-属性-C/C++-附加包含目录”输入include目录的绝对路径:

image-20210602222126273

接着根据配置(Debug/Release)和平台(x64/Win32),在“链接器-输入-附加依赖项”引入对应文件夹中的.lib文件,这里引入的是静态库文件(static),这也是这个库的作者推荐的使用方式,不需要额外添加.dll文件到.exe文件目录下或环境变量中,生成成功后可以直接运行。

image-20210603200243168

由于Crypto++的工程文件默认生成MT的运行库,因此还需要额外修改“C/C++-代码生成-运行库”,Debug版本选择/MTD,Release版本选择/MT。(如果不想修改这个设置,见后续小节)

image-20210602223220980

两个地方引入完成后,创建一个.cpp,在里面写入如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <iostream>
#include <cryptopp/aes.h>

typedef CryptoPP::byte byte;

int main(int argc, char* argv[])
{
CryptoPP::AESEncryption aesEncryptor;

byte aesKey[CryptoPP::AES::DEFAULT_KEYLENGTH];
byte inBlock[CryptoPP::AES::BLOCKSIZE] = "123456789";
byte outBlock[CryptoPP::AES::BLOCKSIZE];

byte xorBlock[CryptoPP::AES::BLOCKSIZE];
memset(xorBlock, 0, CryptoPP::AES::BLOCKSIZE);

aesEncryptor.SetKey(aesKey, CryptoPP::AES::DEFAULT_KEYLENGTH);
aesEncryptor.ProcessAndXorBlock(inBlock, xorBlock, outBlock);

for (int i = 0; i < 16; i++)
{
std::cout << std::hex << (int)outBlock[i] << " ";
}
std::cout << std::endl;

return 0;
}

如果配置正确,执行代码后将在命令行中输出如下信息:

image-20210602222909560

使用动态链接库的方式引入Crypto++

上一小节记录了作者推荐的静态库的引入方式,如果有特别的需求需要用到.lib+.dll的动态库组合,需要进行额外的设置。

Crypto++的代码仓库中有这么一段说明:

To use the Crypto++ DLL in your application, #include “dll.h” before including
any other Crypto++ header files, and place the DLL in the same directory as
your .exe file. dll.h includes the line #pragma comment(lib, “cryptopp”)
so you don’t have to explicitly list the import library in your project
settings. To use a static library form of Crypto++, make the “cryptlib”
project a dependency of your application project, or specify it as
an additional library to link with in your project settings.
In either case you should check the compiler options to
make sure that the library and your application are using the same C++
run-time libraries and calling conventions.

因此,在工程配置中,将引入的静态库改为动态库:

image-20210603201053828

接着在代码的头文件处引入dll.h

1
#include <cryptopp/dll.h>

最后把对应的.dll文件cryptopp.dll拷贝到.exe目录下,即可运行。如果在VS中运行Debug版本抛出异常,按下F5继续执行即可,或者直接Ctrl+F5执行不调试也可以正确输出,发生的异常属于Crypto++内部的小BUG,无伤大雅。

使用动态库生成的.exe文件会小很多,且频繁运行时可以充分利用系统的缓存机制,运行速度快;使用静态库生成的.exe文件较大,但是只有一个文件,较为简洁,便于传递。

image-20210603202427819

MD还是MT?生成不同运行库的方法

MD意为:multithread and DLL-specific version,多线程DLL版本。编译器加载的是动态运行的库,依赖.lib和.dll文件。

MT意为:multithread, static version, 多线程静态版本。编译器加载的是静态运行的库,仅依赖.lib文件。

若生成工程时使用的是/MT生成的,它所调用的运行时库为:LIBCMT.lib,编译器将其安置到.obj文件中,让链接器使用LIBCMT.lib处理外部符号,并完成相关的运算操作(一个文件完成);

若生成工程时使用的是/MD生成的,调用的运行库有两个:MSVCRT.libMSVCRxxx.dll,这里xxx对应MSVC版本,如VS2019使用的MSVC2019,调用的.dll文件为MSVCR142.dll。MSVC2017对应141,MSVC2015对应140,MSVC2013对应120,MSVC2010对应100。编译器将MSVCRT.lib安置到.obj文件中,与MSVCRxxx.dll建立静态链接,由.lib完成外部符号处理,相关的运算交给.dll文件(两个文件完成);

因此,若一个生成运行库的工程使用的是/MT方式生成,调用的运行库是LIBCMT.lib,在另一个工程中引用这个库时,若这个工程使用的是/MD方式生成,调用的运行库是MSVCRT.libMSVCRxxx.dll,当调用某个系统库函数时,若两个运行库中均有这个函数,就会出现重定义的错误。因此当在另一个工程中使用由其他工程生成的库文件时,在“属性-C/C++-代码生成”中的运行库设置一定需要一至。

在Crypto++的.sln文件中,四个工程的默认运行库都是/MT,因此生成库文件要在别的工程中使用,该工程需要将运行库也一同设置为/MT。此时的矛盾点在于,使用VS创建的工程默认都是/MD,有一些现有的工程都是使用/MD的运行库,因此要进行嵌入的话,必须适配。

image-20210603212737537

修改不同运行库的方法很简单,右键两个工程cryptdllcryptlib,在“属性-C/C++-代码生成-运行库”位置全都改成/MD相关,Debug就改成/MDd/Release改成/MD,改完之后,按照上述小节的基本编译过程进行编译和结果输出,即可保存使用。

使用Crypto++分别完成AES和RSA加密解密

RSA:

RSA Cryptography - Encryption Scheme (OAEP using SHA)

AES:

Advanced Encryption Standard - Crypto++ Wiki

在C++中实现AES和RSA加密解密借助Crypto++来实现,这里引用官网的两个示例来演示如何通过C++代码实现加密解密流程。

生成RSA公钥私钥对,进行加密解密

Crypto++官网示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
using namespace CryptoPP;
using namespace std;

////////////////////////////////////////////////
// Generate keys
AutoSeededRandomPool rng;

InvertibleRSAFunction params;
params.GenerateRandomWithKeySize(rng, 3072);

RSA::PrivateKey privateKey(params);
RSA::PublicKey publicKey(params);

string plain = "RSA Encryption", cipher, recovered;

////////////////////////////////////////////////
// Encryption
RSAES_OAEP_SHA_Encryptor e(publicKey);

StringSource ss1(plain, true,
new PK_EncryptorFilter(rng, e,
new StringSink(cipher)
) // PK_EncryptorFilter
); // StringSource

////////////////////////////////////////////////
// Decryption
RSAES_OAEP_SHA_Decryptor d(privateKey);

StringSource ss2(cipher, true,
new PK_DecryptorFilter(rng, d,
new StringSink(recovered)
) // PK_DecryptorFilter
); // StringSource

cout << "Recovered plain text" << endl;

AES加密与解密

Crypto++官网示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
#include "cryptlib.h"
#include "rijndael.h"
#include "modes.h"
#include "files.h"
#include "osrng.h"
#include "hex.h"

#include <iostream>
#include <string>

int main(int argc, char* argv[])
{
using namespace CryptoPP;

AutoSeededRandomPool prng;
HexEncoder encoder(new FileSink(std::cout));

SecByteBlock key(AES::DEFAULT_KEYLENGTH);
SecByteBlock iv(AES::BLOCKSIZE);

prng.GenerateBlock(key, key.size());
prng.GenerateBlock(iv, iv.size());

std::string plain = "CBC Mode Test";
std::string cipher, recovered;

std::cout << "plain text: " << plain << std::endl;

/*********************************\
\*********************************/

try
{
CBC_Mode< AES >::Encryption e;
e.SetKeyWithIV(key, key.size(), iv);

StringSource s(plain, true,
new StreamTransformationFilter(e,
new StringSink(cipher)
) // StreamTransformationFilter
); // StringSource
}
catch(const Exception& e)
{
std::cerr << e.what() << std::endl;
exit(1);
}

/*********************************\
\*********************************/

std::cout << "key: ";
encoder.Put(key, key.size());
encoder.MessageEnd();
std::cout << std::endl;

std::cout << "iv: ";
encoder.Put(iv, iv.size());
encoder.MessageEnd();
std::cout << std::endl;

std::cout << "cipher text: ";
encoder.Put((const byte*)&cipher[0], cipher.size());
encoder.MessageEnd();
std::cout << std::endl;

/*********************************\
\*********************************/

try
{
CBC_Mode< AES >::Decryption d;
d.SetKeyWithIV(key, key.size(), iv);

StringSource s(cipher, true,
new StreamTransformationFilter(d,
new StringSink(recovered)
) // StreamTransformationFilter
); // StringSource

std::cout << "recovered text: " << recovered << std::endl;
}
catch(const Exception& e)
{
std::cerr << e.what() << std::endl;
exit(1);
}

return 0;
}

C++与JS之间互通的加密和解密函数

为了在C++和JS之间进行数据传输通信,需要设定一个统一的数据交互格式。在这里的设计中,AES密钥表示为16进制字符串,通过AES加密后的内容仍为16进制字符串;RSA公钥私钥对通过.pem文件存储,密钥内容由Base64编码(为了迎合CryptoJS),通过RSA公钥加密后的数据为Base64编码后的字符串。

C++实现的AES加密解密函数

在Crypto++中,喂给加密解密函数的密钥的数据类型是字节数组,而生成的用于传输的密文、密钥、偏移量是16进制的字符串,因此需要首先实现字节数组和16进制字符串之间的转换。

头文件

1
2
3
4
5
6
7
#include <iostream>
#include <cryptopp/rijndael.h>
#include <cryptopp/modes.h>
#include <cryptopp/osrng.h>
#include <cryptopp/hex.h>
#include <cryptopp/files.h>
#include <string>

1. 16进制字符串 转 字节数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/// <summary>
/// 16进制字符串转为字节数组
/// </summary>
/// <param name="hexStr">16进制字符串</param>
/// <returns>字节数组</returns>
CryptoPP::SecByteBlock HexString2ByteBlock(std::string hexStr)
{
using namespace CryptoPP;

SecByteBlock byteBlock(0);
try
{
std::string byteStr;
StringSource s(hexStr, true, new HexDecoder(new StringSink(byteStr)));

byteBlock.Assign((const byte*)&byteStr[0], byteStr.size());
}
catch (const std::exception& e)
{
std::cerr << e.what() << std::endl;
}
return byteBlock;
}

2. 字节数组 转 16进制字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/// <summary>
/// 字节数组转16进制字符串
/// </summary>
/// <param name="byteBlock">字节数组</param>
/// <returns>16进制字符串</returns>
std::string ByteBlock2HexString(CryptoPP::SecByteBlock byteBlock)
{
using namespace CryptoPP;

std::string str;
try
{
StringSource s(byteBlock, byteBlock.size(), true, new HexEncoder(new StringSink(str)));
}
catch (const std::exception& e)
{
std::cerr << e.what() << std::endl;
}
return str;
}

3. 16进制编码:字符串 转为 16进制字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/// <summary>
/// 将由char组成的字符串转为16进制字符串
/// </summary>
/// <param name="cStr">字符串, 每一位是一个char(-128~127)</param>
/// <returns>16进制字符串</returns>
std::string String2HexString(std::string cStr)
{
using namespace CryptoPP;

std::string str;
try
{
StringSource s((const byte*)&cStr[0], cStr.size(), true, new HexEncoder(new StringSink(str)));
}
catch (const std::exception& e)
{
std::cerr << e.what() << std::endl;
}
return str;
}

4. 16进制解码:16进制字符串 转为 字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/// <summary>
/// 16进制字符串转为由char组成的字符串
/// </summary>
/// <param name="hexStr">16进制字符串</param>
/// <returns>由char组成的字符串</returns>
std::string HexString2String(std::string hexStr)
{
using namespace CryptoPP;

std::string str;
try
{
StringSource s(hexStr, true, new HexDecoder(new StringSink(str)));
}
catch (const std::exception& e)
{
std::cerr << e.what() << std::endl;
}
return str;
}

5. AES 加密

加密时,首先将16进制字符串的密钥key和偏移iv转为字节数组,再调用Crypto++相关的函数进行加密。若密钥和偏移字符串为空,则根据指定的长度生成随机的密钥和偏移字符串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
/// <summary>
/// AES加密
/// </summary>
/// <param name="str">待加密的字符串</param>
/// <param name="encStr">指针: 加密后的字符串, 为16进制数组成的字符串</param>
/// <param name="key">指针: 密钥, 若为空指针或长度为0, 则根据keyLen长度生成</param>
/// <param name="iv">指针: 偏移量, 若为空指针或长度为0, 则根据ivLen长度生成</param>
/// <param name="keyLen">密钥长度, 默认16, 若密钥指针不为空, 该参数失效</param>
/// <param name="ivLen">偏移量长度, 默认为16, 若偏移量指针不为空, 该参数失效</param>
/// <returns>是否加密成功</returns>
bool AesEncrypt(std::string str, std::string* hexEncStr = nullptr, std::string* hexKeyStr = nullptr, std::string* hexIvStr = nullptr, int keyLen = 16, int ivLen = 16)
{
if (hexEncStr == nullptr)
{
std::cerr << "未为加密字符串分配空间" << std::endl;
return false;
}

if (str.empty())
{
std::cerr << "待加密的字符串为空" << std::endl;
return false;
}

// 如果key或iv为空指针, 说明不需要输出, 需要在函数执行完成后删除变量
bool isDelKey, isDelIv;
isDelKey = isDelIv = false;
if (hexKeyStr == nullptr)
{
hexKeyStr = new std::string();
isDelKey = true;
}

if (hexIvStr == nullptr)
{
hexIvStr = new std::string();
isDelIv = true;
}

bool isSucceed = false;
try
{
using namespace CryptoPP;

/*
* 获取密钥和偏移量的字节数组, 若key或iv长度为0, 生成随机字符串
*/
SecByteBlock byteKey;
SecByteBlock byteIv;
{
if (hexKeyStr->size() != 0)
{
keyLen = int(hexKeyStr->size() / 2);
}
if (keyLen != 8 && keyLen != 16 && keyLen != 32)
{
throw std::exception(("密钥长度错误: " + std::to_string(keyLen)).c_str());
}
if (hexKeyStr->size() == 0)
{
// 生成随机的key
byteKey.New(keyLen);
AutoSeededRandomPool prng;
prng.GenerateBlock(byteKey, byteKey.size());

// 转为字符串
*hexKeyStr = ByteBlock2HexString(byteKey);
}
else
{
byteKey = HexString2ByteBlock(*hexKeyStr);
}

if (hexIvStr->size() != 0)
{
ivLen = int(hexIvStr->size() / 2);
}
if (ivLen != 8 && ivLen != 16 && ivLen != 32)
{
throw std::exception(("偏移量长度错误: " + std::to_string(ivLen)).c_str());
}
if (hexIvStr->size() == 0)
{
// 生成随机的iv
byteIv.New(ivLen);
AutoSeededRandomPool prng;
prng.GenerateBlock(byteIv, byteIv.size());

// 转为字符串
*hexIvStr = ByteBlock2HexString(byteIv);
}
else
{
byteIv = HexString2ByteBlock(*hexIvStr);
}
}

// 加密
std::string encStrByte;
{
CBC_Mode<AES>::Encryption e;
e.SetKeyWithIV(byteKey, byteKey.size(), byteIv, byteIv.size());
StringSource s(str, true, new StreamTransformationFilter(e, new StringSink(encStrByte)));
}
*hexEncStr = String2HexString(encStrByte);

isSucceed = true;
}
catch (const std::exception& e)
{
std::cerr << e.what() << std::endl;
isSucceed = false;
}

if (isDelKey) delete hexKeyStr;
if (isDelIv) delete hexIvStr;

return isSucceed;
}

6. AES 解密

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/// <summary>
/// AES 解密
/// </summary>
/// <param name="hexEncStr">16进制的加密字符串</param>
/// <param name="hexKeyStr">16进制的密钥字符串</param>
/// <param name="hexIvStr">16进制的偏移量字符串</param>
/// <param name="decStr">解密后的字符串</param>
/// <returns>是否解密成功</returns>
bool AesDecrypt(std::string hexEncStr, std::string hexKeyStr, std::string hexIvStr, std::string* decStr = nullptr)
{
if (decStr == nullptr)
{
std::cerr << "输出指针为空" << std::endl;
return false;
}

bool isSucceed = false;

using namespace CryptoPP;
try
{
// 将16进制转为字节数组
SecByteBlock key, iv;
key = HexString2ByteBlock(hexKeyStr);
iv = HexString2ByteBlock(hexIvStr);
std::string cStr = HexString2String(hexEncStr);

// 解密
CBC_Mode<AES>::Decryption d;
d.SetKeyWithIV(key, key.size(), iv, iv.size());
StringSource s(cStr, true,
new StreamTransformationFilter(d,
new StringSink(*decStr)
)
);

isSucceed = true;
}
catch (const std::exception& e)
{
std::cerr << e.what() << std::endl;
isSucceed = false;
}

return isSucceed;
}

调用结果

调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
std::string str, key, iv, encStr, decStr;

// 加密
{
str = "AES CBC Encrypt, Test, Hello World!!!!!";
if (AesEncrypt(str, &encStr, &key, &iv))
{
std::cout << "key: " << key << std::endl;
std::cout << "iv: " << iv << std::endl;
std::cout << "encStr: " << encStr << std::endl;
}
else
{
std::cerr << "加密失败" << std::endl;
}
}

// 解密
{
if (AesDecrypt(encStr, key, iv, &decStr))
{
std::cout << "decStr: " << decStr << std::endl;
}
else
{
std::cerr << "解密失败" << std::endl;
}
}

输出结果:

1
2
3
4
key: D486B7316C9F9F69078D031DB1499E58
iv: 6A8C943C59F23D9ECE3C09934F5464DA
encStr: 4CA2770DE3FB68D7E17F46B87A1E2689794835A63B1D0A7E2AC10139F018093F13079967ECECBC9046A522D3E8A29B7D
decStr: AES CBC Encrypt, Test, Hello World!!!!!

JS实现的AES加密解密函数

通过调用CryptoJS实现AES加密解密,需要注意的是输出的参数类型,用于与C++通信的数据格式为16进制字符串,因此需要针对16进制的密文、密钥、偏移量做解析,进而完成加密解密。

这里的密钥长度和偏移量长度指的是将16进制字符串转为字节数组后的长度,存在一个2倍的关系,即:16进制字符串的长度除以2得到由字节数组构成的密钥的长度。

1. 16进制随机字符串生成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 随机生成指定长度的16进制字符串
* @param {Number} len 字符串长度
* @returns 16进制字符串
*/
const randomHexStr = (len) => {
let str = '';

for (let i = 0; i < len; i++) {
str += Math.floor(Math.random() * 16).toString(16);
}

return str;
}

2. AES 加密

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/**
* AES 加密
* @param {String} str 待加密的字符串
* @param {String} hexKeyStr 16进制的密钥字符串
* @param {String} hexIvStr 16进制的偏移量字符串
* @param {Number} keyLen 密钥字符串长度, 当16进制密钥字符串为0时生效
* @param {Number} ivLen 偏移量字符串长度, 当16进制偏移字符串为0时生效
* @returns {Object} 16进制的密文/密钥/偏移量 {hexEncStr, hexKeyStr, hexIvStr}
*/
const AesEncrypt = (str = String, hexKeyStr = '', hexIvStr = '', keyLen = 16, ivLen = 16) => {
// 解析密钥和偏移
var key, iv;

// 密钥
if (hexKeyStr.length !== 0) {
keyLen = hexKeyStr.length / 2;
}
if (keyLen !== 8 && keyLen !== 16 && keyLen !== 32) {
throw '密钥长度不符合要求: ' + keyLen;
}
if (hexKeyStr.length === 0) {
hexKeyStr = randomHexStr(keyLen * 2);
key = CryptoJS.enc.Hex.parse(hexKeyStr);
} else {
key = CryptoJS.enc.Hex.parse(hexKeyStr);
}

// 偏移量
if (hexIvStr.length !== 0) {
ivLen = hexIvStr.length / 2;
}
if (ivLen !== 8 && ivLen !== 16 && ivLen !== 32) {
throw '偏移量长度不符合要求: ' + ivLen;
}
if (hexIvStr.length === 0) {
hexIvStr = randomHexStr(ivLen * 2);
iv = CryptoJS.enc.Hex.parse(hexIvStr);
} else {
iv = CryptoJS.enc.Hex.parse(hexIvStr);
}

// 加密
const enc = CryptoJS.AES.encrypt(str, key, {
iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
return {
hexEncStr: enc.ciphertext.toString(),
hexKeyStr,
hexIvStr
}
}

3. AES 解密

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* Aes 解密
* @param {String} encHexStr 16进制字符串: 密文
* @param {String} keyHexStr 16进制字符串: 密钥
* @param {String} ivHexStr 16进制字符串: 偏移量
* @returns 解密后的字符串
*/
const AesDecrypt = (encHexStr = String, keyHexStr = String, ivHexStr = String) => {
// 解析密钥和偏移
const key = CryptoJS.enc.Hex.parse(keyHexStr);
const iv = CryptoJS.enc.Hex.parse(ivHexStr);

// 解密
const dec = CryptoJS.AES.decrypt(CryptoJS.enc.Base64.stringify(CryptoJS.enc.Hex.parse(encHexStr)), key, {
iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});

return dec.toString(CryptoJS.enc.Utf8);
}

调用结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
try {
const str = 'AES CBC Encrtypt, Test, Hello World!!!!!';
const aesResults = AesEncrypt(str);
const encStr = aesResults.hexEncStr;
const key = aesResults.hexKeyStr;
const iv = aesResults.hexIvStr;

console.log('encStr: %s', encStr);
console.log('key: %s', key);
console.log('iv: %s', iv);

const decStr = AesDecrypt(encStr, key, iv);
console.log('decStr: %s', decStr);
} catch (e) {
console.error(e)
}

输出

1
2
3
4
encStr: 3aed669123c8be163a8ed89602d86e49683828fd860aa73c21aeae6905695c1bba2bfd7205531768c2d66b4f3e9e527f
key: fffbd3050f001baf4c0e21097ff704ad
iv: 6e4fa052041e7faa1e606a6ef56ab34b
decStr: AES CBC Encrtypt, Test, Hello World!!!!!

C++实现的RSA加密解密函数

相应的RSA函数、Base64编码解码函数需要引入以下头文件:

1
2
#include <cryptopp/rsa.h>
#include <cryptopp/base64.h>

1. 生成RSA密钥对

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/// <summary>
/// 生成RSA密钥对
/// </summary>
bool GenerateRSAKey(std::string* pubKeyStr, std::string* prvKeyStr, unsigned int keyLen = 2048)
{
using namespace CryptoPP;
try
{
if (!pubKeyStr || !prvKeyStr)
{
throw "Null Pointer";
}

// 使用随机数初始化不可逆函数
AutoSeededRandomPool rng;
InvertibleRSAFunction params;
params.GenerateRandomWithKeySize(rng, keyLen);

// 生成随机密钥对
RSA::PublicKey pubKey(params);
RSA::PrivateKey prvKey(params);

// 进行Base64编码
Base64Encoder pubeEncoder(new StringSink(*pubKeyStr));
pubKey.DEREncode(pubeEncoder);
pubeEncoder.MessageEnd();

Base64Encoder prveEncoder(new StringSink(*prvKeyStr));
prvKey.DEREncode(prveEncoder);
prveEncoder.MessageEnd();

return true;
}
catch (const std::exception& e)
{
std::cerr << e.what() << std::endl;
return false;
}
}

调用:

1
2
3
4
std::string pubKey, prvKey;
GenerateRSAKey(&pubKey, &prvKey, 2048);
std::cout << "pubKey: \n" << pubKey << std::endl;
std::cout << "prvKey: \n" << prvKey << std::endl;

输出是一串带有换行符\n的字符串:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
pubKey:
MIIBIDANBgkqhkiG9w0BAQEFAAOCAQ0AMIIBCAKCAQEAqsx9tnvD/801PJoUX/DV3Vk7cXL7
F3WuSeSGkYyw69dyLJvjb1PYwBOAt3+oKrGsaqclFRhq0iqQaHWRL/TVdBM74DWM0wLAu1zU
Mfwql1qpqN2qoFfYS0ETRnsECqiVy1FDDhHvI5vhwJbUCfWDHIa1CPZKCDvbIc3pGYr/k3Yq
d4Hh8bdFEpzI47P/aIXJm0sF1/WugCqAxvC2qO04XoWxMPfbcz/OtNbYHAfBange+HV6RVLh
7WMYv7hYO5WLQnfldgkNEAOsX9rYfO9G3EBSDeAyQipkePDPuX6kDvhT/kC23tbFKMVoKTIb
2RWXn8YmYjavkXHPbWXICZFWewIBEQ==

prvKey:
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCqzH22e8P/zTU8mhRf8NXd
WTtxcvsXda5J5IaRjLDr13Ism+NvU9jAE4C3f6gqsaxqpyUVGGrSKpBodZEv9NV0EzvgNYzT
AsC7XNQx/CqXWqmo3aqgV9hLQRNGewQKqJXLUUMOEe8jm+HAltQJ9YMchrUI9koIO9shzekZ
iv+Tdip3geHxt0USnMjjs/9ohcmbSwXX9a6AKoDG8Lao7ThehbEw99tzP8601tgcB8FqeB74
dXpFUuHtYxi/uFg7lYtCd+V2CQ0QA6xf2th870bcQFIN4DJCKmR48M+5fqQO+FP+QLbe1sUo
xWgpMhvZFZefxiZiNq+Rcc9tZcgJkVZ7AgERAoIBAEZUM8OcX8Ou2a+KvRhyOfG7VLY+Z2QD
R8QSzf+yZvezEOUxIWoTd14mJfE0kIoM7KRi8SbN0aHVSoVdliLOOcZiRdTKwYQQMT4XKjKz
IJis3HK1oJxgaB78rZV98pr6H4/0SMmO6f+aiiIf/PUKvYQ3d7hlaccntJVy54L8/9NOGVEX
X/kTCA5yJ+cbrbATL4Eq5MQPsRDC6vsc+IkUY8ORf01EIisxScZP+JJVHXY1FnzgqspblCU+
9Z+NPZlHUd0/nD5/cCHT44UYzoUMh6suvqMRuHJHQ7I5GlcyCP8UdGG5hT/+WfJHPPNfpDJ7
n7alL9nbMI+GnokIdOmxt9ECgYEAv21N5MXlkoCFBMI8IeoQ4On7G/gsbha+NobcTfzOuhct
rC6P5I27lSk6W843o8op+bMnFDIcFTXJG4JHY9XRSqfNCkrXOdfUGhxs2ybfMlXkYoH+W2iz
hg4EpUswrwFVaZU+BnBi37qfW70wKZMOeeVmDxPsGeHroCumd94MrtECgYEA5GnWpmE1w5OU
0PA0pWUhvneembp+kmVJxnCE6IPIZ2xkYiIwNjN6PTxH/A8BaFyopYO/EHDqrhQBFnDQzIPN
54FZhEpFecF2IJJ64zeD7518sttJFuiAp7oYWCzUcX2Vz8TSbNMKL495Cq8+Pu//dUfzfoZa
1pwarj4YfWGwe4sCgYEAqOf5b4FwNfjt1wW8lmUd86FG+o+uu316qJUcvUh6K7oZPZJ+9tdp
R2Cc55fWvbJhRbwxikpVA6ftrtxdG9rHulfTCRTcBdyN+OvnlDFbhsRB/JDRX7a8hVepvwYb
5bXh87/rbxfexWhufh9mu0WFPmD/svN1+LhIYCaD0y1WfCECgYEAoTumk4/ptzr/ootwdL/b
lYGdIThZWEd/XuYDdvOchT10CQkS+RVHOkirSIMQDW6VKYoshBN4euDxtXzPn2wY36aZio7H
oT1EUzo4oGNsMKtnFNcGaoYAdmU+XFvhQQ1asMcrH6QHMKGCniFZHV4dvDLJ/vVtTDH0tzrk
HET1C+kCgYEAjmr1l/Ie6FTuAU1Hm628n8Slyfp121dHScmjz426gKLmG+ckNXlYOoVj/F34
UxYdjlq9UG12RwxMrEdaH2hR3j0IBCHbQrXUXZotJS1Yx1pieZ/NE4HB5uAqMk6FlaqWXokZ
mvxkXgwCkVXO4fO5Bh3BZY0+kKXbuByu2Wm4Dfs=

2. 保存和读取RSA密钥对

在CryptoJS的密钥对保存方式中,将RSA密钥保存到.pem文件时,包含了文件内容的收尾信息,例如私钥的保存格式为:

1
2
3
-----BEGIN PRIVATE KEY-----
Base64编码的密钥:xxxxxxx
-----END PRIVATE KEY-----

保存:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
/// <summary>
/// 保存私钥到文件
/// </summary>
bool SaveRSAPrvKey(std::string filePath, std::string prvKey)
{
using namespace CryptoPP;
try
{
if (filePath.empty())
{
throw "File Path is empty!";
}

std::ofstream ofs(filePath);

if (!ofs.is_open())
{
throw "File Open Failed! - " + filePath;
}

ofs << "-----BEGIN PRIVATE KEY-----\n";

ofs << prvKey;

ofs << "-----END PRIVATE KEY-----";

ofs.close();

return true;
}
catch (const std::exception& e)
{
std::cerr << e.what() << std::endl;
return false;
}
}

/// <summary>
/// 保存公钥到文件
/// </summary>
bool SaveRSAPubKey(std::string filePath, std::string pubKey)
{
using namespace CryptoPP;
try
{
if (filePath.empty())
{
throw "File Path is empty!";
}

std::ofstream ofs(filePath);

if (!ofs.is_open())
{
throw "File Open Failed! - " + filePath;
}

ofs << "-----BEGIN PUBLIC KEY-----\n";

ofs << pubKey;

ofs << "-----END PUBLIC KEY-----";

ofs.close();

return true;
}
catch (const std::exception& e)
{
std::cerr << e.what() << std::endl;
return false;
}
}

读取:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93

/// <summary>
/// 从文件读取RSA私钥
/// </summary>
/// <param name="filePath">文件路径</param>
/// <param name="prvKey">读取的私钥字符串</param>
/// <returns>是否读取成功</returns>
bool ReadRSAPrvKey(std::string filePath, std::string* prvKey)
{
try
{
if (!prvKey)
{
throw "Null Pointer";
}

std::ifstream ifs(filePath);
if (!ifs.is_open())
{
throw "File Open Failed! - " + filePath;
}

std::string line;

// 首行
std::getline(ifs, line);

// 读取密钥
*prvKey = "";
while (!ifs.eof())
{
std::getline(ifs, line);
if (line == "-----END PRIVATE KEY-----")
{
break;
}
*prvKey += line + '\n';
}
ifs.close();
return true;
}
catch (const std::exception& e)
{
std::cerr << e.what() << std::endl;
return false;
}
}

/// <summary>
/// 从文件读取RSA公钥
/// </summary>
/// <param name="filePath">文件路径</param>
/// <param name="pubKey">读取的公钥字符串</param>
/// <returns>是否读取成功</returns>
bool ReadRSAPubKey(std::string filePath, std::string* pubKey)
{
try
{
if (!pubKey)
{
throw "Null Pointer";
}
std::ifstream ifs(filePath);
if (!ifs.is_open())
{
throw "File Open Failed! - " + filePath;
}

std::string line;

// 首行
std::getline(ifs, line);

// 读取密钥
*pubKey = "";
while (!ifs.eof())
{
std::getline(ifs, line);
if (line == "-----END PUBLIC KEY-----")
{
break;
}
*pubKey += line + '\n';
}
ifs.close();
return true;
}
catch (const std::exception& e)
{
std::cerr << e.what() << std::endl;
return false;
}
}

3. RSA加密解密

加密函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/// <summary>
/// RSA加密
/// </summary>
/// <param name="str">待加密字符串</param>
/// <param name="pubKey">公钥</param>
/// <param name="encStr">加密后的字符串</param>
/// <returns>是否加密成功</returns>
bool RSAEncrypt(std::string str, std::string pubKey, std::string* encStr)
{
using namespace CryptoPP;
try
{
if (!encStr)
{
throw "Null Pointer";
}
AutoSeededRandomPool rng;
StringSource pubKeySrc(pubKey, true, new Base64Decoder);
RSAES_OAEP_SHA_Encryptor e(pubKeySrc);
StringSource s(str, true,
new PK_EncryptorFilter(rng, e,
new Base64Encoder(new StringSink(*encStr))
)
);

return true;
}
catch (const std::exception& e)
{
std::cerr << e.what() << std::endl;
return false;
}
}

解密函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/// <summary>
/// RSA解密
/// </summary>
/// <param name="encStr">待解密字符串</param>
/// <param name="prvKey">私钥</param>
/// <param name="str">解密后的字符串</param>
/// <returns>是否解密成功</returns>
bool RSADecrypt(std::string encStr, std::string prvKey, std::string* str)
{
using namespace CryptoPP;
try
{
if (!str)
{
throw "Null Pointer";
}
AutoSeededRandomPool rng;
StringSource prvKeySrc(prvKey, true, new Base64Decoder);
RSAES_OAEP_SHA_Decryptor d(prvKeySrc);
StringSource s(encStr, true,
new Base64Decoder(
new PK_DecryptorFilter(rng, d,
new StringSink(*str)
)
)
);

return true;
}
catch (const std::exception& e)
{
std::cerr << e.what() << std::endl;
return false;
}
}

调用示例

以下示例演示了从.pem文件中读取密钥对进行RSA加密解密的过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
///*
//* RSA加密解密
//* 需要读取文件: 公钥.pem, 私钥.pem
//* 公钥 + 明文字符串 --> 密文
//* 私钥 + 密文字符串 --> 明文
//*/
std::string pubKey;
std::string prvKey;

ReadRSAPubKey("./test_pub.pem", &pubKey);
ReadRSAPrvKey("./test_prv.pem", &prvKey);

std::cout << pubKey << std::endl;
std::cout << std::endl;
std::cout << prvKey << std::endl;

// 加密
std::string str = "Test RSA Encrypt, Hello World";
std::string encStr;
RSAEncrypt(str, pubKey, &encStr);
std::cout << encStr << std::endl;

// 解密
std::string decStr;
RSADecrypt(encStr, prvKey, &decStr);
std::cout << decStr << std::endl;

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
公钥:
MIIBIDANBgkqhkiG9w0BAQEFAAOCAQ0AMIIBCAKCAQEAqZI1MFbpgioFmCGeITRcFuoEP0n3
UA/lSxxZKwSFQCS9eguEQIxVs05njeI4Fhoy5Ch6SWz8jvQZtXZa9qtmJqNUFnssJAPVmW/1
yXtr444KdOsFXdhC8sWc9MVVUYgcuyMIBrtWNfLOr5yPnbLd6mW7mDKJW2eN8WQWxW+6Q8sI
2hHU2A8iCvfewFbk8B6mkQTBDdd9jlhBJYFHuiYaxikHUiz4lVyIjOfmdayXgcoSzmuK3+oD
xBVLeIFfBmWFN7O6HRQCthA1T9O7v1Q/0lp321Ml1o7fovw6wYnhYiWzjHdO1QrH4RgepXV3
SpH05yySYUQkSWun9tXrF2lTBwIBEQ==

私钥:
MIIEuwIBADANBgkqhkiG9w0BAQEFAASCBKUwggShAgEAAoIBAQCpkjUwVumCKgWYIZ4hNFwW
6gQ/SfdQD+VLHFkrBIVAJL16C4RAjFWzTmeN4jgWGjLkKHpJbPyO9Bm1dlr2q2Ymo1QWeywk
A9WZb/XJe2vjjgp06wVd2ELyxZz0xVVRiBy7IwgGu1Y18s6vnI+dst3qZbuYMolbZ43xZBbF
b7pDywjaEdTYDyIK997AVuTwHqaRBMEN132OWEElgUe6JhrGKQdSLPiVXIiM5+Z1rJeByhLO
a4rf6gPEFUt4gV8GZYU3s7odFAK2EDVP07u/VD/SWnfbUyXWjt+i/DrBieFiJbOMd07VCsfh
GB6ldXdKkfTnLJJhRCRJa6f21esXaVMHAgERAoIBABjv2qU59Sm64rR9ahPxHJn1PNwhdzFr
v9ZW/g3bBIlu0JHyooH2hRLW0v5G6iFeNKkVAuyuJSQU1pqvhdj7Fo02IvQ/SkGIF+HbwkNG
2yj+TNT1YqvrGOd3aefCqqobi8EqzHl15wfvAEcAb3kSxkgeBP/LMlE04CsHMIZyUBkHB7gX
yqyDrkWtoxLgyDTuptbwNqFzB7ZHXIL6gKDY3QJWDQHgiC0Nd6bGHUATgY5s+07auUqXsaN1
xdpu7RLF3PpKpiq12R7gpKHJKkcYEyM4ZVJyVFyAQnTHXBNpUuxf5BlcGxj1fA09HspTdqlM
X+1EtVNYOhsIGonfXQwoiakCgYEAxssVdJ+DfkPVMUnlZ2p1yeaL+CNM8adS+O2wQ8ZFCKmy
2J8HXU7g7PdE9r8/WWbNn4lzAQIR0E1lmr4KQ+kx3nN2PTdy1/1KW8gXYPd/hRQrFWxH+O61
0ZCHG5tavR4J8oIhr71HKr8lAq0ossx535auuE8SiAMAeUHAPDbia28CgYEA2l5al6NynFO5
JVx4+OqFOvTkIjpJJEK5n/BczHrrMz/GowzIY625oSeS9ZxPAFJ9xl/2BEkD4ev1PCBRm9C+
+1F1EvZmMxoBeghkbn390jUWSws07z+kFvhcrQiWI5ZcfsRJs6E+UZS55DIVNzIQvDZEPEHZ
qsHNrXHznI3ZpekCgYBpPlanJ0WdMvhlY1tU3gIfmCv71nQHdrN0uhIFw1G5SsgYVDETR+B9
c9k3VjCYvfRjhQCmLkWqg1Pogroj8+05iGvGDkvbwlSK8XXJ7HCv3YA4hJ6S2LqcH1aWJRHr
pn28nz8Cr4AHkl7jTJ0TXTF2XtT4KdyiW/ExIs8u7+FH7wKBgQCaJHwuzbpQOxlHjJGgpYs4
rN1FVlG/PiitIiNjKZb2/9eCJyQKIEbMG+9D9d1po6QTjwgDBl0X8dpIj0iqOP9H/UOU6ioF
9D1HFPuZSeBYJXkl6csDO/tbgiNM9wCRl1BZe39Rnv7QLL9z5x4I2AvQJk5IpvQAEFTy5wZQ
ZCEp0QKBgA5a/PIYaKQo3n2qw5jdRI34HLy7Bhpmt2pHMVfcnou/TIZpbyCqSJcqFRorrOvn
i9EhWenM0b4wStuD4KRYByG91N7oZwRKuexIXmzz2YbpImvrfTFbT52wIV5k9jVcgodCDK7N
DekQzQSgu1PFgQitYO+/vaa0DNkKR7yLLguX

密文:
GqvUMa1RpFa5+bgXzXOeor1NkRvYSg776jK9Gq5Foe38sePak9OQEXljUMKxCipjABs6EP+y
AVhxYhYUl/P3xvihponjjDY2U+Fy/JJhGCHjAhzcpfl04a7xQY9C2dPbt41TXXZ4sIYh2k18
X9bvjeQmdtq9x+gQvK4zW0OZn9IBBiKSnbJ2C+N1T/f3YczL4UagjIVNKjGQ6vYhVLBhPAH+
N02bNvknfL/ll+2YNNtuviQpUhIQwlbYOkquQjjzC6JJ8SxOGDogfrO2LQ5V4qENFdqRTa9m
CSRdc4BQbNw7EY6if/EobRTDu/1qBsR/f1xBmKfBKiZXA9CXwIkJIw==

明文:
Test RSA Encrypt, Hello World

JS实现的RSA加密解密函数

与本文之前章节的借助Node-RSA实现的RSA加密解密方式一至,唯一需要修改的地方在于读取的密钥模式,从pkcs1改为pkcs1_oaep

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
const fs = require('fs');
const NodeRSA = require('node-rsa');
const path = require('path');

try {
const keyDir = path.join(__dirname, 'rsa');
const pubKey = new NodeRSA(fs.readFileSync(path.join(keyDir, 'test_pub.pem')),
'pkcs8-public-pem', { encryptionScheme: 'pkcs1_oaep' });
const prvKey = new NodeRSA(fs.readFileSync(path.join(keyDir, 'test_prv.pem')),
'pkcs8-private-pem', { encryptionScheme: 'pkcs1_oaep' });

const msg = JSON.stringify({
text: 'Hello World',
time: Date.now()
});

console.log('公钥:\n%s\n', pubKey.exportKey('pkcs8-public-pem'));
console.log('私钥:\n%s\n', prvKey.exportKey('pkcs8-private-pem'));

const encMsg = pubKey.encrypt(msg, 'base64', 'utf8');
console.log('密文:\n%s\n', encMsg);

const decMsg = prvKey.decrypt(encMsg, 'utf8');
console.log('明文:\n%s\n', decMsg);

} catch (e) {
console.error(e)
}

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
公钥:
-----BEGIN PUBLIC KEY-----
MIIBIDANBgkqhkiG9w0BAQEFAAOCAQ0AMIIBCAKCAQEAqZI1MFbpgioFmCGeITRc
FuoEP0n3UA/lSxxZKwSFQCS9eguEQIxVs05njeI4Fhoy5Ch6SWz8jvQZtXZa9qtm
JqNUFnssJAPVmW/1yXtr444KdOsFXdhC8sWc9MVVUYgcuyMIBrtWNfLOr5yPnbLd
6mW7mDKJW2eN8WQWxW+6Q8sI2hHU2A8iCvfewFbk8B6mkQTBDdd9jlhBJYFHuiYa
xikHUiz4lVyIjOfmdayXgcoSzmuK3+oDxBVLeIFfBmWFN7O6HRQCthA1T9O7v1Q/
0lp321Ml1o7fovw6wYnhYiWzjHdO1QrH4RgepXV3SpH05yySYUQkSWun9tXrF2lT
BwIBEQ==
-----END PUBLIC KEY-----

私钥:
-----BEGIN PRIVATE KEY-----
MIIEuwIBADANBgkqhkiG9w0BAQEFAASCBKUwggShAgEAAoIBAQCpkjUwVumCKgWY
IZ4hNFwW6gQ/SfdQD+VLHFkrBIVAJL16C4RAjFWzTmeN4jgWGjLkKHpJbPyO9Bm1
dlr2q2Ymo1QWeywkA9WZb/XJe2vjjgp06wVd2ELyxZz0xVVRiBy7IwgGu1Y18s6v
nI+dst3qZbuYMolbZ43xZBbFb7pDywjaEdTYDyIK997AVuTwHqaRBMEN132OWEEl
gUe6JhrGKQdSLPiVXIiM5+Z1rJeByhLOa4rf6gPEFUt4gV8GZYU3s7odFAK2EDVP
07u/VD/SWnfbUyXWjt+i/DrBieFiJbOMd07VCsfhGB6ldXdKkfTnLJJhRCRJa6f2
1esXaVMHAgERAoIBABjv2qU59Sm64rR9ahPxHJn1PNwhdzFrv9ZW/g3bBIlu0JHy
ooH2hRLW0v5G6iFeNKkVAuyuJSQU1pqvhdj7Fo02IvQ/SkGIF+HbwkNG2yj+TNT1
YqvrGOd3aefCqqobi8EqzHl15wfvAEcAb3kSxkgeBP/LMlE04CsHMIZyUBkHB7gX
yqyDrkWtoxLgyDTuptbwNqFzB7ZHXIL6gKDY3QJWDQHgiC0Nd6bGHUATgY5s+07a
uUqXsaN1xdpu7RLF3PpKpiq12R7gpKHJKkcYEyM4ZVJyVFyAQnTHXBNpUuxf5Blc
Gxj1fA09HspTdqlMX+1EtVNYOhsIGonfXQwoiakCgYEAxssVdJ+DfkPVMUnlZ2p1
yeaL+CNM8adS+O2wQ8ZFCKmy2J8HXU7g7PdE9r8/WWbNn4lzAQIR0E1lmr4KQ+kx
3nN2PTdy1/1KW8gXYPd/hRQrFWxH+O610ZCHG5tavR4J8oIhr71HKr8lAq0ossx5
35auuE8SiAMAeUHAPDbia28CgYEA2l5al6NynFO5JVx4+OqFOvTkIjpJJEK5n/Bc
zHrrMz/GowzIY625oSeS9ZxPAFJ9xl/2BEkD4ev1PCBRm9C++1F1EvZmMxoBeghk
bn390jUWSws07z+kFvhcrQiWI5ZcfsRJs6E+UZS55DIVNzIQvDZEPEHZqsHNrXHz
nI3ZpekCgYBpPlanJ0WdMvhlY1tU3gIfmCv71nQHdrN0uhIFw1G5SsgYVDETR+B9
c9k3VjCYvfRjhQCmLkWqg1Pogroj8+05iGvGDkvbwlSK8XXJ7HCv3YA4hJ6S2Lqc
H1aWJRHrpn28nz8Cr4AHkl7jTJ0TXTF2XtT4KdyiW/ExIs8u7+FH7wKBgQCaJHwu
zbpQOxlHjJGgpYs4rN1FVlG/PiitIiNjKZb2/9eCJyQKIEbMG+9D9d1po6QTjwgD
Bl0X8dpIj0iqOP9H/UOU6ioF9D1HFPuZSeBYJXkl6csDO/tbgiNM9wCRl1BZe39R
nv7QLL9z5x4I2AvQJk5IpvQAEFTy5wZQZCEp0QKBgA5a/PIYaKQo3n2qw5jdRI34
HLy7Bhpmt2pHMVfcnou/TIZpbyCqSJcqFRorrOvni9EhWenM0b4wStuD4KRYByG9
1N7oZwRKuexIXmzz2YbpImvrfTFbT52wIV5k9jVcgodCDK7NDekQzQSgu1PFgQit
YO+/vaa0DNkKR7yLLguX
-----END PRIVATE KEY-----

密文:
ZTONK6mHtgVEDjFB9mIOqg+pxEfNgpeJQuH5I0gVVP1OkUaL37VKTA26UcIVakphcVa3Ju5fD0TpaEFpZRsWrxuGIM8Umrbc7fI1Y2Pie3O/EhlF51DRqKxEL5LZKbVqulCw1kI6rtxQlWsogeOwIVHY2vTsGQAjAJB1nZUQNZEfelnSRZxs+OU8VlRgn3hWAua5wtPrCM48eq+Mfo1X/EuzD8EFAAtqHEsYQzz6UJl+LPZCw0vGk7flJ3HffMFhXmezCEQbKWeE6B9eyaSP9qFDiZGAu8AoCBWyNJeMIZ9cmbNG1yPCEyciICUUCvqw2Av05VMmJSpQkmoehrZuOw==

明文:
{"text":"Hello World","time":1624011798408}

C++实现的

C++与JS的配合使用的AES+RSA双重加密

AES+RSA的双重加密思路为:随机生成AES的密钥key和偏移iv,使用该密钥+偏移对数据进行加密,得到密文1,接着使用RSA算法对包含AES密钥key和偏移iv的json字符串进行加密,得到密文2;传输两串密文,接收方收到这两串密文后,首先通过RSA解密算法解出AES密钥和偏移,接着再解出数据的明文。

加密说明示例

原始数据(JSON格式):

1
2
3
4
{
"account": "test",
"password": "123456"
}

随机生成的AES密钥和偏移,用JSON格式保存:

1
2
3
4
{
"key": "D8BB5D728B2C96A77AFA7519D060BE57",
"iv": "170A9B9576B1ECAEE13583C784D6139B"
}

将原始数据和AES密钥和偏移的JSON转为字符串后,经AES+RSA双重加密,得到加密后的数据:

1
2
3
4
{
"key": "mM5/dq8hd2ekOHPHT48yrcAJrw3Z4aMAwdmifDeVYbK2xfy53cRm5tJISq5jnN6xDX5SvwrDKCy3QB9qGW0C1wzp/ou5SuJ3/4Rumd2ZdEs9KbmnN/Atcm1oksi5TfmGnqlbnwKN2Z98saZ46DBdiH4pGC2Us9m2MqNTlHXgbQuU56SAjvM7seN/RCwFxjPkAOCjvMIKcq7O2TOhABWSt6GIypvT2PkwopavfmNO3J+5G4dQgNNiCeRM/5uRDEdQcz0GWhF0Fh846rSF9Yt2S5oTww/gxyiMSGOe5fuxaBGEvEhOzEhF6UqHo9lR1qHidSjNJbVQWKpadcKu5VLVuw==",
"params": "C596AF0D135693FF66B515161710158100B55434E7CDD62C7696BABAA7C5F68774AD6C839551FA00F5353C9DCD6F47CE0814D34658215E1BCA4C40D778E136A5"
}

解密时,先用RSA私钥从key中解出AES密钥和偏移,接着用AES算法从params中解出原始数据。

纯C++实现

用到了boost存放JSON数据,头文件、预定义和工具函数如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
typedef boost::property_tree::ptree JsonData;


void print(JsonData json, std::string title = "")
{
try
{
std::stringstream ss;
boost::property_tree::write_json(ss, json);

if (title.size() > 0)
{
std::cout << title << std::endl;
}
std::cout << ss.str() << std::endl;
}
catch (const std::exception& e)
{
std::cerr << e.what() << std::endl;
}
}

加密函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
bool AES_RSA_Encrypt(JsonData data, JsonData* encData, std::string rsaPubKeyPath = "./test_pub.pem")
{
try
{
if (!encData) throw "输出指针为空";

std::stringstream ss;

// 转为字符串
std::string jsonStr;
boost::property_tree::write_json(ss, data);
jsonStr = ss.str();
ss.str("");

/*
* AES 加密
*/
JsonData aesKey;
std::string encJsonStr, key, iv;
if (!AesEncrypt(jsonStr, &encJsonStr, &key, &iv)) throw "Aes加密失败";

std::string aesKeyStr;
aesKey.put<std::string>("key", key);
aesKey.put<std::string>("iv", iv);
boost::property_tree::write_json(ss, aesKey);
aesKeyStr = ss.str();
ss.str("");

/*
* RSA加密
*/
std::string encAesKeyStr, pubKey;
if (!ReadRSAPubKey(rsaPubKeyPath, &pubKey)) throw "读取RSA公钥失败";
if (!RSAEncrypt(aesKeyStr, pubKey, &encAesKeyStr)) throw "RSA加密失败";

// 覆盖输出内容
encData->clear();
encData->put<std::string>("params", encJsonStr);
encData->put<std::string>("key", encAesKeyStr);

return true;
}
catch (const std::exception& e)
{
std::cerr << e.what() << std::endl;
return false;
}
}

解密函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
bool AES_RSA_Decrypt(JsonData encData, JsonData* decData, std::string rsaPrvKeyPath = "./test_prv.pem")
{
try
{
if (!decData) throw "输出指针为空";

using boost::property_tree::read_json;

std::stringstream ss;

// 解出AES密钥
JsonData aesKey;
std::string key, iv, aesKeyStr;
std::string prvKey;
if (!ReadRSAPrvKey(rsaPrvKeyPath, &prvKey)) throw "读取RSA密钥失败";
if (!RSADecrypt(encData.get<std::string>("key"), prvKey, &aesKeyStr)) throw "RSA解密失败";
ss.str(aesKeyStr);
read_json(ss, aesKey);
key = aesKey.get<std::string>("key");
iv = aesKey.get<std::string>("iv");
ss.str("");

// AES解密
decData->clear();
std::string dataStr;
if (!AesDecrypt(encData.get<std::string>("params"), key, iv, &dataStr)) throw "AES解密失败";
ss.str(dataStr);
read_json(ss, *decData);
ss.str("");

return true;
}
catch (const std::exception& e)
{
std::cerr << e.what() << std::endl;
return false;
}
}

调用结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
int main(int argc, char* argv[])
{
try
{
// json数据
JsonData json;
json.put<std::string>("account", "test");
json.put<std::string>("password", "123456");
print(json, "原始数据:");

// 加密
JsonData encJson;
AES_RSA_Encrypt(json, &encJson, "./test_pub.pem");
print(encJson, "加密后的数据:");

// 解密
JsonData decJson;
AES_RSA_Decrypt(encJson, &decJson, "./test_prv.pem");
print(decJson, "解密后的数据:");
}
catch (const std::exception& e)
{
std::cerr << e.what() << std::endl;
return -1;
}


return getchar();
}

输出结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
原始数据:
{
"account": "test",
"password": "123456"
}

加密后的数据:
{
"params": "16DCBC4F58148239BCC789C356F8A33CFE9453C91589B4395C9694CCF39D14382BFEC01512686A4796AC8203EE341E1D24CBCD83E2B9247266E3B708DA842909",
"key": "m3c\/k\/9Rcus1Zg6gJuqaXbtbbUmEp2VIh4cu2J14DIiMRqQdyFQbOEjVyFPt7yVLrNJM+JIL\nR5Jz12dAmrvaEosxn2QLF91E2vXFPb5\/jMHbpO4aP6LqfAse6SJWu73eNqBXHfmw4Jjlk8Pp\nGst58hFt8JBEqPqESSMiMH5YAYtxQW2vk08xQO86bxkBwPrUlFnp0VJ\/bcyZ2881fP0lus8m\nIppIvnChyyDHK2sUS1pRCBfnKOHayVrLQft6euXnDgXinhtxBIiM90FBEG1lAugnz6+PRpDl\nWXOXv5XdeXsFYTmqC\/2L+34L3a4pdM+mjD7zaybUXwhRjAa77IQOpg==\n"
}

解密后的数据:
{
"account": "test",
"password": "123456"
}

纯JS实现

加密和解密函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const encrypt = (json = Object, rsaPubKeyPath = './test_pub.pem') => {
// AES加密
const aesResults = AesEncrypt(JSON.stringify(json));
const key = aesResults.hexKeyStr;
const iv = aesResults.hexIvStr;

// RSA加密
const aesKey = { key, iv };
const pubKey = new NodeRSA(fs.readFileSync(rsaPubKeyPath),
'pkcs8-public-pem', { encryptionScheme: 'pkcs1_oaep' });

return {
params: aesResults.hexEncStr,
key: pubKey.encrypt(JSON.stringify(aesKey), 'base64', 'utf8')
};
}

const decrypt = (encJson = Object, rsaPrvKeyPath = './test_prv.pem') => {
// RSA解密,解出AES密钥偏移
const prvKey = new NodeRSA(fs.readFileSync(rsaPrvKeyPath),
'pkcs8-private-pem', { encryptionScheme: 'pkcs1_oaep' });
const aesKey = JSON.parse(prvKey.decrypt(encJson['key'], 'utf8'));

// AES解密
return JSON.parse(AesDecrypt(encJson['params'], aesKey['key'], aesKey['iv']));
}

调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
try {
const json = {
account: 'test',
password: '123456'
};
console.log('原始数据:\n', json);

const encJson = encrypt(json, './test_pub.pem');
console.log('加密后的数据:\n', encJson);

const decJson = decrypt(encJson, './test_prv.pem');
console.log('解密后的数据:\n', decJson);
} catch (e) {
console.error(e)
}

输出结果:

1
2
3
4
5
6
7
8
9
原始数据:
{ account: 'test', password: '123456' }
加密后的数据:
{
params: '9f1de48b55c4b422f01923fd7e535293a07ddb950615fae7c7563313ba605e7164e67145ecb285ea1ea507d851c9238f',
key: 'C7ZEIYUcxT2suOAIwNZj+Inof/WUFMWPPSrbB29hQ38AkeS44wsfizrvaojAzzN/xfyOXt9Slg88p6QmnmlvtIwNtl9ibrDqQJP/IGtV7bYImQIGKhm8MjAR6lYohM8dhExB5ht+NUzKXBQYzkjXgipbqOXwk0bu2NNrBfftQ+/EJVOsZd5W7Dsf+hhsnSWgZ/DusMjRqt0kY1UGKFTgKotBin0bNTpRARCnBdYibrb+JyqiYOQotQgSXfLvdBmJVFs9YvGDZJtfn6zz5yBDkkWY4LDIF7/aIukw7qYnK2E17r+sWA018ElofSmOv4yyKe6WKgvU49R5DOaKBUUjpA=='
}
解密后的数据:
{ account: 'test', password: '123456' }

C++加密,JS解密

在C++中,通过如下调用方式进行数据加密:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int main(int argc, char* argv[])
{
try
{
// json数据
JsonData json;
json.put<std::string>("account", "test");
json.put<std::string>("password", "123456");

// 加密
JsonData encJson;
AES_RSA_Encrypt(json, &encJson, "./test_pub.pem");
print(encJson);
}
catch (const std::exception& e)
{
std::cerr << e.what() << std::endl;
return -1;
}

return getchar();
}

得到输出:

1
2
3
4
{
"params": "645588172227F34FBA6C7A6D2D3CB1154046E970D780536E6BCC83B75BC4DFE3D90AD7C2A96CB48E6547AFEC0FA1DD7605DD0A44FAA42FAC8E284E69445F90C6",
"key": "aRoVwUpqFYyDcCxFfDEmaFQIq3djFKMH\/U0vyb8rJAvKX4hhzVieDcCnsC9icWAaXqMQJKEe\n9Hj7XvSf9bRG68qze5KHW3vNRGS44iAr2hI0Bf+w0SbPceYElnAritlFd2iTtKe5QkdPt9gR\nnV1O7JnWISXDW0CmdTJ\/T3okCo40sRyQnnKhbyv15MaM1xJVaAmBQDxVA0LsiSyM23kPu2bi\nQg9QOISF3zdp8\/QNrrjS66AlgMfBlFB3DUA1+kC8KSQfa4k1X\/SVfoeZLd6LB4OBR0hQbXi\/\n6mQpbXOpTpaCrQkEsrcnCdebWQPcBKtmF0OuuvAKXpmekhYyE9MwKw==\n"
}

将输出拷贝到JS中解密:

1
2
3
4
5
6
7
8
9
10
try {
const encJson = {
"params": "645588172227F34FBA6C7A6D2D3CB1154046E970D780536E6BCC83B75BC4DFE3D90AD7C2A96CB48E6547AFEC0FA1DD7605DD0A44FAA42FAC8E284E69445F90C6",
"key": "aRoVwUpqFYyDcCxFfDEmaFQIq3djFKMH\/U0vyb8rJAvKX4hhzVieDcCnsC9icWAaXqMQJKEe\n9Hj7XvSf9bRG68qze5KHW3vNRGS44iAr2hI0Bf+w0SbPceYElnAritlFd2iTtKe5QkdPt9gR\nnV1O7JnWISXDW0CmdTJ\/T3okCo40sRyQnnKhbyv15MaM1xJVaAmBQDxVA0LsiSyM23kPu2bi\nQg9QOISF3zdp8\/QNrrjS66AlgMfBlFB3DUA1+kC8KSQfa4k1X\/SVfoeZLd6LB4OBR0hQbXi\/\n6mQpbXOpTpaCrQkEsrcnCdebWQPcBKtmF0OuuvAKXpmekhYyE9MwKw==\n"
}
const decJson = decrypt(encJson, './test_prv.pem');
console.log('解密后的数据:\n', decJson);
} catch (e) {
console.error(e)
}

得到输出:

1
2
解密后的数据:
{ account: 'test', password: '123456' }

JS加密,C++加密

在JS中加密:

1
2
3
4
5
6
7
8
9
10
11
12
try {
const json = {
account: 'test',
password: '123456'
};

const encJson = encrypt(json, './test_pub.pem');
console.log(encJson);

} catch (e) {
console.error(e)
}

输出:

1
2
3
4
{
params: 'a16f83ebb59728f9b0a22fcbac1011c718017cbea832e2abe400e4628ee5562eaf382641a9b9bd8edf09c3ac36106e30',
key: 'Us2xIrwbB7WgZVMJriP8oAXUFa1UF9cTJPq1pvUHWdJCtkbcYLtRD5WJwTavtmg0Wj1EmzN6awMPmHR0bEdu1Psh8UbgEzZh1+pHQ86kw3DrHn2JhYNbZeucKUwKjqbKIzr/ti7hKRxtVE6PtUn9xZe+BfhgJzqZRAvr12NToXc+vU5DxT5Rp7BhMDasi1V1O/ssNIh3/RIeasfD1tmTtlzGmW3F/+VBAkR1S3eaFpPBtNB77SHYJSZW6GEjJA85HZCYyuQKVRPIxoPPp2vC7LOanxhREMiAebEt877RJ5SuC1UP4jHV46VBBhn7N2N/eh1eeQysyV8+ipM3kzA4Og=='
}

拷贝到C++中进行解密:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int main(int argc, char* argv[])
{
try
{
// 加密
JsonData encJson;
encJson.put<std::string>("params", "a16f83ebb59728f9b0a22fcbac1011c718017cbea832e2abe400e4628ee5562eaf382641a9b9bd8edf09c3ac36106e30");
encJson.put<std::string>("key", "Us2xIrwbB7WgZVMJriP8oAXUFa1UF9cTJPq1pvUHWdJCtkbcYLtRD5WJwTavtmg0Wj1EmzN6awMPmHR0bEdu1Psh8UbgEzZh1+pHQ86kw3DrHn2JhYNbZeucKUwKjqbKIzr/ti7hKRxtVE6PtUn9xZe+BfhgJzqZRAvr12NToXc+vU5DxT5Rp7BhMDasi1V1O/ssNIh3/RIeasfD1tmTtlzGmW3F/+VBAkR1S3eaFpPBtNB77SHYJSZW6GEjJA85HZCYyuQKVRPIxoPPp2vC7LOanxhREMiAebEt877RJ5SuC1UP4jHV46VBBhn7N2N/eh1eeQysyV8+ipM3kzA4Og==");

// 解密
JsonData decJson;
AES_RSA_Decrypt(encJson, &decJson, "./test_prv.pem");
print(decJson, "解密后的数据:");
}
catch (const std::exception& e)
{
std::cerr << e.what() << std::endl;
return -1;
}

return getchar();
}

输出:

1
2
3
4
5
解密后的数据:
{
"account": "test",
"password": "123456"
}