js rsa验签_RSA后台签名前台验签的应用(前台采用jsrsasign库)

本文介绍了如何使用RSA算法在后台生成签名并返回给前端,前端利用公钥进行验签,确保数据在传输过程中未被篡改。详细讲述了后台Java实现签名的过程以及前端JavaScript的验签代码,旨在防止中间人攻击导致的数据安全性问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

写在前面

安全测试需要, 为防止后台响应数据返给前台过程中被篡改前台再拿被篡改后的数据进行接下来的操作影响正常业务, 决定采用RSA对响应数据进行签名和验签, 于是有了这篇.

我这里所谓的返给前台的数据只是想加密用户验证通过与否的字段success是true还是false, 前台拿这个success作为判断依据进行下一步的操作, 是进一步向后台发起请求还是直接弹出错误消息.照测试结果看这是个逻辑漏洞, 即使后台返回的是false, 在返回前台的过程中响应包被劫获, 将false改为true, 这样的操作也是能做到的(BurpSuit). 所以后台响应数据尽量不要再二次使用. 那既然能篡改, 如何防止流氓篡改呢?

说一下整体思路: 首先生成密钥对, 私钥存放在后台用于签名, 公钥存放在前台用于验签. 所谓签名就是指拿明文+私钥生成的签名结果, 返回数据给前台时将明文+签名结果一并返给前台, 前台用公钥+接收到的明文+签名结果进行验签, 这样即使响应包被劫获, 篡改明文后, 验证签名时也不会验证通过, 从而达到响应数据防篡改的目的.

接下来说一下具体步骤.

正文(具体步骤)

1.在线生成密钥对

采用在线工具生成密钥对, 私钥密码可填可不填, 网址:http://web.chacuo.net/netrsakeypair 截图中的密钥对是写博客时重新生成的, 和代码中的不一样不要见怪~

生成密钥对备用.

2.后台签名

Controller层java代码

privateAjaxJson getAjaxJson(HttpServletRequest req, HttpServletResponse res) {

AjaxJson j= newAjaxJson();

String passresStr= GetRSAStr.getResStr(true);//pass明文

j.setRsaStr(passresStr);//明文

try{

j.setSign(RSAEnDeUtils.sign(passresStr, RSAEnDeUtils.getPrivateKey(RSAEnDeUtils.getPrivateKey())));//pass签名

} catch(Exception e) {

e.printStackTrace();

}//============================client判断开始============================

String sessionCounterStr =sysConfigService.queryConfValueByConfId(SysParamConfig.forSecurityTest.SESSION_COUNTER.getParam());

BigInteger counterParam= newBigInteger(sessionCounterStr);int clientCount =clientManager.getAllClient().size();

BigInteger clients= newBigInteger(String.valueOf(clientCount));if (clients.compareTo(counterParam) >= 0) {

j.setSuccess(false);

j.setRsaStr(GetRSAStr.getResStr(false));//notpass明文

j.setMsg("系统已达最大会话数!");returnj;

}

......

简单说一下这段代码逻辑, 这是校验登录用户用户名密码的其中一段代码.(关键代码已加粗)

思路就是进入该方法时, 把将要返回给前台的结果ajaxJson中首先设置两个参数, 一个属性名为rsaStr, 另一个属性名为sign.

其中rsaStr用于存放随机生成的uuid, sign用于存放由该uuid和第1步的私钥生成的签名结果.

当程序中遇到用户校验不通过时生成另一个uuid替换上面的rsaStr的值(sign并不替换), 由ajaxJson一并返回给前台.

后台关键代码(两个工具类)

签名用到的工具类RSAEnDeUtils.java和生成uuid的工具类GetRSAStr.java如下:

RSAEnDeUtils.java

packageorg.jeecgframework.web.system.util;importorg.apache.commons.codec.binary.Base64;importjavax.crypto.Cipher;importjava.io.ByteArrayOutputStream;import java.security.*;importjava.security.spec.PKCS8EncodedKeySpec;importjava.security.spec.X509EncodedKeySpec;importjava.util.UUID;public classRSAEnDeUtils {//私钥

private static final String PRIVATE_KEY = "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAMlEZXt7J32l4s84ioWDeiKidaqmauNWKTbDInNaq/yK3fIC+j+jg5HjTJutk8ernbqTqeC+oc4I0m+Gs3vBc1QQhP49fIu7B9Y/TgjgQMFLcGfctxMwCcZWgiRrR/k7qWjcjRi09bCfKFxCGsda5OJ60YLQI3C54jXUm6rw1XafAgMBAAECgYAii9PjcwsfPQcGTI0yR5QCN+J8jR4RsWtXk/zo0eptaaSY8rvjinx94Qb4Pb386s8jBE+HXRFG3SrJq9RI7LaPrGjU3qbURTExr9qRo9//eR9VahCKyftryRkeXGqBcOreDgbiTb6wYzUL9OdgSV4to4hz7oIBmnal3+oy5grpIQJBAOgQwoMgAQfjfDSeBcXRklLestWvHRxLu3mpgcvcqHWmeH6HdSxBidJlu0U14QkruvxOZAeW0Y4iu20LY0JKZY8CQQDeBneXmEJr1Pnd/GAUo61i9xpKJOmGmFaiM78DE+JYFdnim+wdye1z/u7GPuD6HmcQC3kb7zpSRVSdOWsnxvXxAkEAhJBWXMsia5wybmg6ifcebAJVDCW9LlXAoU4IHClPfe17dWPxtjc2AJ8ma/HMPA3kAY7SK1enG1eR00enCs4u1wJBAJitY9H4Xzyd0VGIul2XDKVwfUCdT4VB/tk9sk2gf9bI9/Mv+9ekQ0iv92yWUslM3NyYtyixgq6OhJg1ou1QkVECQB3Vu4KvKafP5ejMPe3XplyDI20HJbHlAWH5NGZ67oRWLsVnKAIyLxZRhF4LPXew3gC9BVFCw8zj1geO42oOAso=";public staticString getPrivateKey() {returnPRIVATE_KEY;

}/*** RSA最大加密明文大小*/

private static final int MAX_ENCRYPT_BLOCK = 117;/*** RSA最大解密密文大小*/

private static final int MAX_DECRYPT_BLOCK = 128;/*** 获取密钥对

*

*@return密钥对*/

public static KeyPair getKeyPair() throwsException {

KeyPairGenerator generator= KeyPairGenerator.getInstance("RSA");

generator.initialize(1024);returngenerator.generateKeyPair();

}/*** 获取私钥

*

*@paramprivateKey 私钥字符串

*@return

*/

public static PrivateKey getPrivateKey(String privateKey) throwsException {

KeyFactory keyFactory= KeyFactory.getInstance("RSA");byte[] decodedKey =Base64.decodeBase64(privateKey.getBytes());

PKCS8EncodedKeySpec keySpec= newPKCS8EncodedKeySpec(decodedKey);returnkeyFactory.generatePrivate(keySpec);

}/*** 获取公钥

*

*@parampublicKey 公钥字符串

*@return

*/

public static PublicKey getPublicKey(String publicKey) throwsException {

KeyFactory keyFactory= KeyFactory.getInstance("RSA");byte[] decodedKey =Base64.decodeBase64(publicKey.getBytes());

X509EncodedKeySpec keySpec= newX509EncodedKeySpec(decodedKey);returnkeyFactory.generatePublic(keySpec);

}/*** RSA加密

*

*@paramdata 待加密数据

*@parampublicKey 公钥

*@return

*/

public static String encrypt(String data, PublicKey publicKey) throwsException {

Cipher cipher= Cipher.getInstance("RSA");

cipher.init(Cipher.ENCRYPT_MODE, publicKey);int inputLen =data.getBytes().length;

ByteArrayOutputStream out= newByteArrayOutputStream();int offset = 0;byte[] cache;int i = 0;//对数据分段加密

while (inputLen - offset > 0) {if (inputLen - offset >MAX_ENCRYPT_BLOCK) {

cache=cipher.doFinal(data.getBytes(), offset, MAX_ENCRYPT_BLOCK);

}else{

cache= cipher.doFinal(data.getBytes(), offset, inputLen -offset);

}

out.write(cache,0, cache.length);

i++;

offset= i *MAX_ENCRYPT_BLOCK;

}byte[] encryptedData =out.toByteArray();

out.close();//获取加密内容使用base64进行编码,并以UTF-8为标准转化成字符串//加密后的字符串

return newString(Base64.encodeBase64String(encryptedData));

}/*** RSA解密

*

*@paramdata 待解密数据

*@paramprivateKey 私钥

*@return

*/

public static String decrypt(String data, PrivateKey privateKey) throwsException {

Cipher cipher= Cipher.getInstance("RSA");

cipher.init(Cipher.DECRYPT_MODE, privateKey);byte[] dataBytes =Base64.decodeBase64(data);int inputLen =dataBytes.length;

ByteArrayOutputStream out= newByteArrayOutputStream();int offset = 0;byte[] cache;int i = 0;//对数据分段解密

while (inputLen - offset > 0) {if (inputLen - offset >MAX_DECRYPT_BLOCK) {

cache=cipher.doFinal(dataBytes, offset, MAX_DECRYPT_BLOCK);

}else{

cache= cipher.doFinal(dataBytes, offset, inputLen -offset);

}

out.write(cache,0, cache.length);

i++;

offset= i *MAX_DECRYPT_BLOCK;

}byte[] decryptedData =out.toByteArray();

out.close();//解密后的内容

return new String(decryptedData, "UTF-8");

}/*** 签名

*

*@paramdata 待签名数据

*@paramprivateKey 私钥

*@return签名*/

public static String sign(String data, PrivateKey privateKey) throwsException {byte[] keyBytes =privateKey.getEncoded();

PKCS8EncodedKeySpec keySpec= newPKCS8EncodedKeySpec(keyBytes);

KeyFactory keyFactory= KeyFactory.getInstance("RSA");

PrivateKey key=keyFactory.generatePrivate(keySpec);

Signature signature= Signature.getInstance("MD5withRSA");

signature.initSign(key);

signature.update(data.getBytes());return newString(Base64.encodeBase64(signature.sign()));

}/*** 验签

*

*@paramsrcData 原始字符串

*@parampublicKey 公钥

*@paramsign 签名

*@return是否验签通过*/

public static boolean verify(String srcData, PublicKey publicKey, String sign) throwsException {byte[] keyBytes =publicKey.getEncoded();

X509EncodedKeySpec keySpec= newX509EncodedKeySpec(keyBytes);

KeyFactory keyFactory= KeyFactory.getInstance("RSA");

PublicKey key=keyFactory.generatePublic(keySpec);

Signature signature= Signature.getInstance("MD5withRSA");

signature.initVerify(key);

signature.update(srcData.getBytes());returnsignature.verify(Base64.decodeBase64(sign.getBytes()));

}public static voidmain(String[] args) {try{//生成密钥对//KeyPair keyPair = getKeyPair();//String privateKey = new String(Base64.encodeBase64(keyPair.getPrivate().getEncoded()));//String publicKey = new String(Base64.encodeBase64(keyPair.getPublic().getEncoded()));

String privateKey = "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAMlEZXt7J32l4s84ioWDeiKidaqmauNWKTbDInNaq/yK3fIC+j+jg5HjTJutk8ernbqTqeC+oc4I0m+Gs3vBc1QQhP49fIu7B9Y/TgjgQMFLcGfctxMwCcZWgiRrR/k7qWjcjRi09bCfKFxCGsda5OJ60YLQI3C54jXUm6rw1XafAgMBAAECgYAii9PjcwsfPQcGTI0yR5QCN+J8jR4RsWtXk/zo0eptaaSY8rvjinx94Qb4Pb386s8jBE+HXRFG3SrJq9RI7LaPrGjU3qbURTExr9qRo9//eR9VahCKyftryRkeXGqBcOreDgbiTb6wYzUL9OdgSV4to4hz7oIBmnal3+oy5grpIQJBAOgQwoMgAQfjfDSeBcXRklLestWvHRxLu3mpgcvcqHWmeH6HdSxBidJlu0U14QkruvxOZAeW0Y4iu20LY0JKZY8CQQDeBneXmEJr1Pnd/GAUo61i9xpKJOmGmFaiM78DE+JYFdnim+wdye1z/u7GPuD6HmcQC3kb7zpSRVSdOWsnxvXxAkEAhJBWXMsia5wybmg6ifcebAJVDCW9LlXAoU4IHClPfe17dWPxtjc2AJ8ma/HMPA3kAY7SK1enG1eR00enCs4u1wJBAJitY9H4Xzyd0VGIul2XDKVwfUCdT4VB/tk9sk2gf9bI9/Mv+9ekQ0iv92yWUslM3NyYtyixgq6OhJg1ou1QkVECQB3Vu4KvKafP5ejMPe3XplyDI20HJbHlAWH5NGZ67oRWLsVnKAIyLxZRhF4LPXew3gC9BVFCw8zj1geO42oOAso=";

String publicKey= "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDJRGV7eyd9peLPOIqFg3oionWqpmrjVik2wyJzWqv8it3yAvo/o4OR40ybrZPHq526k6ngvqHOCNJvhrN7wXNUEIT+PXyLuwfWP04I4EDBS3Bn3LcTMAnGVoIka0f5O6lo3I0YtPWwnyhcQhrHWuTietGC0CNwueI11Juq8NV2nwIDAQAB";

System.out.println("私钥:" +privateKey);

System.out.println("公钥:" +publicKey);//RSA加密//String data = "123456";

UUID uuid =UUID.randomUUID();

String data= uuid.toString();//.toString().replace("-","")

String encryptData =encrypt(data, getPublicKey(publicKey));

System.out.println("加密后内容:" +encryptData);//RSA解密

String decryptData =decrypt(encryptData, getPrivateKey(privateKey));

System.out.println("解密后内容:" +decryptData);//RSA签名

String sign =sign(data, getPrivateKey(privateKey));

System.out.println("签名:" +sign);//RSA验签

boolean result =verify(data, getPublicKey(publicKey), sign);

System.out.print("验签结果:" +result);

}catch(Exception e) {

e.printStackTrace();

System.out.print("加解密异常");

}

}

}

GetRSAStr.java(忽略备注释的代码吧, 那是RSA之前想到的方案, 不可行)

packageorg.jeecgframework.web.system.util;importjava.io.UnsupportedEncodingException;importjava.util.UUID;public classGetRSAStr {public static String getResStr(booleanb) {

String resStr= "";if(b) {

UUID uuid=UUID.randomUUID();

String passuuidStr= uuid.toString().replace("-", "");

resStr=passuuidStr;

}else{

UUID uuid=UUID.randomUUID();

String notpassuuidStr= uuid.toString().replace("-", "");

resStr=notpassuuidStr;

}/*java.util.Base64.Encoder encoder = java.util.Base64.getEncoder();

final byte[] textByte;

try {

textByte = resStr.getBytes("UTF-8");

String encodedText = encoder.encodeToString(textByte);

resStr = new StringBuilder(encodedText).reverse().toString();

} catch (UnsupportedEncodingException e) {

e.printStackTrace();

}*/

returnresStr;

}

}

3.前台验签

jsp中js部分

$.ajax({

async:false,

cache:false,

type:'POST',

url: checkurl,//请求的action路径

data: formData,

error:function () {//请求失败处理函数

},

success:function(data) {var d =$.parseJSON(data);var success =d.success;//验证签名start===========

var rsaStr = d.rsaStr;//明文

var sign = d.sign;//签名

const rsaverify = RSA_VERIFY_SIGN(publicKey, rsaStr, sign);//验签结果

//验证签名end===========

if(rsaverify) {

window.location.href=actionurl;

}else{

showErrorMsg(d.msg);

......

这段就是登陆方法的其中一段代码, 忽略这坨翔吧, 看一下思路, 公钥也即是publicKey是定义在js文件中的全局变量, rsaStr和sign都是由后台响应数据data中获取的, 在采用rsa之前if else判断条件是用的var success = d.success;中success的结果, 由于该结果有可能被篡改, 才采用了现在的验签结果rsaverify作为判断依据.

前台关键代码(js文件)

验签用到的js文件如下, rsaverify.js和jsrsasign-all-min.js这两个文件, 在jsp中引入即可.

rsaverify.js

const ALGORITHM = 'MD5withRSA';/**

* 私钥签名

* rsa 用 MD5withRSA 算法签名

* @param privateKey 私钥

* @param src 明文

* @return {*}

* @constructor*/const RSA_SIGN= (privateKey, src) =>{

const signature= new KJUR.crypto.Signature({'alg': ALGORITHM});

const priKey= KEYUTIL.getKey(privateKey); //因为后端提供的是pck#8的密钥对,所以这里使用 KEYUTIL.getKey来解析密钥

signature.init(priKey); //初始化实例

signature.updateString(src); //传入待签明文

const a = signature.sign(); //签名, 得到16进制字符结果

return hex2b64(a) //转换成base64

};/**

* 公钥验签

* @param publicKey 公钥

* @param src 明文

* @param data 经过私钥签名并且转换成base64的结果

* @return {Boolean} 是否验签成功

* @constructor*/const RSA_VERIFY_SIGN= (publicKey, src, data) =>{

const signature= new KJUR.crypto.Signature({'alg': ALGORITHM, 'prvkeypem': publicKey});

signature.updateString(src);//传入待签明文

returnsignature.verify(b64tohex(data))

};

const publicKey= '-----BEGIN PUBLIC KEY-----\n' +

'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDJRGV7eyd9peLPOIqFg3oionWq\n' +

'pmrjVik2wyJzWqv8it3yAvo/o4OR40ybrZPHq526k6ngvqHOCNJvhrN7wXNUEIT+\n' +

'PXyLuwfWP04I4EDBS3Bn3LcTMAnGVoIka0f5O6lo3I0YtPWwnyhcQhrHWuTietGC\n' +

'0CNwueI11Juq8NV2nwIDAQAB\n' +

'-----END PUBLIC KEY-----';

jsrsasign-all-min.js库可以从GitHub上下载, 地址:https://github.com/kjur/jsrsasign, 也可以直接拷贝下面代码.只不过我为了便于观看代码把min代码格式化了一下(算了, 格式化后一万两千多行还是自行下载吧).

其他的也没什么可说的了, 网上关于前台加密后台解密的很多, 后台签名前台验签的寥寥无几, 查看的资料很多不一一列举了, 在这里感谢一下

关于后台加密前台解密的, 如果有需要可以参考我的另一片文章:

传输以及存储的保密性和完整性(补充)

关于登录过程中涉及到的传输保密性及完整性以及存储保密性和完整性描述:

前后台用到的关键文件可参考我另一篇博客, 这里只作描述, 不作展开, 详细参考:

感谢

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值