RSA后台签名前台验签的应⽤(前台采⽤jsrsasign库)
写在前⾯
安全测试需要, 为防⽌后台响应数据返给前台过程中被篡改前台再拿被篡改后的数据进⾏接下来的操作影响正常业务, 决定采⽤RSA对响应数据进⾏签名和验签, 于是有了这篇
<RSA后台签名前台验签的应⽤>.
我这⾥所谓的返给前台的数据只是想加密⽤户验证通过与否的字段success是true还是false, 前台拿这个success作为判断依据进⾏下⼀步的操作, 是进⼀步向后台发起请求还是直
接弹出错误消息.照测试结果看这是个逻辑漏洞, 即使后台返回的是false, 在返回前台的过程中响应包被劫获, 将false改为true, 这样的操作也是能做到的(BurpSuit). 所以后台响应数
据尽量不要再⼆次使⽤. 那既然能篡改, 如何防⽌流氓篡改呢?
说⼀下整体思路: ⾸先⽣成密钥对, 私钥存放在后台⽤于签名, 公钥存放在前台⽤于验签. 所谓签名就是指拿明⽂+私钥⽣成的签名结果, 返回数据给前台时将明⽂+签名结果⼀并返
给前台, 前台⽤公钥+接收到的明⽂+签名结果进⾏验签, 这样即使响应包被劫获, 篡改明⽂后, 验证签名时也不会验证通过, 从⽽达到响应数据防篡改的⽬的.
接下来说⼀下具体步骤.
正⽂(具体步骤)
1.在线⽣成密钥对
⽣成密钥对备⽤.
2.后台签名
Controller层java代码
private AjaxJson getAjaxJson(HttpServletRequest req, HttpServletResponse res) {
AjaxJson j = new AjaxJson();
String passresStr = ResStr(true);// pass明⽂
j.setRsaStr(passresStr);// 明⽂
try {
j.setSign(RSAEnDeUtils.sign(passresStr, PrivateKey())));// pass签名
} catch (Exception e) {
e.printStackTrace();
}
//============================client判断开始============================
String sessionCounterStr = sysConfigService.queryConfValueByConfId(SysParamConfig.forSecurityTest.Param());
BigInteger counterParam = new BigInteger(sessionCounterStr);
int clientCount = AllClient().size();
BigInteger clients = new BigInteger(String.valueOf(clientCount));
if (clientspareTo(counterParam) >= 0) {
j.setSuccess(false);
j.ResStr(false));// notpass明⽂
j.setMsg("系统已达最⼤会话数!");
return j;
}
......
简单说⼀下这段代码逻辑, 这是校验登录⽤户⽤户名密码的其中⼀段代码.(关键代码已加粗)
思路就是进⼊该⽅法时, 把将要返回给前台的结果ajaxJson中⾸先设置两个参数, ⼀个属性名为rsaStr, 另⼀个属性名为sign.
其中rsaStr⽤于存放随机⽣成的uuid, sign⽤于存放由该uuid和第1步的私钥⽣成的签名结果.
当程序中遇到⽤户校验不通过时⽣成另⼀个uuid替换上⾯的rsaStr的值(sign并不替换), 由ajaxJson⼀并返回给前台.
后台关键代码(两个⼯具类)
签名⽤到的⼯具类RSAEnDeUtils.java和⽣成uuid的⼯具类GetRSAStr.java如下:
RSAEnDeUtils.java
package org.jeecgframework.web.system.util;
import dec.binary.Base64;
pto.Cipher;
import java.io.ByteArrayOutputStream;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.UUID;
public class RSAEnDeUtils {
// 私钥
private static final String PRIVATE_KEY = "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAMlEZXt7J32l4s84ioWDeiKidaqmauNWKTbDInNaq/yK3fIC+j+jg5HjTJutk8ernbqTqeC+oc4I0m+Gs3vBc1QQhP49fIu7B9Y/TgjgQM public static String getPrivateKey() {
return PRIVATE_KEY;
}
/**
* RSA最⼤加密明⽂⼤⼩
*/
private static final int MAX_ENCRYPT_BLOCK = 117;
/**
* RSA最⼤解密密⽂⼤⼩
*/
private static final int MAX_DECRYPT_BLOCK = 128;
/**
* 获取密钥对
*
* @return密钥对
*/
public static KeyPair getKeyPair() throws Exception {
KeyPairGenerator generator = Instance("RSA");
generator.initialize(1024);
ateKeyPair();
}
/**
* 获取私钥
*
* @param privateKey 私钥字符串
* @return
*/
public static PrivateKey getPrivateKey(String privateKey) throws Exception {
KeyFactory keyFactory = Instance("RSA");
byte[] decodedKey = Base64.Bytes());
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decodedKey);
atePrivate(keySpec);
}
/**
* 获取公钥
*
* @param publicKey 公钥字符串
* @return
*/
public static PublicKey getPublicKey(String publicKey) throws Exception {
KeyFactory keyFactory = Instance("RSA");
byte[] decodedKey = Base64.Bytes());
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(decodedKey);
atePublic(keySpec);
}
/**
* RSA加密
*
* @param data      待加密数据
* @param publicKey 公钥
* @return
*/
public static String encrypt(String data, PublicKey publicKey) throws Exception {
Cipher cipher = Instance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
int inputLen = Bytes().length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offset = 0;
byte[] cache;
int i = 0;
// 对数据分段加密
while (inputLen - offset > 0) {
if (inputLen - offset > MAX_ENCRYPT_BLOCK) {
cache = cipher.Bytes(), offset, MAX_ENCRYPT_BLOCK);
} else {
cache = cipher.Bytes(), offset, inputLen - offset);
}
out.write(cache, 0, cache.length);
i++;
offset = i * MAX_ENCRYPT_BLOCK;
}
byte[] encryptedData = ByteArray();
out.close();
/
/ 获取加密内容使⽤base64进⾏编码,并以UTF-8为标准转化成字符串
// 加密后的字符串
return new deBase64String(encryptedData));
}
/**
* RSA解密
*
* @param data      待解密数据
* @param privateKey 私钥
* @return
*/
public static String decrypt(String data, PrivateKey privateKey) throws Exception {
Cipher cipher = Instance("RSA");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] dataBytes = Base64.decodeBase64(data);
int inputLen = dataBytes.length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
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 = ByteArray();
out.close();
/
/ 解密后的内容
return new String(decryptedData, "UTF-8");
}
/**
* 签名
*
* @param data      待签名数据
* @param privateKey 私钥
* @return签名
*/
public static String sign(String data, PrivateKey privateKey) throws Exception {
byte[] keyBytes = Encoded();
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = Instance("RSA");
PrivateKey key = atePrivate(keySpec);
Signature signature = Instance("MD5withRSA");
signature.initSign(key);
signature.Bytes());
return new deBase64(signature.sign()));
}
/**
* 验签
*
* @param srcData  原始字符串
* @param publicKey 公钥
* @param sign      签名
* @return是否验签通过
*/
public static boolean verify(String srcData, PublicKey publicKey, String sign) throws Exception { byte[] keyBytes = Encoded();
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = Instance("RSA");
PublicKey key = atePublic(keySpec);
Signature signature = Instance("MD5withRSA");
signature.initVerify(key);
signature.Bytes());
return signature.verify(Base64.Bytes()));
}
public static void main(String[] args) {
try {
// ⽣成密钥对
//            KeyPair keyPair = getKeyPair();
//            String privateKey = new Private().getEncoded()));js获取json的key和value
//            String publicKey = new Public().getEncoded()));
String privateKey = "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAMlEZXt7J3
2l4s84ioWDeiKidaqmauNWKTbDInNaq/yK3fIC+j+jg5HjTJutk8ernbqTqeC+oc4I0m+Gs3vBc1QQhP49fIu7B9Y/TgjgQMFLcGfctxMwCcZW            String publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDJRGV7eyd9peLPOIqFg3oionWqpmrjVik2wyJzWqv8it3yAvo/o4OR40ybrZPHq526k6ngvqHOCNJvhrN7wXNUEIT+PXyLuwfWP04I4EDBS3Bn3LcTMAnGVoIka0f            System.out.println("私钥:" + privateKey);
System.out.println("公钥:" + publicKey);
// RSA加密
//            String data = "123456";
UUID uuid = UUID.randomUUID();
String data = String();//.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之前想到的⽅案, 不可⾏)
package org.jeecgframework.web.system.util;
import java.io.UnsupportedEncodingException;
import java.util.UUID;
public class GetRSAStr {
public static String getResStr(boolean b) {
String resStr = "";
if (b) {
UUID uuid = UUID.randomUUID();
String passuuidStr = String().replace("-", "");
resStr = passuuidStr;
} else {
UUID uuid = UUID.randomUUID();
String notpassuuidStr = String().replace("-", "");
resStr = notpassuuidStr;
}
/*java.util.Base64.Encoder encoder = java.Encoder();
final byte[] textByte;
try {
textByte = Bytes("UTF-8");
String encodedText = deToString(textByte);
resStr = new StringBuilder(encodedText).reverse().toString();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}*/
return resStr;
}
}
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中引⼊即可.
  <%--逻辑漏洞修复,需引⼊下⾯js⽂件--%>
<script src="<%=basePath%>/plug-in/ace/js/rsaverify.js"></script>
<script src="<%=basePath%>/plug-in/ace/js/jsrsasign-all-min.js"></script>
rsaverify.js
const ALGORITHM = 'MD5withRSA';
/**
* 私钥签名
* rsa ⽤ MD5withRSA 算法签名
* @param privateKey 私钥
* @param src 明⽂
* @return {*}
* @constructor
*/
const RSA_SIGN = (privateKey, src) => {
const signature = pto.Signature({'alg': ALGORITHM});
const priKey = Key(privateKey); // 因为后端提供的是pck#8的密钥对,所以这⾥使⽤ Key来解析密钥    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 = pto.Signature({'alg': ALGORITHM, 'prvkeypem': publicKey});
signature.updateString(src); // 传⼊待签明⽂
return signature.verify(b64tohex(data))
};
const publicKey = '-----BEGIN PUBLIC KEY-----\n' +
'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDJRGV7eyd9peLPOIqFg3oionWq\n' +
'pmrjVik2wyJzWqv8it3yAvo/o4OR40ybrZPHq526k6ngvqHOCNJvhrN7wXNUEIT+\n' +
'PXyLuwfWP04I4EDBS3Bn3LcTMAnGVoIka0f5O6lo3I0YtPWwnyhcQhrHWuTietGC\n' +
'0CNwueI11Juq8NV2nwIDAQAB\n' +
'-----END PUBLIC KEY-----';
关于后台加密前台解密的, 如果有需要可以参考我的另⼀⽚⽂章:
传输以及存储的保密性和完整性(补充)
关于登录过程中涉及到的传输保密性及完整性以及存储保密性和完整性描述:
前后台⽤到的关键⽂件可参考我另⼀篇博客, 这⾥只作描述, 不作展开, 详细参考:.
感谢

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。