Java -- 生成RSA公钥私钥并对文件加密

向导

      • 介绍
      • RSA工具类
      • 测试类
      • 文件工具类
      • 问题
        • 1. Data must not be longer than 117 bytes
          • 解决
          • 分段加解密代码

介绍

  RSA加密算法是一种非对称加密算法。非对称加密与对称加密相比其安全性更好,但对应的加密和解密花费时间长、速度慢,只适合对少量数据进行加密。使用RSA一般需要产生公钥和私钥,当采用公钥加密时,使用私钥解密;采用私钥加密时,使用公钥解密。本文采用公钥给别人使用,别人用公钥加密,我们用私钥解密。

RSA工具类

package com.caxs.warn.common.utils;

import com.caxs.warn.common.exception.BusinessException;
import org.apache.commons.codec.binary.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.Cipher;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;

/**
 * @Author: TheBigBlue
 * @Description:
 * @Date: 2019/9/23
 */
public class RSAUtils {

    private static final Logger LOGGER = LoggerFactory.getLogger(RSAUtils.class);

    /**
     * 加解密算法关键字
     */
    public static final String KEY_ALGORITHM = "RSA";

    /**
     * 公钥关键字
     */
    private static final String PUBLIC_KEY = "RSAPublicKey";

    /**
     * 私钥关键字
     */
    private static final String PRIVATE_KEY = "RSAPrivateKey";

    /**
     * 默认编码
     */
    public static final String CHARSET = "UTF-8";

    /**
     * @Author: TheBigBlue
     * @Description: 解密
     * @Date: 2019/9/23
     * @Param str: 加密的base64串
     * @Param privateKey: Base64私钥串
     * @Return: 明文
     **/
    public static String decrypt(String str, String privateKey) throws Exception {
        //64位解码加密后的字符串
        byte[] inputByte = Base64.decodeBase64(str.getBytes(CHARSET));
        //base64编码的私钥
        byte[] keyBytes = Base64.decodeBase64(privateKey.getBytes(CHARSET));
        RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance(KEY_ALGORITHM).generatePrivate(new PKCS8EncodedKeySpec(keyBytes));
        //RSA解密
        Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, priKey);
        return new String(cipher.doFinal(inputByte), CHARSET);
    }

    /**
     * @Author: TheBigBlue
     * @Description: 加密
     * @Date: 2019/9/23
     * @Param data: 需要加密的数据
     * @Param publicKey: Base64公钥串
     * @Return:
     **/
    public static String encrypt(String data, String publicKey) throws Exception {
        // 取得公钥
        Key key = KeyFactory.getInstance(KEY_ALGORITHM).generatePublic(new X509EncodedKeySpec(Base64.decodeBase64(publicKey.getBytes(CHARSET))));
        // 对数据加密
        Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, key);
        return new String(Base64.encodeBase64(cipher.doFinal(data.getBytes(CHARSET))), CHARSET);
    }

    /**
     * 取得私钥。
     */
    public static String getPrivateKeyStr(Map<String, Object> keyMap) {
        Key key = (Key) keyMap.get(PRIVATE_KEY);
        return Base64.encodeBase64String(key.getEncoded());
    }

    /**
     * 取得公钥。
     */
    public static String getPublicKeyStr(Map<String, Object> keyMap) {
        Key key = (Key) keyMap.get(PUBLIC_KEY);
        return Base64.encodeBase64String(key.getEncoded());
    }

    /**
     * 初始化密钥。
     */
    public static Map<String, Object> initKey() throws NoSuchAlgorithmException {
        KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM);
        keyPairGen.initialize(1024);
        KeyPair keyPair = keyPairGen.generateKeyPair();

        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();    // 公钥
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();  // 私钥
        Map<String, Object> keyMap = new HashMap<>(5);

        keyMap.put(PUBLIC_KEY, publicKey);
        keyMap.put(PRIVATE_KEY, privateKey);
        return keyMap;
    }

    /**
     * @Author: TheBigBlue
     * @Description: 生成公私钥,并保存至指定目录下
     * @Date: 2019/9/23
     * @Param filepath: 保存路径
     * @Return:
     **/
    public static void getKeyAndSave(String savePath) {
        try {
            Map<String, Object> keyMap = initKey();
            LOGGER.info("密钥保存路径为:{}", savePath);
            FileUtil.writeFile(getPrivateKeyStr(keyMap), savePath + "/private.store");
            FileUtil.writeFile(getPublicKeyStr(keyMap), savePath + "/public.store");
        } catch (Exception e) {
            LOGGER.error("生成密钥失败", e);
            throw new BusinessException("生成密钥失败", e);
        }
    }
}

测试类

package com.caxs.warn.secret;

import com.caxs.warn.common.utils.FileUtil;
import com.caxs.warn.common.utils.RSAUtils;
import org.junit.Test;

/**
 * @Author: TheBigBlue
 * @Description:
 * @Date: 2019/9/23
 */
public class SecretTest {

    /**
     * @Author: TheBigBlue
     * @Description: 生成密钥并保存
     * @Date: 2019/9/23
     * @Return:
     **/
    @Test
    public void testCreate() {
        RSAUtils.getKeyAndSave("D:\\Download\\secret");
    }

    /**
     * @Author: TheBigBlue
     * @Description: 测试加密后保存
     * @Date: 2019/9/23
     * @Return:
     **/
    @Test
    public void testEncrypt() {
        try {
            String data = FileUtil.readFile("D:\\Download\\secret\\script.sql");
            System.out.println("需要加密的内容:" + data);
            String publicKey = FileUtil.readFile("D:\\Download\\secret\\public.store");
            String encryptStr = RSAUtils.encrypt(data, publicKey);
            FileUtil.writeFile(encryptStr, "D:\\Download\\secret\\secret-data");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * @Author: TheBigBlue
     * @Description: 测试读取后解密
     * @Date: 2019/9/23
     * @Return:
     **/
    @Test
    public void testDecrypt() {
        try {
            String data = FileUtil.readFile("D:\\Download\\secret\\secret-data");
            String privateKey = FileUtil.readFile("D:\\Download\\secret\\private.store");
            String decryptStr = RSAUtils.decrypt(data, privateKey);
            System.out.println("解密后的内容:" + decryptStr);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * @Author: TheBigBlue
     * @Description: 测试加/解密
     * @Date: 2019/9/23
     * @Return:
     **/
    @Test
    public void testEncryptAndDecrypt() {
        try {
            String data = FileUtil.readFile("D:\\Download\\secret\\script.sql");
            System.out.println("需要加密的内容:" + data);
            String publicKey = FileUtil.readFile("D:\\Download\\secret\\public.store");
            String privateKey = FileUtil.readFile("D:\\Download\\secret\\private.store");
            String encryptStr = RSAUtils.encrypt(data, publicKey);
            String decryptStr = RSAUtils.decrypt(encryptStr, privateKey);
            System.out.println("解密后的内容:" + decryptStr);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

文件工具类

package com.caxs.warn.common.utils;


import com.caxs.warn.common.exception.BusinessException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.nio.charset.StandardCharsets;

/**
 * @ClassName FileUtil
 * @Description 操作文件工具
 * @Author baojin
 * @Date 2019/9/18 14:39
 * @Version: v1.0 文件初始创建
 **/
public class FileUtil {

    private static final Logger LOGGER = LoggerFactory.getLogger(FileUtil.class);

    private FileUtil() {
    }

    /**
     * @return java.lang.String
     * @Description 读取文件
     * @Param filePath  路径
     * @Author baojin
     * @date 2019/9/18 14:41
     * @Throw
     **/
    public static String readFile(String filePath) {
        String string;
        StringBuilder data = new StringBuilder();
        try (
                InputStreamReader inputStreamReader = new InputStreamReader(new FileInputStream(filePath), StandardCharsets.UTF_8);
                BufferedReader bufferedReader = new BufferedReader(inputStreamReader)
        ) {
            while ((string = bufferedReader.readLine()) != null) {
                //每行用分号分割
                data.append(string);
            }
            LOGGER.info("文件读取成功,文件名称为:{}", filePath);
            return data.toString();
        } catch (Exception e) {
            LOGGER.error("文件读取失败,文件名称为:" + filePath, e);
            throw new BusinessException("文件读取失败", e);
        }

    }
}

问题

1. Data must not be longer than 117 bytes

  非对称加密存在加密数据限制问题(即使用非对称密钥加密数据时,一次加密的数据长度是(密钥长度/8-11)本程序中,密钥长度是1024( keyPairGen.initialize(1024);程序中这里指定了),那么加密内容应该不得超过1024/8-11=117,解密要求密文最大长度为1024/8=128字节,如果需要加密的数据长度大于117,则会报错:Data must not be longer than 117 bytes

解决
  1. 第一种方式是扩大密钥长度,比如将密钥长度扩展为2048、4096。。。但是对于非对称加密来说,加密内容越大,则加解密越慢,所以不是很推荐。
  2. 分段加解密:我们可以判断,当数据超过最大值则采用分段加密的方式,每次加解密最大值117字节。这种方式还是上面说的那样,虽然可以实现加解密,但是因为加密数据大,导致加解密时间很长。分段加密代码如下。
  3. 第三种也是大家推荐的,就是加解密用AES对称加密,然后用RSA的公钥加密AES的私钥,最后用RSA的私钥解密出来AES的私钥,再AES对称解密。详见:RSA配合AES加密
分段加解密代码
 /**
     * 最大加密字节数,超出最大字节数需要分组加密
     */
    private static int MAX_ENCRYPT_BLOCK = 117;

    /**
     * 最大解密字节数,超出最大字节数需要分组解密
     */
    private static int MAX_DECRYPT_BLOCK = 128;

    /**
     * @Author: TheBigBlue
     * @Description: 解密
     * @Date: 2019/9/23
     * @Param str: 加密的base64串
     * @Param privateKey: Base64私钥串
     * @Return: 明文
     **/
    public static String decrypt(String inputData, String privateKey) throws Exception {
        //64位解码加密后的字符串
        byte[] inputBytes = Base64.decodeBase64(inputData.getBytes(CHARSET));
        //base64编码的私钥
        byte[] keyBytes = Base64.decodeBase64(privateKey.getBytes(CHARSET));
        RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance(KEY_ALGORITHM).generatePrivate(new PKCS8EncodedKeySpec(keyBytes));
        //RSA解密
        Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, priKey);
        byte[] resultBytes = doFinalBySegment(inputBytes, cipher, MAX_DECRYPT_BLOCK);
        return new String(resultBytes, CHARSET);
    }

    /**
     * @Author: TheBigBlue
     * @Description: 加密
     * @Date: 2019/9/23
     * @Param data: 需要加密的数据
     * @Param publicKey: Base64公钥串
     * @Return:
     **/
    public static String encrypt(String inputData, String publicKey) throws Exception {
        // 取得公钥
        Key key = KeyFactory.getInstance(KEY_ALGORITHM).generatePublic(new X509EncodedKeySpec(Base64.decodeBase64(publicKey.getBytes(CHARSET))));
        // 对数据加密
        Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, key);
        byte[] resultBytes = doFinalBySegment(inputData.getBytes(CHARSET), cipher, MAX_ENCRYPT_BLOCK);
        return new String(Base64.encodeBase64(resultBytes), CHARSET);
    }

    /**
     * @Author: TheBigBlue
     * @Description: 分段加解密
     * @Date: 2019/9/24
     * @Return:
     **/
    private static byte[] doFinalBySegment(byte[] inputBytes, Cipher cipher, int maxLength) throws Exception {
        int inputLenth = inputBytes.length;
        LOGGER.info("数据超出最大字节数进行分段加解密:maxLength={}, inputLength={}", maxLength, inputLenth);
        // 标识
        int offSet = 0;
        byte[] cache;
        byte[] resultBytes = {};
        while (inputLenth - offSet > 0) {
            //超出最大字节数分组加密
            if (inputLenth - offSet > maxLength) {
                cache = cipher.doFinal(inputBytes, offSet, maxLength);
                offSet += maxLength;
            } else {
                //直接加密
                cache = cipher.doFinal(inputBytes, offSet, inputLenth - offSet);
                offSet = inputLenth;
            }
            resultBytes = Arrays.copyOf(resultBytes, resultBytes.length + cache.length);
            System.arraycopy(cache, 0, resultBytes, resultBytes.length - cache.length, cache.length);
        }
        return resultBytes;
    }

你可能感兴趣的:(Java)