SignEncryptUtil工具类实现AES结合RSA加密

概述

有些安全级别比较高的接口,需要做严格的加密,则可以用AES结合RSA做一个加密处理。RSA是非对称过加密,需要准备一对匹配的公钥私钥文件。
(待完善。。。)

RSA加密算法简介

RSA是最流行的非对称加密算法之一。也被称为公钥加密。它是由罗纳德·李维斯特(Ron Rivest)、阿迪·萨莫尔(Adi Shamir)和伦纳德·阿德曼(Leonard Adleman)在1977年一起提出的。当时他们三人都在麻省理工学院工作。RSA就是他们三人姓氏开头字母拼在一起组成的。

RSA是非对称的,也就是用来加密的密钥和用来解密的密钥不是同一个。

和DES一样的是,RSA也是分组加密算法,不同的是分组大小可以根据密钥的大小而改变。如果加密的数据不是分组大小的整数倍,则会根据具体的应用方式增加额外的填充位。

RSA作为一种非对称的加密算法,其中很重要的一特点是当数据在网络中传输时,用来加密数据的密钥并不需要也和数据一起传送。因此,这就减少了密钥泄露的可能性。RSA在不允许加密方解密数据时也很有用,加密的一方使用一个密钥,称为公钥,解密的一方使用另一个密钥,称为私钥,私钥需要保持其私有性。

RSA被认为是非常安全的,不过计算速度要比DES慢很多。同DES一样,其安全性也从未被证明过,但想攻破RSA算法涉及的大数(至少200位的大数)的因子分解是一个极其困难的问题。所以,由于缺乏解决大数的因子分解的有效方法,因此,可以推测出目前没有有效的办法可以破解RSA。

代码

这里我分了AesUtil、KeyEncryptUtil和SignEncryptUtil三个工具类来处理。

AesUtil

AesUtil 工具类详见我前面写的文章,戳这里

KeyEncryptUtil
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

/**
 * 秘钥工具类
 *
 * @author weizhenhui
 */
public class KeyEncryptUtil {
    private KeyEncryptUtil() {
    }

    /**
     * 根据密钥对中的公钥字符串,生成公钥
     */
    public static PublicKey getPublicKey(String publicKeyStr) throws NoSuchAlgorithmException, InvalidKeySpecException {
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        byte[] bytes = Base64.getDecoder().decode(publicKeyStr);
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(bytes);
        return keyFactory.generatePublic(keySpec);
    }

    /**
     * 根据秘钥对中的私钥字符串,生成私钥
     */
    public static PrivateKey getPrivateKey(String privateKeyStr) throws NoSuchAlgorithmException, InvalidKeySpecException {
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        byte[] bytes = Base64.getDecoder().decode(privateKeyStr);
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(bytes);
        return keyFactory.generatePrivate(keySpec);
    }

    /**
     * 从流中生成公钥(Pem格式的私钥)
     */
    public static PublicKey getKeyPairOfPemPublicKey(InputStream inputStream) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
        BufferedReader bufReader = new BufferedReader(new InputStreamReader(inputStream));
        StringBuilder sb = new StringBuilder();
        String temp;
        while ((temp = bufReader.readLine()) != null) {
            if (temp.startsWith("--") && temp.endsWith("--")) {
                continue;
            }
            sb.append(temp);
        }
        return getPublicKey(sb.toString());
    }

    /**
     * 从流中生成私钥
     */
    public static PrivateKey getKeyPairOfPemPrivateKey(InputStream inputStream) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
        BufferedReader bufReader = new BufferedReader(new InputStreamReader(inputStream));
        StringBuilder sb = new StringBuilder();
        String temp;
        while ((temp = bufReader.readLine()) != null) {
            if (temp.startsWith("--") && temp.endsWith("--")) {
                continue;
            }
            sb.append(temp);
        }
        return getPrivateKey(sb.toString());
    }
}
SignEncryptUtil

import com.alibaba.fastjson.JSONObject;

import javax.crypto.*;
import java.io.UnsupportedEncodingException;
import java.security.*;
import java.util.*;

/**
 * RSA加密工具类
 *
 * @author weizhenhui
 */
public class SignEncryptUtil {
    private SignEncryptUtil() {
    }

    public static final String KEY_ALGORITHM = "AES";
    public static final String KEY_ENCRYPT_ALGORITHM = "RSA";
    public static final String CHARSET_NAME = "UTF-8";

    /**
     * RSA私钥解密
     *
     * @param encyrptString 密文数据
     * @param privateKey    私钥
     * @return
     */
    public static byte[] decryptKeyByPrivateKey(String encyrptString, PrivateKey privateKey)
            throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
        Cipher cipher = Cipher.getInstance(KEY_ENCRYPT_ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        return cipher.doFinal(Base64.getDecoder().decode(encyrptString));
    }

    /**
     * 生成加密key
     */
    public static byte[] getEncryptKey() {
        KeyGenerator keyGenerator = null;
        // 实例化一个用AES加密算法的密钥生成器
        try {
            keyGenerator = KeyGenerator.getInstance(KEY_ALGORITHM);
        } catch (NoSuchAlgorithmException e) {
            throw new IllegalArgumentException("NoSuchAlgorithmException=>" + e.getMessage());
        }
        // bitSize
        keyGenerator.init(128);
        // 生成一个密钥。
        SecretKey key = keyGenerator.generateKey();
        return key.getEncoded();
    }

    /**
     * 通过公钥加密
     *
     * @param keyBytes  AES 秘钥
     * @param publicKey 公钥
     */
    public static String encryptKeyByPublicKey(byte[] keyBytes, PublicKey publicKey) throws Exception {
        // 创建密码器
        Cipher cipher = Cipher.getInstance(KEY_ENCRYPT_ALGORITHM);
        // 初始化为加密模式的密码器
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        // 加密
        byte[] enBytes = cipher.doFinal(keyBytes);
        // 通过Base64转码返回
        return Base64.getEncoder().encodeToString(enBytes);
    }

    /**
     * AES加密
     */
    public static String encryptData(String data, byte[] keyBytes) {
        return AesUtil.encrypt(data, keyBytes);
    }

    /**
     * AES解密
     */
    public static String decryptData(String encryptData, byte[] keyBytes) {
        return AesUtil.decrypt(encryptData, keyBytes);
    }

    /**
     * 检查签名是否正确
     *
     * @param content   签名内容
     * @param sign      签名比较对象
     * @param publicKey 公钥
     * @param signType  签名类型
     * @return 成功:true,失败:false
     */
    public static boolean checkSignByPublicKey(String content, String sign, PublicKey publicKey, String signType) throws Exception {
        try {
            Signature signature = Signature.getInstance(signType);
            signature.initVerify(publicKey);
            signature.update(content.getBytes(CHARSET_NAME));
            return signature.verify(Base64.getDecoder().decode(sign.getBytes(CHARSET_NAME)));
        } catch (InvalidKeyException | NoSuchAlgorithmException | SignatureException | UnsupportedEncodingException e) {
            throw new IllegalArgumentException(e.getClass().getSimpleName() + "=>" + e.getMessage());
        } catch (Exception e) {
            e.printStackTrace();
            throw new Exception("checkSignByPublicKey() => " + e.getMessage());
        }
    }

    /**
     * RSA私钥签名
     */
    public static String signByPrivateKey(String content, PrivateKey privateKey, String signType) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException, UnsupportedEncodingException {
        Signature signature = Signature.getInstance(signType);
        signature.initSign(privateKey);
        signature.update(content.getBytes(CHARSET_NAME));
        return new String(Base64.getEncoder().encode(signature.sign()), CHARSET_NAME);
    }

    /**
     * 对多节点的Map对象生成签名字符串(参数名 ASCII 码从小到大排序(字典序)) 全部节点都排序
     *
     * @param params
     * @return
     */
    public static final String toString(Map<String, Object> params) {
        Iterator<Map.Entry<String, Object>> iterator = params.entrySet().iterator();
        Map<String, Object> signMap = newSortedMap(true);
        toStringLoop(signMap, iterator);
        return new JSONObject(signMap).toJSONString();
    }

    private static final void toStringLoop(Map<String, Object> jsonObject, Iterator<Map.Entry<String, Object>> iterator) {
        while (iterator.hasNext()) {
            Map.Entry<String, Object> next = iterator.next();
            String key = next.getKey();
            Object value = next.getValue();
            // 如果参数的值为空不参与签名
            if (key == null || "".equals(key) || "sign".equals(key) || value == null || "".equals(value)) {
                continue;
            }

            if (value instanceof Map) {
                Map<String, Object> map = (Map<String, Object>) value;
                Iterator<Map.Entry<String, Object>> iter = map.entrySet().iterator();
                Map<String, Object> child = newSortedMap(true);
                toStringLoop(child, iter);
                jsonObject.put(key, child);
            } else if (value instanceof List) {
                List<Object> newList = new ArrayList<>();
                for (Object obj : (List<Object>) value) {
                    if (obj instanceof Map) {
                        Map<String, Object> map = (Map<String, Object>) obj;
                        Iterator<Map.Entry<String, Object>> iter = map.entrySet().iterator();
                        Map<String, Object> child = newSortedMap(true);
                        toStringLoop(child, iter);
                        newList.add(child);
                    }
                }
                jsonObject.put(key, newList);
            } else if (value != null && value.getClass().isArray()) {
                List<Object> newList = new ArrayList<>();
                for (Object obj : (Object[]) value) {
                    if (obj instanceof Map) {
                        Map<String, Object> map = (Map<String, Object>) obj;
                        Iterator<Map.Entry<String, Object>> iter = map.entrySet().iterator();
                        Map<String, Object> child = newSortedMap(true);
                        toStringLoop(child, iter);
                        newList.add(child);
                    }
                }
                jsonObject.put(key, newList);
            } else {
                jsonObject.put(next.getKey(), next.getValue());
            }
        }
    }

    /**
     * 实例化一个排序的Map.
     *
     * @param isAsc 是否升序
     * @return
     */
    public static Map<String, Object> newSortedMap(final boolean isAsc) {
        return new TreeMap<>((o1, o2) -> {
            int ret = o1.compareTo(o2);
            return isAsc ? ret : -ret;
        });
    }

}

使用案例

这里写个加密请求第三方接口的案例。这里A是我方,B是第三方。在这之前需要双方交换公钥。

思路:

用第三方的公钥B_public_key_dev.pem加签、加密请求他接口,他们用自己的私钥B_private_key_dev.pem验签并解密数据,然后响应;响应数据用我方公钥A_public_key_dev.pem加签加密返回,我这边得到的响应数据就需要用自己的私钥A_private_key_dev.pem验签、解密数据。总之,要成对的验签、加密解密数据。
这里RSA是用来加签验签,以防在请求过程中数据被篡改,实际加密请求参数的是用的AES加密,然后AES秘钥是用公钥加密后传输的,这里接收到参数后也是需要用配对的私钥解密得到AES秘钥,方能解密。案例中的请求头参数中reqKey就是加密后的秘钥。

实现代码:
package com.wzh.test;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.wzh.utils.HttpUtil;
import com.wzh.utils.KeyEncryptUtil;
import com.wzh.utils.SignEncryptUtil;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SignatureException;
import java.util.HashMap;
import java.util.Map;

/**
 * RSA 加密请求 Test
 * @author weizhenhui
 * @date 2021/4/30 16:49
 */
public class SignEncryptUtilTest {
    public static final Logger logger = LoggerFactory.getLogger(SignEncryptUtilTest.class);
    public static final String SUCCESS_CODE = "200";

    public static void main(String[] args) throws Exception {
        // 请求地址
        String requestUrl = "localhost:8080/testSign";
        // 公共请求头参数
        Map<String, Object> reqMap= new HashMap<>(7);
        reqMap.put("requestNo", "20210430173828001");
        reqMap.put("requestTimestamp", "2021-04-30 17:38:28");
        reqMap.put("version", "1.0");
        reqMap.put("sign", "");
        reqMap.put("signType", "");
        reqMap.put("reqKey", "");
        reqMap.put("reqDate", "");

        // 接口请求参数
        Map<String, Object> reqDataMap= new HashMap<>(3);
        reqDataMap.put("username", "weizhenhui");
        reqDataMap.put("age", 18);
        reqDataMap.put("sex", 1);
        reqDataMap.put("occupation", "程序员");

        Map<String, Object> returnMap = requestData(requestUrl, reqMap, reqDataMap);
        System.out.println(reqDataMap);
    }

    /**
     * @param reqMap     公共请求头参数map
     * @param reqDataMap 真正的请求参数map
     * @return
     * @throws Exception
     */
    public static Map<String, Object> requestData(String requestUrl, Map<String, Object> reqMap, Map<String, Object> reqDataMap) throws Exception {
        Map<String, Object> returnMap;
        String signType = "SHA256WithRSA";
        InputStream publicKeyIn = null;
        InputStream privateKeyIn = null;
        try {
            // 生成AES秘钥
            byte[] keyBytes = SignEncryptUtil.getEncryptKey();

            // 根据.pem格式的秘钥文件获取公钥、私钥
            String publicKeyPath = "/key/B_public_key_dev.pem";
            String privateKeyPath = "/key/A_private_key_dev.pem";
            publicKeyIn = new FileInputStream(new File(publicKeyPath));
            privateKeyIn = new FileInputStream(new File(privateKeyPath));
            PublicKey publicKey = KeyEncryptUtil.getKeyPairOfPemPublicKey(publicKeyIn);
            PrivateKey privateKey = KeyEncryptUtil.getKeyPairOfPemPrivateKey(privateKeyIn);

            // 根据公钥得到加密aesKey
            String encryptAesKey = SignEncryptUtil.encryptKeyByPublicKey(keyBytes, publicKey);
            reqMap.put("reqKey", encryptAesKey);

            // 用AES秘钥加密真正的请求参数reqDataMap<>,并添加到公共请求头reqMap<>中
            logger.info("请求数据:加密前 =>> " + reqDataMap.toString());
            String encryptReqDataString = SignEncryptUtil.encryptData(reqDataMap.toString(), keyBytes);
            logger.info("请求数据:加密后 =>> " + encryptReqDataString);
            reqMap.put("reqData", encryptReqDataString);

            // 生成签名,用于验证请求参数是否被篡改。
            // 这里toString()是带排序功能的,请求前后的参数排序要相同,否则验签不通过
            String signString = SignEncryptUtil.toString(reqMap);
            logger.info("待签报文 =>> " + signString);
            String sign = SignEncryptUtil.signByPrivateKey(signString, privateKey, signType);
            reqMap.put("sign", sign);
            logger.info("请求报文 =>> " + reqMap);

            // 请求接口
            Map<String, Object> httpResultMap = HttpUtil.httpPost(requestUrl, JSONObject.toJSONString(reqMap));
            logger.info("响应报文 =>> " + httpResultMap);

            String returnCode = (String) httpResultMap.get("retCode");
            String returnMessage = (String) httpResultMap.get("retMessage");
            String returnSign = httpResultMap.get("sign").toString();
            // 根据返回的code判断是否返回成功
            if (!SUCCESS_CODE.equals(returnCode) || StringUtils.isEmpty(returnSign)) {
                throw new Exception("接口返回数据有误,retCode=" + returnCode + ", returnMessage= " + returnMessage);
            }

            // ============ 验证签名 Start ============
            // 1、得到返回的签名串sign和签名的类型
            String resultSign = (String) httpResultMap.get("sign");
            String resultSignType = (String) httpResultMap.get("signType");
            // 2、在把返回结果map排序toString()一次,跟前面的保持一致
            String resultJsonString = SignEncryptUtil.toString(httpResultMap);
            logger.info("响应待签报文 =>> " + resultJsonString);
            boolean resultSignResult;
            // 3、验证签名
            try {
                resultSignResult = SignEncryptUtil.checkSignByPublicKey(resultJsonString, resultSign, publicKey, resultSignType);
            } catch (Exception e) {
                throw new SignatureException("签名错误:" + e.getMessage());
            }
            logger.info("响应验签结果 =>> " + resultSignResult);
            if (!resultSignResult) {
                throw new SignatureException("签名不匹配");
            }
            // ============ 验证签名 End ============

            // 验证签名通过后,方可解密数据,操作跟加密对称处理即可
            // ============ 解密数据 Start ============
            String resKey = (String) httpResultMap.get("respKey");
            String resData = (String) httpResultMap.get("respData");
            // 发送者的数据用公钥加密,接受者就要用与其一对的秘钥解密数据
            // 1、先解密得到AES秘钥
            byte[] resKeyBytes = SignEncryptUtil.decryptKeyByPrivateKey(resKey, privateKey);
            // 2、再用AES秘钥解密数据
            logger.info("响应数据:解密前 =>> " + resData);
            String decryptData = SignEncryptUtil.decryptData(resData, resKeyBytes);
            logger.info("响应数据:解密后 =>> " + decryptData);
            // ============ 解密数据 End ============

            // 数据解密完成,得到响应数据
            returnMap = JSON.parseObject(decryptData);
        } catch (Exception e) {
            logger.error("请求接口异常 =>> " + e.getMessage());
            e.printStackTrace();
            throw new Exception("请求接口异常 =>> " + e.getMessage());
        } finally {
            try {
                if (null != privateKeyIn) {
                    privateKeyIn.close();
                }
                if (null != publicKeyIn) {
                    publicKeyIn.close();
                }
            } catch (IOException e) {
                logger.error("关闭流异常");
            }
        }

        return returnMap;
    }
}

编写有误的地方还请指出,共勉~

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值