环境说明 Node-RSA的安装与使用 生成公钥私钥.pem文件 读取.pem文件,RSA加密解密 Crypto.js的安装与使用 生成随机的密钥 AES加密与解密 Crypto++的编译与使用 基本编译过程 创建C++程序引入Crypto++进行测试 使用动态链接库的方式引入Crypto++ MD还是MT?生成不同运行库的方法 使用Crypto++分别完成AES和RSA加密解密 生成RSA公钥私钥对,进行加密解密 AES加密与解密 C++与JS之间互通的加密和解密函数 C++实现的AES加密解密函数 1. 16进制字符串 转 字节数组 2. 字节数组 转 16进制字符串 3. 16进制编码:字符串 转为 16进制字符串 4. 16进制解码:16进制字符串 转为 字符串 5. AES 加密 6. AES 解密 调用结果 JS实现的AES加密解密函数 1. 16进制随机字符串生成 2. AES 加密 3. AES 解密 调用结果 C++实现的RSA加密解密函数 1. 生成RSA密钥对 2. 保存和读取RSA密钥对 3. RSA加密解密 调用示例 JS实现的RSA加密解密函数 C++与JS的配合使用的AES+RSA双重加密 加密说明示例 纯C++实现 纯JS实现 C++加密,JS解密 JS加密,C++加密
环境说明 客户端:C++
服务端:Node.js
需要在客户端与服务端进行加密通讯,首先考虑使用RSA算法进行加密,该算法要求数据长度不能超过密钥长度,因此通常用来加密一些较短的数据,考虑使用AES对数据进行加密,使用RSA对AES密钥进行加密,以此充分利用算法的特性,实现加密过程。
Node-RSA的安装与使用
https://www.npmjs.com/package/node-rsa
安装
生成公钥私钥.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 { const key = new NodeRSA ({ b : 2048 }); 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 2 3 4 5 6 7 8 9 10 11 12 13 14 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个项目,其中我们需要用到的有两个:cryptdll
和cryptlib
,分别对应动态库(lib+dll)和静态库(lib)。在“生成-批生成”中分别选择对应项目的32位和64位的Debug和Release版本,总共四个,全都勾上,点击【生成】,全程大约花费3-5min,时间和CPU性能有关系。
生成的结果保存在.sln
文件同一目录下的Win32
和x64
文件夹下,需要把里面我们需要用到的东西取出放到特定的文件夹下。创建两个文件夹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目录的绝对路径:
接着根据配置(Debug/Release)和平台(x64/Win32),在“链接器-输入-附加依赖项”引入对应文件夹中的.lib文件,这里引入的是静态库文件(static),这也是这个库的作者推荐的使用方式,不需要额外添加.dll文件到.exe文件目录下或环境变量中,生成成功后可以直接运行。
由于Crypto++的工程文件默认生成MT的运行库,因此还需要额外修改“C/C++-代码生成-运行库”,Debug版本选择/MTD
,Release版本选择/MT
。(如果不想修改这个设置,见后续小节)
两个地方引入完成后,创建一个.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 ; }
如果配置正确,执行代码后将在命令行中输出如下信息:
使用动态链接库的方式引入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.
因此,在工程配置中,将引入的静态库改为动态库:
接着在代码的头文件处引入dll.h
:
1 #include <cryptopp/dll.h>
最后把对应的.dll文件cryptopp.dll
拷贝到.exe目录下,即可运行。如果在VS中运行Debug版本抛出异常,按下F5继续执行即可,或者直接Ctrl+F5执行不调试也可以正确输出,发生的异常属于Crypto++内部的小BUG,无伤大雅。
使用动态库生成的.exe文件会小很多,且频繁运行时可以充分利用系统的缓存机制,运行速度快;使用静态库生成的.exe文件较大,但是只有一个文件,较为简洁,便于传递。
MD还是MT?生成不同运行库的方法 MD意为:multithread and DLL-specific version,多线程DLL版本。编译器加载的是动态运行的库,依赖.lib和.dll文件。
MT意为:multithread, static version, 多线程静态版本。编译器加载的是静态运行的库,仅依赖.lib文件。
若生成工程时使用的是/MT
生成的,它所调用的运行时库为:LIBCMT.lib
,编译器将其安置到.obj文件中,让链接器使用LIBCMT.lib
处理外部符号,并完成相关的运算操作(一个文件完成);
若生成工程时使用的是/MD
生成的,调用的运行库有两个:MSVCRT.lib
和MSVCRxxx.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.lib
和MSVCRxxx.dll
,当调用某个系统库函数时,若两个运行库中均有这个函数,就会出现重定义的错误。因此当在另一个工程中使用由其他工程生成的库文件时,在“属性-C/C++-代码生成”中的运行库设置一定需要一至。
在Crypto++的.sln文件中,四个工程的默认运行库都是/MT
,因此生成库文件要在别的工程中使用,该工程需要将运行库也一同设置为/MT
。此时的矛盾点在于,使用VS创建的工程默认都是/MD
,有一些现有的工程都是使用/MD
的运行库,因此要进行嵌入的话,必须适配。
修改不同运行库的方法很简单,右键两个工程cryptdll
和cryptlib
,在“属性-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;AutoSeededRandomPool rng; InvertibleRSAFunction params; params.GenerateRandomWithKeySize (rng, 3072 ); RSA::PrivateKey privateKey (params) ;RSA::PublicKey publicKey (params) ;string plain = "RSA Encryption" , cipher, recovered; RSAES_OAEP_SHA_Encryptor e (publicKey) ;StringSource ss1 (plain, true , new PK_EncryptorFilter(rng, e, new StringSink(cipher) ) ) ; RSAES_OAEP_SHA_Decryptor d (privateKey) ;StringSource ss2 (cipher, true , new PK_DecryptorFilter(rng, d, new StringSink(recovered) ) ) ; 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) ) ) ; } 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) ) ) ; 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 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 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 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 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 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 ; } 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; 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 ) { 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 ) { 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 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 { 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 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 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 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 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) ; 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 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 ; } } 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 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 ; } } 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 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 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 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 ("" ); 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 ("" ); 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; 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 ("" ); 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 { 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' ) => { const aesResults = AesEncrypt (JSON .stringify (json)); const key = aesResults.hexKeyStr ; const iv = aesResults.hexIvStr ; 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' ) => { const prvKey = new NodeRSA (fs.readFileSync (rsaPrvKeyPath), 'pkcs8-private-pem' , { encryptionScheme : 'pkcs1_oaep' }); const aesKey = JSON .parse (prvKey.decrypt (encJson['key' ], 'utf8' )); 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 { 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" }