UE4中Crypto++库加密解密
第五节:RSA签名解签 - 前端JSEncrypt库、jsrsasign库和后端UE4使用Crypto++互相加解签
前言
后端签名,前端解签,或者前端加签,后端解签。后端事先生成公钥和私钥,公钥发给前端页面,私钥后端自己保留。非对称加密算法常用RSA算法,签文使用base64编码成字符串,后端UE4使用Crypto++库,前端使用JSEncrypt.js或者jsrsasign.js进行RSA的对应操作。经过测试,本例中的前后端代码的加签解签计算结果是一致的。
提示:以下是本篇文章正文内容,下面案例可供参考
一、前端
前端html分别使用JSEncrypt库和jsrsasign库写两个函数,一个是加签函数,使用私钥加签字符串并将签文发送到后端进行解签。另一个是解签函数,需要接收后端发来的签文,并使用公钥验证签文。
<script src="./crypto-js.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/jsencrypt/3.2.1/jsencrypt.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/jsrsasign/10.5.13/jsrsasign-all-min.min.js"></script>
<script type = "text/javascript">
//签名
function RsaSign(plainText){
var signStr = new JSEncrypt();
//设置私钥
// signStr.setPrivateKey('-----BEGIN RSA PRIVATE KEY-----'+privKey+'-----END RSA PRIVATE KEY-----');
signStr.setPrivateKey(privKey)
//用私钥给明文加签,例子中使用SHA1算法,其它算法也可,如:SHA256
// var signature = signStr.sign(plainText, CryptoJS.SHA256, "sha256");
var signature = signStr.sign(plainText, CryptoJS.SHA1, "sha1"); //和后端使用一样的散列算法
return signature;
}
//验签
function RsaVerify(plainText, signature){
var verify = new JSEncrypt();
//设置公钥
// verify.setPublicKey('-----BEGIN PUBLIC KEY-----' + pubKey + '-----END PUBLIC KEY-----');
verify.setPublicKey(pubKey)
//验证方法有三个参数明文,用私钥加签后的字符串,加签的算法(跟上文保持一致哈~)
var verified = verify.verify(plainText, signature, CryptoJS.SHA1); //和后端使用一样的散列算法
return verified;
}
//签名
function RsaSign_jsrsasign(plainText){
// 创建 Signature 对象
let signature=new KJUR.crypto.Signature({alg:"SHA1withRSA",prvkeypem:privKey}); //!这里指定 私钥 pem!
signature.updateString(plainText);
let a = signature.sign();
let sign = hextob64(a);
return sign
}
//验签
function RsaVerify_jsrsasign(plainText, signature){
// !要重新new 一个Signature, 否则, 取摘要和签名时取得摘要不一样, 导致验签误报失败(原因不明)!
let signatureVf = new KJUR.crypto.Signature({alg:"SHA1withRSA",prvkeypem:pubKey});
signatureVf.updateString(plainText);
// !接受的参数是16进制字符串!
let b = signatureVf.verify(b64tohex(signature));
return b;
}
</script>
二、后端
后端UE4写三个函数,一个是生成公私密钥函数。一个是加签函数,使用私钥加签字符串并将签文发送到前端进行解签。另一个是解签函数,需要接收前端发来的签文,并使用公钥验证签文。
以下为实现逻辑:
1. C++代码
RSALibrary.h
#pragma once
#include <string>
using namespace std;
#include "../../../ThirdParty/crypto/include/Win64/osrng.h"
#include "../../../ThirdParty/crypto/include/Win64/aes.h"
#include "../../../ThirdParty/crypto/include/Win64/hex.h"
#include "../../../ThirdParty/crypto/include/Win64/files.h"
#include "../../../ThirdParty/crypto/include/Win64/rsa.h"
#include "../../../ThirdParty/crypto/include/Win64/base64.h"
using namespace CryptoPP;
#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "RSALibrary.generated.h"
/**
*
*/
UCLASS()
class H_CRYPTO860_PLUGIN_API URSALibrary : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
//生成RSA公钥和私钥
UFUNCTION(BlueprintCallable, meta = (DisplayName = "GenerateRSAKey_ue4", Wordkeys = "GenerateRSAKey_ue4"), Category= "RSA")
static bool GenerateRSAKey_ue4(int keyLength, FString privFilename, FString pubFilename);
//RSA使用私钥签名
UFUNCTION(BlueprintCallable, meta = (DisplayName = "RsaSignString", Wordkeys = "RsaSignString"), Category= "RSA")
static FString RsaSignString(FString privFilename, FString tobeSignString);
//RSA使用公钥验证
UFUNCTION(BlueprintCallable, meta = (DisplayName = "RsaVerString", Wordkeys = "RsaVerString"), Category= "RSA")
static bool RsaVerString(FString pubFilename, FString tobeSignString, FString signedString);
private:
//随机种子
static AutoSeededRandomPool & getRng();
};
RSALibrary.cpp
#include "RSALibrary.h"
#include "assert.h"
//生成公私密钥
bool URSALibrary::GenerateRSAKey_ue4(int keyLength, FString privFilename, FString pubFilename)
{
bool isGenerate = true;
try
{
RSAES_PKCS1v15_Decryptor priv(getRng(), keyLength);
Base64Encoder privFile(new FileSink(TCHAR_TO_ANSI(*privFilename))); //生成base64格式密文
priv.AccessMaterial().Save(privFile);
privFile.MessageEnd();
RSAES_PKCS1v15_Encryptor pub(priv);
Base64Encoder pubFile(new FileSink(TCHAR_TO_ANSI(*pubFilename))); //生成base64格式密文
pub.AccessMaterial().Save(pubFile);
pubFile.MessageEnd();
}
catch (CryptoPP::Exception& e) {
isGenerate = false;
cout << "RSALibrary::GenerateRSAKey_ue4 Error: " << e.what() << endl;
}
return isGenerate;
}
//签名
FString URSALibrary::RsaSignString(FString privFilename, FString tobeSignString)
{
const char* fileName = TCHAR_TO_ANSI(*privFilename);
FileSource priFile(fileName, true, new Base64Decoder); //使用base64格式密钥
RSASSA_PKCS1v15_SHA_Signer privkey(priFile);
string signedString; //签名后的文本
StringSource(TCHAR_TO_UTF8(*tobeSignString), true, new SignerFilter(getRng(), privkey, new Base64Encoder(new StringSink(signedString))));//使用base64格式密文
return UTF8_TO_TCHAR(signedString.c_str());
}
//验证签名
bool URSALibrary::RsaVerString(FString pubFilename, FString tobeSignString, FString signedString)
{
const char* fileName = TCHAR_TO_ANSI(*pubFilename);
FileSource pubFile(fileName, true, new Base64Decoder); //使用base64格式密钥
RSASSA_PKCS1v15_SHA_Verifier pubkey(pubFile);
StringSource signatureString(TCHAR_TO_UTF8(*signedString), true, new Base64Decoder); //使用base64格式密文
if (signatureString.MaxRetrievable() != pubkey.SignatureLength())
{
return false;
}
SecByteBlock signature(pubkey.SignatureLength());
signatureString.Get(signature, signature.size());
SignatureVerificationFilter *verifierFilter = new SignatureVerificationFilter(pubkey);
verifierFilter->Put(signature, pubkey.SignatureLength());
StringSource(TCHAR_TO_UTF8(*tobeSignString), true, verifierFilter);
return verifierFilter->GetLastResult();
}
//自动生成随机种子
AutoSeededRandomPool & URSALibrary::getRng()
{
static AutoSeededRandomPool m_Rng;
return m_Rng;
}
2. 蓝图
后端UE4自己实现签名和验签
UE4签名后,将签文发给前端进行验签
UE4接收前端发来的签文,进行验签。
测试结果
将html页面加载到UE4中,运行效果如下:
-
后端UE4加签,前端html分别使用JSEncrypt库和jsrsasign库解签
-
前端html分别使用JSEncrypt库和jsrsasign库加签,后端UE4解签
case 'JSEncrypt签名':
textStr = RsaSign(content);
console.log(textStr);
window.ue4("Decoder",{"Type":"RSA_Sign","content":textStr});
deStr = RsaVerify(content, textStr);
// deStr = RsaVerify_jsrsasign(content, textStr);
console.log(deStr);
break;
case 'jsrsasign签名':
textStr = RsaSign_jsrsasign(content);
console.log(textStr);
window.ue4("Decoder",{"Type":"RSA_Sign","content":textStr});
deStr = RsaVerify_jsrsasign(content, textStr);
// deStr = RsaVerify(content, textStr);
console.log(deStr);
break;
前端签名
完美实现前后端加解签。
总结
一般情况下,前端自己实现加解签没有问题,后端自己实现加解签也没有问题。问题主要出在2个方面:
- 前后端编码格式不一样
前端加签的结果传到后端,或者后端加签的结果传到前端,对方无法识别接收到的字符串。主要原因在于双方的字符串编码格式不一样,前端的是base64格式,后端的就不能是hex格式,需要使用同样的编码格式。 - 散列算法不一样
因为后端使用SHA散列算法,这是默认的SHA1算法,所以前端也要使用SHA1算法。 - 密钥格式不一样
后端Crypto++库生成的是key格式的密钥,前端jsrsasign库使用的是pem格式的密钥(JSEncrypt库可以兼容两种格式),所以需要将key格式转换为pem格式,转换请参考:RSA公钥私钥生成pem文件
参考
前端开源项目 CDN 加速服务网站
前端RSA加密、解密、签名、验签。本文包含jsencrypt和jsrsasign两种方法教程
jsrsasign使用笔记(加密,解密,签名,验签)
JavaScript怎么做RSA加密解密, 签名和验签? 有没有好用的库?
RSA PKCS1 填充方式
Crypto++库实现AES和RSA加密解密
基于Crypto++/Cryptopp的rsa密钥生成,rsa加密、解密,rsa签名、验签
Crypto++使用
利用Crypto++实现RSA加密算法