Java实现RSA+AES+签名进行加解密

最近的工作是用户需要提现操作,所以需要对接第三方打款接口,但是交互过程需要加解密这个复杂的过程,然后就梳理学习了一番,那就把RSA+AES混合加密形式并对数据进行加签讲一下,也会贴上相应的代码。

1.RSA密钥

RSA密钥很重要,开始加密的起源第一步。

1.首先我们通过一些工具可以生成RSA密钥对,Java也有生成密钥对的代码,密钥对顾名思义就是一对钥匙,我自己平台生成RSA时会有公钥和私钥,我们会把自己平台的公钥交给第三方,私钥自己保存,同样第三方按照规则生成RSA公私钥,给我们平台公钥,自己保留私钥, 这样更安全,后续加签加密都是需要RSA操作的。

2.第三方会给相应的加密规范,我们生成RSA密钥长度为2048位,填充模式为RSA/ECB/PKCS1Padding。

2.AES密钥

AES主要是对明文数据进行加密的功能,我们加密最多的就是单用AES进行加密,但是这样安全性不高。

1.第三方给我们平台规范,AES秘钥长度为128,填充模式为AES/ECB/PKCS5Padding,而且每次会话的AES秘钥随机生成

3.SIGN(签名)

这里签名是对明文数据进行加数字签名,变成像密文一样的字符串,RSA是保证数据传输安全,AES是对请求数据加密,签名那是为什么呢???签名是为了对数据检验,如果传输过程中对数据更改,那么第三方进行验签的时候就会不通过。

第三方签名的规则是“SHA256withRSA”对加密前的明文数据进行签名。

4.RSA+AES+签名流程

上面三个名词说完大概有一个印象,但是他们怎么互相合作生成最终的加密数据呢?先上个流程图

合作方就是我们平台,佳薪棠就是第三方。

可能看了这个流程图还是蒙蔽,没错我刚开始也是,需要缕清怎么回事还是很费事的,

我平台请求要做的事情

1.因为我们要请求操作给第三方,所以第三方规定了请求的参数必须是哪些,所以aes密钥包含在所说的参数里,并且加签明文数据也需要AES密钥,所以流程图第一个AES密钥生成就应该在前面。

2.合作方RSA私钥加签?我们有规则加签之前获取所有请求参数,不包含字节类型参数,如文件、字节流,剔除sign,剔除值为空的参数,并按照第一字符的键值ASCII码递增排序(字符升序排序),如果遇到相同字符则按照第二字符的键值ASCII码递增排序,以此类推,然后就可以用我自己平台的RSA私钥进行加签了。

3.AES对请求体进行加密?用1这个步骤的AES密钥进行AES明文加密。

4.第三方公钥加密AES密钥?这里就是用的第三方RSA公钥对1这个步骤的AES密钥进行加密。

代码如下:

   /**
     * 对请求的报文进行加密处理
     * 除了异步通知需要调用的统一加密数据
     *
     * @param myReqData app端提现要传的数据
     * @return
     * @throws Exception
     * @Author df
     */  
 public JSONObject createRequestJson(JSONObject myReqData) throws Exception {
        JSONObject jxtReqData = new JSONObject();
        //组装第三方规定的请求的数据
        jxtReqData.put("signType", "RSA");
        jxtReqData.put("version", VERSION);
        jxtReqData.put("mchId", myReqData.get("mchId"));
        myReqData.remove("mchId");
        myReqData.remove("version");
        jxtReqData.put("reqId", System.currentTimeMillis());
        jxtReqData.put("reqTime", TimeUtils.getTimeNowFormat("yyyyMMddHHmmss"));
        jxtReqData.put("reqData", myReqData.toString());
        jxtReqData.put("aes", AESUtil.generateAES());
        // 调用加密操作
        Map<String, String> map = encryptionService(jxtReqData, myReqData);
        jxtReqData.put("reqData", map.get("reqData"));
        jxtReqData.put("sign", map.get("sign"));
        jxtReqData.put("aes", map.get("aes"));
        return jxtReqData;
    }

上面文字叙述的加密过程代码

   /**
     * 拆开加密逻辑操作
     * 1.加薪棠通知我们接口情况需要
     *
     * @param waitSignData 构造待验签字符串
     * @param paramData    请求或响应报文体
     * @Author df
     * @Date 2020/8/12 13:10
     */
public Map<String, String> encryptionService(JSONObject waitSignData, JSONObject paramData) throws Exception {
        Map<String, String> data = new HashMap<>();
        String dataForm = buildDataForm(waitSignData);
        // 获取aes密钥
        String aesKey = waitSignData.get("aes").toString();
        // RSA私钥加签
        String sign = RSAUtils.sign(dataForm,"MY_RSA_PRIVATE");
        // 佳薪棠RSA公钥加密AES公钥
        String encAes = RSAUtils.encrypt(aesKey,"JXT_RSA_PUBLIC");
        // AES对报文体进行加密
        String encString = AESUtil.encrypt(paramData.toString(), aesKey);
        data.put("sign", sign);
        data.put("aes", encAes);
        data.put("reqData", encString);
        return data;
    }
  // 获取当前时间并格式化
    public static String getTimeNowFormat(String format) {
        if (format == null || "".equals(format)) {
            format = "yyyy-MM-dd HH:mm:ss";
        }
        String now = DateTimeFormatter.ofPattern(format).format(LocalDateTime.now());
        return now;
    }

加签前对数据进行规范 

 /**
     * 获取待签名字符串
     *
     * @return
     */
    public static String buildDataForm(Map params) {
        if (params.containsKey(SIGN)) {
            params.remove(SIGN);
        }

        StringBuilder sb = new StringBuilder();
        //使用TreeMap对key按照字典升序排序
        TreeMap<String, Object> sortedMap = new TreeMap(params);
        //拼接为key=value&key2=value2形式
        for (Map.Entry<String, Object> entry : sortedMap.entrySet()) {
            if (entry.getValue() == null) {
                //空值不参与签名
                continue;
            }
            sb.append(entry.getKey()).append("=");
            sb.append(entry.getValue()).append("&");
        }
        sb.deleteCharAt(sb.lastIndexOf("&"));

        return sb.toString();
    }

别着急哈,后面再上RSA和AES工具类,咱们在这里继续讲下我请求完第三方,第三方操作完后也会把返回结果进行同样操作传输给我,所以返回的数据是加密的,我需要解析。返回的格式与字段都是第三方规定好的,我需要根据规定进行相应解析。

解密一定要按顺序进行

1.先用自己RSA私钥解密AES密钥,获得响应报文AES密钥

2.经过第1步骤就直接获取到了AES密钥,用这个AES的密钥进行AES解密操作获取明文数据

3.用第三方的RSA公钥对返回并解密的数据进行验签操作。验签通过整个流程结束

代码如下:

  /**
     * 对返回的数据进行解析
     *
     * @param jxtResult 返回结果体
     * @Author df
     * @Date 2020/8/8 11:24
     */
    public Map<String, String> responseParsing(JSONObject jxtResult) throws Exception {
        // 第三方返回的通用数据字段
        Map<String, Object> rtMap = new HashMap<>();
        rtMap.put("respData", jxtResult.get("respData"));
        rtMap.put("sign", jxtResult.get("sign"));
        rtMap.put("signType", "RSA");
        rtMap.put("version", jxtResult.get("version"));
        rtMap.put("mchId", jxtResult.get("mchId"));
        rtMap.put("reqId", jxtResult.get("reqId"));
        rtMap.put("respTime", jxtResult.get("respTime"));
        rtMap.put("aes", jxtResult.get("aes"));
        // 调用解密
        Map<String, String> map = decryptService(jxtResult, keyFilePre);
        return map;
    }

解密操作 

 /**
     * 拆开解密逻辑操作
     * 1.加薪棠通知我们接口情况需要
     * 2.一定遵守解析流程
     * 2.1 先RSA私钥解密AES密钥
     * 2.2 AES解密响应报文
     * 2.3 构造验签数据,并验签
     *
     * @param paramData 请求或响应报文体
     * @Author df
     * @Date 2020/8/12 11:19
     */
    public Map<String, String> decryptService(JSONObject paramData) throws Exception {
        Map<String, String> resultMap = new HashMap<>();
        // 自己RSA私钥解密AES密钥,获得响应报文AES密钥
        String decAES = RSAUtils.decrypt(paramData.get("aes").toString(), super.readRsaPrivateKey(RSA_PRIVATE_KEY));
        if (decAES != null) {
            // AES解密响应 业务报文respData
            String decString = AESUtil.decrypt((String) paramData.get("respData"), decAES);
            paramData.put("respData", decString);
            paramData.put("aes", decAES);
            String sign = paramData.get("sign").toString();
            paramData.remove("code");
            paramData.remove("msg");
            // 构造待验签字符串
            String dataForm = buildDataForm(paramData);
            // 验签结果
            boolean isverifyPass = RSAUtils.verifySign(dataForm, sign, super.readRsaPrivateKey(JXT_RSA_PUBLIC_KEY));
            System.out.println("验签结果: " + isverifyPass);
            if (isverifyPass) {
                // 返回结果
                resultMap.put("code", String.valueOf(ReqAnswerCodeEnum.SUCCESS.getCode()));
                resultMap.put("respData", decString);
            } else {
                // 返回结果
                resultMap.put("code", ReqAnswerCodeEnum.SIGN_CHECK_ERROR.getCode() + "");
                resultMap.put("msg", ReqAnswerCodeEnum.SIGN_CHECK_ERROR.getMessage());
            }
            return resultMap;
        } else {
            resultMap.put("code", "0001");
            resultMap.put("msg", "获取业务报文失败");
            return resultMap;
        }
    }

AES工具类代码

package com.xiyin.finance.jiaxintang.util.encryption;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

import org.apache.tomcat.util.codec.binary.Base64;

/**
 * 随机生成AES
 *
 * @author zhangbj
 * @author df
 * @version 1.0
 * @date 2020/7/31 16:07
 */
public abstract class AESUtil {
    public static final String ALG_AES = "AES";
    public static final String CHARSET_UTF8 = "utf-8";
    public static final String AES_ALGORITHM = "AES/ECB/PKCS5Padding";

    public static String generateAES() {
        try {
            KeyGenerator keygen = KeyGenerator.getInstance(ALG_AES);
            SecretKey desKey = keygen.generateKey();
            return Base64.encodeBase64String(desKey.getEncoded());
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * AES对报文体进行加密并Base64位编码
     *
     * @param content
     * @param password
     * @return
     * @throws Exception
     */
    public static String encrypt(String content, String password) throws Exception {
        SecretKeySpec key = new SecretKeySpec(password.getBytes(CHARSET_UTF8), ALG_AES);// 转换为AES专用密钥
        Cipher cipher = Cipher.getInstance(AES_ALGORITHM);// 创建密码器
        byte[] byteContent = content.getBytes(CHARSET_UTF8);
        cipher.init(Cipher.ENCRYPT_MODE, key);// 初始化为加密模式的密码器
        byte[] result = cipher.doFinal(byteContent);// 加密
        return Base64.encodeBase64String(result);
    }

    /**
     * AES解密报文
     *
     * @param content  AES加密过过的内容
     * @param password 加密时的密码
     * @return 明文
     */
    public static String decrypt(String content, String password) throws Exception {

        SecretKeySpec key = new SecretKeySpec(password.getBytes(CHARSET_UTF8), ALG_AES);// 转换为AES专用密钥
        Cipher cipher = Cipher.getInstance(AES_ALGORITHM);// 创建密码器
        cipher.init(Cipher.DECRYPT_MODE, key);// 初始化为解密模式的密码器
        byte[] result = cipher.doFinal(Base64.decodeBase64(content));
        return new String(result); // 明文

    }
}

RSA工具类代码

package com.xiyin.finance.jiaxintang.util.encryption;


import org.apache.tomcat.util.codec.binary.Base64;

import javax.crypto.Cipher;
import java.io.*;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

/**
 * @author zhangbj
 * @author df
 * @version 1.0
 * @date 2020/8/3 9:42
 */
public abstract class RSAUtils {

    private static final String ALGORITHM_RSA = "RSA";
    private static final String SHA256WithRSA = "SHA256WithRSA";

    /**
     * 私钥对报文签名并Base64编码(加密前报文)
     *
     * @param content 加密前报文
     * @param priKey  RSA私钥
     * @return
     */
    public static String sign(String content, String priKey) throws Exception {
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(priKey));
        KeyFactory fac = KeyFactory.getInstance(ALGORITHM_RSA);
        RSAPrivateKey privateKey = (RSAPrivateKey) fac.generatePrivate(keySpec);
        Signature sigEng = Signature.getInstance(SHA256WithRSA);
        sigEng.initSign(privateKey);
        sigEng.update(content.getBytes());
        byte[] signature = sigEng.sign();
        return Base64.encodeBase64String(signature);
    }

    /**
     * 私钥数字签名验签(对加密前的数据)
     *
     * @param content 加密前报文
     * @param sign    签名
     * @param pubKey  公钥
     * @return
     */
    public static boolean verifySign(String content, String sign, String pubKey) {
        try {
            X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64.decodeBase64(pubKey));
            KeyFactory fac = KeyFactory.getInstance(ALGORITHM_RSA);
            RSAPublicKey rsaPubKey = (RSAPublicKey) fac.generatePublic(keySpec);
            Signature sigEng = Signature.getInstance(SHA256WithRSA);
            sigEng.initVerify(rsaPubKey);
            sigEng.update(content.getBytes());
            return sigEng.verify(Base64.decodeBase64(sign));
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 私钥解密
     *
     * @param content 已加密数据
     * @param pubKey  私钥
     * @return
     * @throws Exception
     */
    public static String encrypt(String content, String pubKey)
            throws Exception {
        PublicKey publicKey = getPublicKeyFromX509(pubKey);
        Cipher cipher = Cipher.getInstance(ALGORITHM_RSA);
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        return Base64.encodeBase64String(cipher.doFinal(content.getBytes()));
    }


    private static PublicKey getPublicKeyFromX509(String pubKey) throws Exception {
        InputStream ins = new ByteArrayInputStream(pubKey.getBytes());
        KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM_RSA);
        byte[] bytes = readAsString(ins).getBytes();

        return keyFactory.generatePublic(new X509EncodedKeySpec(Base64.decodeBase64(bytes)));
    }

    /**
     * 内部私钥解密AES报文
     *
     * @param content 已加密数据
     * @param priKey  内部私钥
     * @return
     * @throws Exception
     */
    public static String decrypt(String content, String priKey)
            throws Exception {
        PrivateKey privateKey = getPrivateKeyFromPKCS8(priKey);
        Cipher cipher = Cipher.getInstance(ALGORITHM_RSA);
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        return new String(cipher.doFinal(Base64.decodeBase64(content)));
    }

    private static PrivateKey getPrivateKeyFromPKCS8(String priKey) throws Exception {
        InputStream ins = new ByteArrayInputStream(priKey.getBytes());
        KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM_RSA);
        byte[] bytes = readAsString(ins).getBytes();
        return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(Base64.decodeBase64(bytes)));
    }

    private static String readAsString(InputStream ins) throws IOException {
        Reader reader = new InputStreamReader(ins);
        StringWriter writer = new StringWriter();
        char[] chars = new char[2048];
        int length;
        while ((length = reader.read(chars)) >= 0) {
            writer.write(chars, 0, length);
        }
        return writer.toString();
    }

}

以上就是加解密全部过程!

RSAAES 是非常常见的加密算法。RSA 算法可以用于进行公钥加密和数字签名AES 算法可以用于进行对称加密。下面是一个使用 Java 实现 RSAAES 混合加解密系统的示例代码,同时支持图片加解密: ```java import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.io.*; import java.nio.charset.StandardCharsets; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.PrivateKey; import java.security.PublicKey; import java.util.Base64; public class RSAAndAES { // 随机生成 AES 密钥 public static byte[] generateAESKey() throws Exception { KeyGenerator keyGenerator = KeyGenerator.getInstance("AES"); keyGenerator.init(128); // 128 位密钥长度 SecretKey secretKey = keyGenerator.generateKey(); return secretKey.getEncoded(); } // 随机生成 RSA 密钥对 public static KeyPair generateRSAKeyPair() throws Exception { KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); keyPairGenerator.initialize(1024); // 1024 位密钥长度 return keyPairGenerator.generateKeyPair(); } // 使用 RSA 公钥加密数据 public static byte[] encryptWithRSAPublicKey(byte[] data, PublicKey publicKey) throws Exception { Cipher cipher = Cipher.getInstance("RSA"); cipher.init(Cipher.ENCRYPT_MODE, publicKey); return cipher.doFinal(data); } // 使用 RSA 私钥解密数据 public static byte[] decryptWithRSAPrivateKey(byte[] data, PrivateKey privateKey) throws Exception { Cipher cipher = Cipher.getInstance("RSA"); cipher.init(Cipher.DECRYPT_MODE, privateKey); return cipher.doFinal(data); } // 使用 AES 密钥加密数据 public static byte[] encryptWithAES(byte[] data, byte[] key, byte[] iv) throws Exception { Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES"); IvParameterSpec ivParameterSpec = new IvParameterSpec(iv); cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec); return cipher.doFinal(data); } // 使用 AES 密钥解密数据 public static byte[] decryptWithAES(byte[] data, byte[] key, byte[] iv) throws Exception { Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES"); IvParameterSpec ivParameterSpec = new IvParameterSpec(iv); cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec); return cipher.doFinal(data); } // 将字节数组转换为 Base64 编码的字符串 public static String encodeBase64(byte[] data) { return Base64.getEncoder().encodeToString(data); } // 将 Base64 编码的字符串转换为字节数组 public static byte[] decodeBase64(String str) { return Base64.getDecoder().decode(str); } // 读取文件内容到字节数组 public static byte[] readFile(String filename) throws Exception { BufferedInputStream bis = new BufferedInputStream(new FileInputStream(filename)); ByteArrayOutputStream bos = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int len; while ((len = bis.read(buffer)) != -1) { bos.write(buffer, 0, len); } bis.close(); bos.close(); return bos.toByteArray(); } // 将字节数组写入文件 public static void writeFile(byte[] data, String filename) throws Exception { BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(filename)); bos.write(data); bos.close(); } public static void main(String[] args) throws Exception { // 随机生成 AES 密钥和向量 byte[] aesKey = generateAESKey(); byte[] iv = generateAESKey(); // 随机生成 RSA 密钥对 KeyPair keyPair = generateRSAKeyPair(); PublicKey publicKey = keyPair.getPublic(); PrivateKey privateKey = keyPair.getPrivate(); // 加密图片文件 byte[] imageBytes = readFile("image.png"); byte[] encryptedImageBytes = encryptWithAES(imageBytes, aesKey, iv); byte[] encryptedImageKeyBytes = encryptWithRSAPublicKey(aesKey, publicKey); String encryptedImage = encodeBase64(encryptedImageBytes); String encryptedImageKey = encodeBase64(encryptedImageKeyBytes); writeFile(encryptedImage.getBytes(StandardCharsets.UTF_8), "encryptedImage.txt"); writeFile(encryptedImageKey.getBytes(StandardCharsets.UTF_8), "encryptedImageKey.txt"); // 解密图片文件 byte[] encryptedImageBytes2 = decodeBase64(new String(readFile("encryptedImage.txt"), StandardCharsets.UTF_8)); byte[] encryptedImageKeyBytes2 = decodeBase64(new String(readFile("encryptedImageKey.txt"), StandardCharsets.UTF_8)); byte[] aesKey2 = decryptWithRSAPrivateKey(encryptedImageKeyBytes2, privateKey); byte[] decryptedImageBytes = decryptWithAES(encryptedImageBytes2, aesKey2, iv); writeFile(decryptedImageBytes, "decryptedImage.png"); } } ``` 这个示例代码中,我们首先使用 `KeyGenerator` 生成 AES 密钥和向量,使用 `KeyPairGenerator` 生成 RSA 密钥对。然后,我们使用 `Cipher` 对象进行加解密操作。需要注意的是,AES 加解密时需要使用密钥和向量,而 RSA 加解密时只需要使用公钥或私钥。最后,我们使用 `Base64` 对字节数组进行编码和解码,以便于将加密后的数据存储到文件中。 在实际应用中,需要注意使用的算法和密钥长度,以及密钥的管理和保护。此外,在对图片等二进制文件进行加解密时,需要注意编码和解码的方式,以免出现数据损坏或丢失的问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值