• 使用 RSA 非对称加密保证数据不被篡改 java 例子代码


    原理: 对原始数据 生成有序的json 字符串,然后取 摘要,然后 对摘要 进项 分对称加密。( 不对原数据加密是应为 原数据太大,加解密速度太慢,非对称加密都不 挺慢的。在摘要函数具有雪崩效应 ,原文发生点点的改变都会引起 摘要的剧烈变化 )

    注意事项:因为使用的 对json 排序。而不是 传统的 from 表单方式。虽然 让请求响应都支持了json 变得统一,但是,这里又一个明显的容易缺陷。json 的 value 中 字符串带有 引号,数字类型没有引号。所以 这可能 应为 数据类型不一样造成验签失败。所以应该 全部使用 字符串。如果都有 引号就没有这种问题了。

    备注:这个适用于 2个 系统之间 的数据交流。2 系统之间的数据交流最明显的特点就是 没有登录, 请求是无状态的,只能 通过请求参数来识别 请求是否合法。 这个 方式安全的 大前提 是自己的私钥 永远只有自己知道。

    备注2: 这种方式侧重是数据不被篡改,而不是 保证数据不被泄露。 如果需要保证数据不被泄露 ,那么 发送请求和响应数据 的时候应该使用对方的公钥加密,对方收到请求以后使用自己的私钥解密。而且 加密的 原文 应该是整个 原始字符串,而不是 原文的摘要。

     目录结构:

    RSAUtils.java: 生成公私玥,加密解密 的工具类

    package com.sbl.ebuygou.trading.biz.sign;
    import org.apache.commons.codec.binary.Base64;
    import org.apache.commons.io.IOUtils;
    
    import javax.crypto.Cipher;
    import java.io.ByteArrayOutputStream;
    import java.security.*;
    import java.security.interfaces.RSAPrivateKey;
    import java.security.interfaces.RSAPublicKey;
    import java.security.spec.InvalidKeySpecException;
    import java.security.spec.PKCS8EncodedKeySpec;
    import java.security.spec.X509EncodedKeySpec;
    import java.util.HashMap;
    import java.util.Map;
     
    public class RSAUtils {
     
        public static final String CHARSET = "UTF-8";
        public static final String RSA_ALGORITHM = "RSA";
     
     
        public static Map<String, String> createKeys(int keySize){
            //为RSA算法创建一个KeyPairGenerator对象
            KeyPairGenerator kpg;
            try{
                kpg = KeyPairGenerator.getInstance(RSA_ALGORITHM);
            }catch(NoSuchAlgorithmException e){
                throw new IllegalArgumentException("No such algorithm-->[" + RSA_ALGORITHM + "]");
            }
     
            //初始化KeyPairGenerator对象,密钥长度
            kpg.initialize(keySize);
            //生成密匙对
            KeyPair keyPair = kpg.generateKeyPair();
            //得到公钥
            Key publicKey = keyPair.getPublic();
            String publicKeyStr = Base64.encodeBase64URLSafeString(publicKey.getEncoded());
            //得到私钥
            Key privateKey = keyPair.getPrivate();
            String privateKeyStr = Base64.encodeBase64URLSafeString(privateKey.getEncoded());
            Map<String, String> keyPairMap = new HashMap<String, String>();
            keyPairMap.put("publicKey", publicKeyStr);
            keyPairMap.put("privateKey", privateKeyStr);
     
            return keyPairMap;
        }
     
        /**
         * 得到公钥
         * @param publicKey 密钥字符串(经过base64编码)
         * @throws Exception
         */
        public static RSAPublicKey getPublicKey(String publicKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
            //通过X509编码的Key指令获得公钥对象
            KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
            X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKey));
            RSAPublicKey key = (RSAPublicKey) keyFactory.generatePublic(x509KeySpec);
            return key;
        }
     
        /**
         * 得到私钥
         * @param privateKey 密钥字符串(经过base64编码)
         * @throws Exception
         */
        public static RSAPrivateKey getPrivateKey(String privateKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
            //通过PKCS#8编码的Key指令获得私钥对象
            KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
            PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKey));
            RSAPrivateKey key = (RSAPrivateKey) keyFactory.generatePrivate(pkcs8KeySpec);
            return key;
        }
     
        /**
         * 公钥加密
         * @param data
         * @param publicKey
         * @return
         */
        public static String publicEncrypt(String data, RSAPublicKey publicKey){
            try{
                Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
                cipher.init(Cipher.ENCRYPT_MODE, publicKey);
                return Base64.encodeBase64URLSafeString(rsaSplitCodec(cipher, Cipher.ENCRYPT_MODE, data.getBytes(CHARSET), publicKey.getModulus().bitLength()));
            }catch(Exception e){
                throw new RuntimeException("加密字符串[" + data + "]时遇到异常", e);
            }
        }
     
        /**
         * 私钥解密
         * @param data
         * @param privateKey
         * @return
         */
     
        public static String privateDecrypt(String data, RSAPrivateKey privateKey){
            try{
                Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
                cipher.init(Cipher.DECRYPT_MODE, privateKey);
                return new String(rsaSplitCodec(cipher, Cipher.DECRYPT_MODE, Base64.decodeBase64(data), privateKey.getModulus().bitLength()), CHARSET);
            }catch(Exception e){
                throw new RuntimeException("解密字符串[" + data + "]时遇到异常", e);
            }
        }
     
        /**
         * 私钥加密
         * @param data
         * @param privateKey
         * @return
         */
     
        public static String privateEncrypt(String data, RSAPrivateKey privateKey){
            try{
                Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
                cipher.init(Cipher.ENCRYPT_MODE, privateKey);
                return Base64.encodeBase64URLSafeString(rsaSplitCodec(cipher, Cipher.ENCRYPT_MODE, data.getBytes(CHARSET), privateKey.getModulus().bitLength()));
            }catch(Exception e){
                throw new RuntimeException("加密字符串[" + data + "]时遇到异常", e);
            }
        }
     
        /**
         * 公钥解密
         * @param data
         * @param publicKey
         * @return
         */
     
        public static String publicDecrypt(String data, RSAPublicKey publicKey){
            try{
                Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
                cipher.init(Cipher.DECRYPT_MODE, publicKey);
                return new String(rsaSplitCodec(cipher, Cipher.DECRYPT_MODE, Base64.decodeBase64(data), publicKey.getModulus().bitLength()), CHARSET);
            }catch(Exception e){
                throw new RuntimeException("解密字符串[" + data + "]时遇到异常", e);
            }
        }
     
        private static byte[] rsaSplitCodec(Cipher cipher, int opmode, byte[] datas, int keySize){
            int maxBlock = 0;
            if(opmode == Cipher.DECRYPT_MODE){
                maxBlock = keySize / 8;
            }else{
                maxBlock = keySize / 8 - 11;
            }
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            int offSet = 0;
            byte[] buff;
            int i = 0;
            try{
                while(datas.length > offSet){
                    if(datas.length-offSet > maxBlock){
                        buff = cipher.doFinal(datas, offSet, maxBlock);
                    }else{
                        buff = cipher.doFinal(datas, offSet, datas.length-offSet);
                    }
                    out.write(buff, 0, buff.length);
                    i++;
                    offSet = i * maxBlock;
                }
            }catch(Exception e){
                throw new RuntimeException("加解密阀值为["+maxBlock+"]的数据时发生异常", e);
            }
            byte[] resultDatas = out.toByteArray();
            IOUtils.closeQuietly(out);
            return resultDatas;
        }
     
    }
    

      

    SignUtil.java : 签名和核心 工具类

    package com.sbl.ebuygou.trading.biz.sign;
    
    import java.security.NoSuchAlgorithmException;
    import java.security.interfaces.RSAPrivateKey;
    import java.security.interfaces.RSAPublicKey;
    import java.security.spec.InvalidKeySpecException;
    
    import org.apache.commons.codec.digest.DigestUtils;
    
    import com.alibaba.fastjson.JSONObject;
    import com.alibaba.fastjson.TypeReference;
    import com.alibaba.fastjson.serializer.SerializerFeature;
    import com.sbl.ebuygou.trading.biz.sign.pram.QueryOrderReq;
    
    /**
     * 签名工具
     * 
     * @author ZHANGYUKUN
     *
     */
    public class SignUtil {
    
    	/**
    	 * 生成签名
    	 * 
    	 * @param sortData
    	 *            原始加密字符串
    	 * @param privateKeyStr
    	 *            私钥字符串
    	 * @return 加密后的 对象
    	 */
    	public static <T extends SignParam> T getSignObject(T signParam, String privateKeyStr) {
    		String sign = getSign(signParam, privateKeyStr);
    		signParam.setSign(sign);
    
    		return signParam;
    	}
    
    	/**
    	 * 生成签名
    	 * 
    	 * @param sortData
    	 *            原始加密字符串
    	 * @param privateKeyStr
    	 *            私钥字符串
    	 * @return 加密后的 sign字符串
    	 */
    	public static <T extends SignParam> String getSign(T signParam, String privateKeyStr) {
    		String sortData = getSortData(signParam);
    
    		RSAPrivateKey privateKey = null;
    		try {
    			privateKey = RSAUtils.getPrivateKey(privateKeyStr);
    		} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
    			e.printStackTrace();
    		}
    
    		String sortDataMd5 = DigestUtils.md5Hex(sortData);
    
    		return RSAUtils.privateEncrypt(sortDataMd5, privateKey);
    	}
    
    	/**
    	 * 核对签名
    	 * 
    	 * @param resultStr
    	 *            带有 sign 的json 字符串
    	 * @param publicKeyStr
    	 *            公钥字符串
    	 * @return
    	 */
    	public static <T extends SignParam > boolean checkSign(String resultStr,String publicKeyStr , TypeReference< T >  typeReference) {
    		Content content = SignUtil.getSortData(resultStr,  typeReference );
    		return checkSign(content.getSortData(), content.getSign(),publicKeyStr);
    	}
    
    	/**
    	 * 核对签名
    	 * 
    	 * @param sortData
    	 *            原始加密字符串
    	 * @param sige
    	 *            签名字符串
    	 * @param publicKeyStr
    	 *            公钥字符串
    	 * 
    	 * @return true 验签成功 ,false 验签失败
    	 */
    	public static boolean checkSign(String sortData, String sige, String publicKeyStr) {
    		RSAPublicKey publicKey = null;
    		try {
    			publicKey = RSAUtils.getPublicKey(publicKeyStr);
    		} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
    			e.printStackTrace();
    		}
    		String data = null;
    		try {
    			data = RSAUtils.publicDecrypt(sige, publicKey);
    		} catch (Exception e) {
    			e.printStackTrace();
    			return false;
    		}
    
    		String sortDataMd5 = DigestUtils.md5Hex(sortData);
    		if (data.equals(sortDataMd5)) {
    			return true;
    		}
    		return false;
    	}
    
    	/**
    	 * 得到排序的 用于签名的 原始 json 字符串
    	 * 
    	 * @param signParam
    	 *            可以签名的对象
    	 * @return 排序了的 原始 加密字符串
    	 */
    	public static <T extends SignParam> String getSortData(T signParam) {
    		signParam.setSign(null);
    		return JSONObject.toJSONString(signParam, SerializerFeature.SortField);
    	}
    
    	/**
    	 * 得到排序的 用于签名的 原始 json 字符串
    	 * 
    	 * @param resultStr
    	 *            带有 签名的 原始字符串
    	 * @return Content 里面带有 原始签名字符串 和签名
    	 */
    	public static <T extends SignParam> Content getSortData(String resultStr, TypeReference<T> typeReference) {
    		T signResult = JSONObject.parseObject(resultStr, typeReference);
    
    		String sign = signResult.getSign();
    		signResult.setSign(null);
    		String sortData = getSortData(signResult);
    
    		Content content = new Content();
    		content.setSign(sign);
    		content.setSortData(sortData);
    
    		return content;
    	}
    
    }
    

      

    Content.java:  存放 原始签名 json 字符串和 签名 的 辅助类

    package com.sbl.ebuygou.trading.biz.sign;
    
    public class Content {
    	
    	/**
    	 * 签名
    	 */
    	private String sign;
    	
    	/**
    	 * 原始的 签名 json 字符串
    	 */
    	private String sortData;
    	
    	public String getSign() {
    		return sign;
    	}
    	public void setSign(String sign) {
    		this.sign = sign;
    	}
    	public String getSortData() {
    		return sortData;
    	}
    	public void setSortData(String sortData) {
    		this.sortData = sortData;
    	}
    
    }
    

      

    RSAKey.java: 存放 公私玥的地方( 我这里省事就只用了一组公私玥,正常应该2 对 , 各自 持有自己的私钥,公钥 ,和对方的 公钥)

    package com.sbl.ebuygou.trading.biz.sign;
    
    public class RSAKey {
    
    	public static final String publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCWLw2S_98GLHAe3ZvVCh3rdJ36A0BHwLehAK5j1378peDDOdz1oIsHyprRsGvnXh9aWZAhKemIUkimK-WtbD4VVmOsfayoSL17c_CZIL7Yd6tfSBiobbXb4m-bt0wGZRzh0L7IMpwIukzmWcyo0BNGdEe6CLDb6gvc6IOwJ_JBuQIDAQAB";
    	
    	public static final String privateKey ="MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAJYvDZL_3wYscB7dm9UKHet0nfoDQEfAt6EArmPXfvyl4MM53PWgiwfKmtGwa-deH1pZkCEp6YhSSKYr5a1sPhVWY6x9rKhIvXtz8Jkgvth3q19IGKhttdvib5u3TAZlHOHQvsgynAi6TOZZzKjQE0Z0R7oIsNvqC9zog7An8kG5AgMBAAECgYADa0bP1etp5JEY4sqtavGLbrg5_OD1wTls_Or7cuh9L_mR-DtDjEgeAUrNA0sxlT75e5iAaMfcRqNIxS8RZ6lyIfhaRlzV2L0baBzL718INpHuc5_zqxX1aFIKScQylX4YAVGwk4AG6NU9rlzNd8wHqH9P7lFQdfsc_cd72CQE0QJBAM8o_hfIUxIHdfNFpvMTkZ9-xEHlDBOaQkjWMu7WCHQ1xiNMR1stSqHLItyG3vk4N3MISAyY9L270dy3E_hR9OsCQQC5l0sZ3L9TB6tibzo2xL39jeOCA7BrrFSThF81KRAujlCgHcKGV64po6YLmjxaX5z77xJOPSyc8YGgppVjH8rrAkEAq_6Q2BYOQk3HdC9EKVT59r49G6ibmjrdBbQxnXI-mp164BuYsu6rpCEP1KB1x90QzIT3rN3hdRXXa7Tk86q3-QJAWCZcLXSEC1PhO2fJJqpb80qpfN9ztDCuG0MMVZuja_l8ohCAjH6o4m4wN-KSN_qh_aeX8kFsJz8uare0zNgU8QJAEZroY4DiiFdIxIA2S5gKDcvZlkl6ePSZzMbPw35wVrE9NqqL7l3S1TfZ9PnAhU70S2IW0ZMM0DdiAX5-zb3t_A";
    
    }
    

      

    SignParam.java:定义的统一的可以签名的对象的抽象(请求对象和响应对象都应该继承这个类 )。

    package com.sbl.ebuygou.trading.biz.sign;
    
    /**
     * 带有签名的参数
     * @author ZHANGYUKUN
     *
     */
    public abstract class SignParam {
    	
    	/**
    	 * 签名
    	 */
    	private String sign;
    	
    
    	public String getSign() {
    		return sign;
    	}
    
    	public void setSign(String sign) {
    		this.sign = sign;
    	}
    
    }
    

      

    上面5 个类是固定的。下面的类根据业务自由拓展.

    QueryOrderReq.java : 查询订单请求参数( 不同 的 请求定义不通接请求参数 )

    package com.sbl.ebuygou.trading.biz.sign.pram;
    
    import com.sbl.ebuygou.trading.biz.sign.SignParam;
    
    /**
     * 查询订单请求
     * @author ZHANGYUKUN
     *
     */
    public class QueryOrderReq extends SignParam {
    	
    	/**
    	 * 订单Id
    	 */
    	private String orderId;
    
    	public String getOrderId() {
    		return orderId;
    	}
    
    	public void setOrderId(String orderId) {
    		this.orderId = orderId;
    	}
    
    	
    
    }
    

      

    SignResult.java:带有签名的 统一返回 ,请求 没有固定格式,为了拓展,返回 固定格式便于对方解析(  备注:请求是多变的,但是响应 都是类似的。 基本都是 有个状态,有个原因 ,有个数据  )。

    package com.sbl.ebuygou.trading.biz.sign.pram;
    
    import com.sbl.ebuygou.trading.biz.sign.RSAKey;
    import com.sbl.ebuygou.trading.biz.sign.SignParam;
    import com.sbl.ebuygou.trading.biz.sign.SignUtil;
    
    /**
     * 签名的结果集
     * 
     * @author ZHANGYUKUN
     *
     * @param <T>
     */
    public class SignResult<T> extends SignParam {
    
    	/**
    	 * 状态
    	 */
    	private Status status;
    	
    	/**
    	 * 备注
    	 */
    	private String  mark;
    	
    	/**
    	 * 数据
    	 */
    	private T data;
    
    
    	public T getData() {
    		return data;
    	}
    
    	public void setData(T data) {
    		this.data = data;
    	}
    
    	public Status getStatus() {
    		return status;
    	}
    
    	public void setStatus(Status status) {
    		this.status = status;
    	}
    
    	public String getMark() {
    		return mark;
    	}
    
    	public void setMark(String mark) {
    		this.mark = mark;
    	}
    	
    	/**
    	 * 得到一个成功的结果集
    	 * 
    	 * @param data 数据
    	 * @param privateKeyStr 私钥字符串
    	 * @return
    	 */
    	public static <T> SignResult<T> getSucceedInstance(T data, String privateKeyStr ) {
    		SignResult<T> signResult = new SignResult<T>();
    		signResult.setMark("成功");
    		signResult.setStatus( Status.SUCCEE );
    		signResult.setData(data);
    		
    		signResult = SignUtil.getSignObject(signResult, RSAKey.privateKey );
    		
    		return signResult;
    	}
    	
    	
    	
    
    }
    

      

    Status.java :统一的 响应状态

    package com.sbl.ebuygou.trading.biz.sign.pram;
    
    public enum Status {
    
    	SUCCEE("成功"),FAILURE("失败"),OTHER("其他问题");
    
        private String mark;
    
        Status(String mark ) {
            this.mark = mark;
        }
    
        public String getMark() {
            return mark;
        }
    }
    

      

    测试类:OrderPublicController.java

    package com.sbl.ebuygou.trading.biz.controller;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import com.alibaba.fastjson.JSONObject;
    import com.alibaba.fastjson.TypeReference;
    import com.sbl.commons.exception.resolver.exception.ParameterErrorException;
    import com.sbl.commons.util.BeanUtils;
    import com.sbl.ebuygou.platform.api.api.SupplierServiceApi;
    import com.sbl.ebuygou.platform.api.api.UserServiceApi;
    import com.sbl.ebuygou.trading.biz.bean.in.QueryOrderItemIn;
    import com.sbl.ebuygou.trading.biz.bean.out.OrderItemOut;
    import com.sbl.ebuygou.trading.biz.bean.out.OrderOut;
    import com.sbl.ebuygou.trading.biz.entity.Order;
    import com.sbl.ebuygou.trading.biz.service.LogisticsService;
    import com.sbl.ebuygou.trading.biz.service.OrderService;
    import com.sbl.ebuygou.trading.biz.sign.RSAKey;
    import com.sbl.ebuygou.trading.biz.sign.SignUtil;
    import com.sbl.ebuygou.trading.biz.sign.pram.QueryOrderReq;
    import com.sbl.ebuygou.trading.biz.sign.pram.SignResult;
    
    import io.swagger.annotations.Api;
    import io.swagger.annotations.ApiOperation;
    
    /**
     * 订单相关接口
     * 
     * @author ZHANGYUKUN
     *
     */
    
    @Api(tags = "订单相关接口(三方调用)")
    @RestController
    @RequestMapping("public/order")
    public class OrderPublicController {
    
    	@Autowired
    	OrderService orderService;
    
    	@Autowired
    	LogisticsService logisticsService;
    
    	@Autowired
    	SupplierServiceApi supplierServiceApi;
    
    	@Autowired
    	UserServiceApi userServiceApi;
    
    	@ApiOperation("测试甲方接受乙方请求,然后验签,并且放回响应乙方签名数据")
    	@GetMapping("queryOrderByIdWithOrderItem")
    	public SignResult<OrderOut> queryByOrderId(@RequestBody QueryOrderReq queryOrderReq   ) {
    		String  resultStr = JSONObject.toJSONString( queryOrderReq );
    		
    		if(  !SignUtil.checkSign( resultStr  ,RSAKey.publicKey ,new TypeReference< QueryOrderReq >() {} ) ) {
    			throw new ParameterErrorException("验签失败");
    		}
    		
    		
    		//业务逻辑
    		long orderId = Long.valueOf( queryOrderReq.getOrderId() );
    
    		OrderOut orderOut = new OrderOut();
    		Order order = orderService.queryOrderById( orderId );
    
    		if (order == null) {
    			return null;
    		}
    
    		BeanUtils.copyProperties(order, orderOut, true);
    
    		// 关联子订单
    		QueryOrderItemIn queryOrderItemIn = new QueryOrderItemIn();
    		queryOrderItemIn.setOrderId( orderId );
    		orderOut.setOrderItem(BeanUtils.copyToOutList(orderService.queryOrderItemByOrderId(queryOrderItemIn, order.getUserId()), OrderItemOut.class));
    		
    		
    		
    		
    		return SignResult.getSucceedInstance(orderOut , RSAKey.privateKey  );
    	}
    	
    	
    	@ApiOperation("测试乙方验签")
    	@GetMapping("checkSign")
    	public boolean checkSign(String orderId) {
    		SignResult<OrderOut> signResult = t1( orderId );
    		
    		String  resultStr = JSONObject.toJSONString( signResult );
    		return SignUtil.checkSign( resultStr  , RSAKey.publicKey , new TypeReference< SignResult<OrderOut> >() {} );
    	}
    	
    	@ApiOperation("测试乙方发送 签名数据给 甲方")
    	@GetMapping("t1")
    	public  SignResult<OrderOut> t1(String orderId ) {
    		QueryOrderReq queryOrderReq = new QueryOrderReq();
    		queryOrderReq.setOrderId(orderId);
    		
    		return queryByOrderId( SignUtil.getSignObject( queryOrderReq, RSAKey.privateKey ) );
    	}
    	
    	
    	
    
    
    
    
    }
    

      

    备注。SignResult<OrderOut> 里面的  OrderOut 我就不上传了。根据自己的业务定义就是了。

  • 相关阅读:
    django 修改默认的user表和默认的认证系统
    django 'WSGIRequest' object has no attribute 'user'
    python scrapy简单爬虫记录(实现简单爬取知乎)
    python 文件操作的注意事项
    windows cmd命令
    数据库便捷的软件
    BCB Access violateion at Address 0000 0003. Read of address 0000 0003
    C++Builder6.0 新建和打开项目软件死机
    163邮箱账号
    使用 MtVerify.h头文件 ,用的时候把他头文件的内容添加到项目
  • 原文地址:https://www.cnblogs.com/cxygg/p/9797178.html
Copyright © 2020-2023  润新知