使用 Java 原生或 Hutool 工具包编写非对称加解密的工具类

1、什么是非对称加密

使用一对(2个)密钥:一个用于加密信息,另一个则用于解密信息。有“公钥(Public Key)”和“私钥(Private Key)”之分。

非对称加密的“公钥”和“私钥”是成对出现(就像“梁山伯”与“祝英台”一样,世界上独一无二的一对),需要使用工具一起同时生成。但是通过公钥推算不出私钥是什么,同样的,通过私钥也推算不出公钥长什么样(“梁山伯”丢失了那“祝英台”也失去意义)。

按照密钥依据性质划分,将其中的一个向外界公开,称为公钥;另一个则自己保留,称为私钥。

大多数情况下,公钥(Public key)用于数据加密,私钥(Private key)用于数据解密。但是并不是绝对的。也就是说:可以使用公钥加密数据,然后使用私钥解密数据。也可以使用私钥加密数据,然后使用公钥解密数据

但是:

1、私钥加密的数据,只能对应的公钥才能解密出来,私钥自己无法解密

2、公钥加密的数据,也是只有对应的私钥才能解密出来,公钥自己也无法解密。

2、使用 Java 原生编写非对称加解密工具类

注意:Java原生的 RSA 对加密解密长度是有限制的,加密长度不能超过 117 个字节,解密长度不能超过 128 个字节(否则会报错 Data must not be longer than 117 bytes),需要采用分段加解密的方式处理。

代码示例: 

package com.study.util;

import java.security.*;
import javax.crypto.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

/**
 * @author CSDN 流放深圳
 * @description Java原生的非对称加密工具类
 * @create 2024-04-13 15:55
 * @since 1.0.0
 */
public class NativeSecurityUtil {

    /**
     * 最大加密长度限制
     */
    public static final int MAX_ENCRYPT_LENGTH = 117;

    /**
     * 最大解密长度限制
     */
    public static final int MAX_DECRYPT_LENGTH = 128;


    /**
     * 生成一对公钥&私钥(仅供测试使用,实际应用上只能生成一次,然后把公钥和私钥保存下来。切忌在业务中每次都调用此方法,否则造成秘钥丢失,数据不可解密!!!)
     * @return
     */
    public static Map<String, String> generateKey() {
        Map<String, String> map = new HashMap<>();
        try{
            // 生成密钥对
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
            keyPairGenerator.initialize(1024); // 设置密钥长度为 1024 位
            KeyPair keyPair = keyPairGenerator.generateKeyPair();
            PublicKey publicObj = keyPair.getPublic();
            PrivateKey privateObj = keyPair.getPrivate();
            String publicKey = Base64.getEncoder().encodeToString(publicObj.getEncoded());
            String privateKey = Base64.getEncoder().encodeToString(privateObj.getEncoded());
            map.put("publicKey", publicKey);
            map.put("privateKey", privateKey);
        }catch (Exception e){
            e.printStackTrace();
        }
        return map;
    }


    /****************************** 【方式一:私钥加密 & 公钥解密】  ***********************************************/
    /**
     * 【方式一】私钥加密
     *
     * @param str        待加密字符串
     * @param privateKey 私钥
     * @return
     */
    public static String encryptByPrivateKey(String str, String privateKey) {
        String result = null;
        try{
            byte[] encoded = Base64.getDecoder().decode(privateKey);
            PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(encoded);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            PrivateKey privateObj = keyFactory.generatePrivate(privateKeySpec);
            Cipher cipher = Cipher.getInstance("RSA");
            cipher.init(Cipher.ENCRYPT_MODE, privateObj);

            //加密的字符串长度不能超过 117 个字节,否则报错,需要分段处理
            byte[] resultBytes = getEncryptResult(str, cipher);
            result = Base64.getEncoder().encodeToString(resultBytes);
        }catch (Exception e){
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 【方式一】公钥解密
     *
     * @param str       待解密字符串
     * @param publicKey 公钥
     * @return
     */
    public static String decryptByPublicKey(String str, String publicKey) {
        String result = null;
        try{
            byte[] encoded = Base64.getDecoder().decode(publicKey);
            X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(encoded);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            PublicKey publicObj = keyFactory.generatePublic(publicKeySpec);
            Cipher cipher = Cipher.getInstance("RSA");
            cipher.init(Cipher.DECRYPT_MODE, publicObj);
            byte[] decryptedBytes = getDecryptResult(str, cipher);
            result = new String(decryptedBytes, "UTF-8");
        }catch (Exception e){
            e.printStackTrace();
        }
        return result;
    }


    /****************************** 【方式二:公钥加密 & 私钥解密】  ***********************************************/
    /**
     * 【方式二】公钥加密
     * @param str
     * @param publicKey
     * @return
     */
    public static String encryptByPublicKey(String str, String publicKey) {
        String result = null;
        try{
            byte[] encoded = Base64.getDecoder().decode(publicKey);
            X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(encoded);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            PublicKey publicObj = keyFactory.generatePublic(publicKeySpec);
            Cipher cipher = Cipher.getInstance("RSA");
            cipher.init(Cipher.ENCRYPT_MODE, publicObj);

            //加密的字符串长度不能超过 117 个字节,否则报错,需要分段处理
            byte[] resultBytes = getEncryptResult(str, cipher);
            result = Base64.getEncoder().encodeToString(resultBytes);
        }catch (Exception e){
            e.printStackTrace();
        }
        return result;
    }


    /**
     * 【方式二】私钥解密
     * @param str
     * @param privateKey
     * @return
     */
    public static String decryptByPrivateKey(String str, String privateKey) {
        String result = null;
        try{
            byte[] encoded = Base64.getDecoder().decode(privateKey);
            PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(encoded);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            PrivateKey privateObj = keyFactory.generatePrivate(privateKeySpec);
            Cipher cipher = Cipher.getInstance("RSA");
            cipher.init(Cipher.DECRYPT_MODE, privateObj);
            byte[] decryptedBytes = getDecryptResult(str, cipher);
            result = new String(decryptedBytes, "UTF-8");
        }catch (Exception e){
            e.printStackTrace();
        }
        return result;
    }


    /**
     * 加密的字符串长度不能超过 117 个字节,否则报错,需要分段处理
     * @param str
     * @param cipher
     * @return
     */
    private static byte[] getEncryptResult(String str, Cipher cipher) throws Exception{
        byte[] originalBytes = str.getBytes("UTF-8");
        int originalLength = originalBytes.length;
        int offSet = 0;//偏移量
        byte[] resultBytes = {};
        byte[] cache = {};
        while (originalLength - offSet > 0) {
            if (originalLength - offSet > MAX_ENCRYPT_LENGTH) {
                cache = cipher.doFinal(originalBytes, offSet, MAX_ENCRYPT_LENGTH);
                offSet += MAX_ENCRYPT_LENGTH;
            } else {
                cache = cipher.doFinal(originalBytes, offSet, originalLength - offSet);
                offSet = originalLength;
            }
            resultBytes = Arrays.copyOf(resultBytes, resultBytes.length + cache.length);
            System.arraycopy(cache, 0, resultBytes, resultBytes.length - cache.length, cache.length);
        }
        return resultBytes;
    }

    /**
     * 解密的字符串长度不能超过 128 个字节,否则报错,需要分段处理
     * @param str
     * @param cipher
     * @return
     * @throws Exception
     */
    private static byte[] getDecryptResult(String str, Cipher cipher) throws Exception{
        byte[] decryptedBytes = Base64.getDecoder().decode(str.getBytes("UTF-8"));
        int originalLength = decryptedBytes.length;
        int offSet = 0;//偏移量
        byte[] resultBytes = {};
        byte[] cache = {};
        while (originalLength - offSet > 0) {
            if (originalLength - offSet > MAX_DECRYPT_LENGTH) {
                cache = cipher.doFinal(decryptedBytes, offSet, MAX_DECRYPT_LENGTH);
                offSet += MAX_DECRYPT_LENGTH;
            } else {
                cache = cipher.doFinal(decryptedBytes, offSet, originalLength - offSet);
                offSet = originalLength;
            }
            resultBytes = Arrays.copyOf(resultBytes, resultBytes.length + cache.length);
            System.arraycopy(cache, 0, resultBytes, resultBytes.length - cache.length, cache.length);
        }
        return resultBytes;
    }

    /**
     * 测试
     * @param args
     */
    public static void main(String[] args) {
        //首先生成一对公钥&私钥,记得保存下来
        Map<String, String> map = generateKey();
        String privateKey = map.get("privateKey");
        String publicKey = map.get("publicKey");
        System.out.println("私钥 privateKey=" + privateKey);
        System.out.println("公钥 publicKey=" + publicKey);

        String str = "CSDN流放深圳。《观沧海》是东汉末年诗人曹操创作的一首四言诗。这首诗是曹操在碣石山登山望海时,用饱蘸浪漫主义激情的大笔,所勾勒出的大海吞吐日月、包蕴万千的壮丽景象;描绘了祖国河山的雄伟壮丽,既刻画了高山大海的壮阔,更表达了诗人以景托志,胸怀天下的进取精神。全诗语言质朴,想象丰富,气势磅礴,苍凉悲壮。";
        System.out.println("测试字符串=" + str);

        System.out.println("*********************** 【方式一:私钥加密 & 公钥解密】 *************************");
        String encrypt = encryptByPrivateKey(str, privateKey);
        System.out.println("【方式一】私钥加密结果 encrypt=" + encrypt);

        String decrypt = decryptByPublicKey(encrypt, publicKey);
        System.out.println("【方式一】公钥解密结果 decrypt=" + decrypt);

        System.out.println("*********************** 【方式二:公钥加密 & 私钥解密】 *************************");
        String encrypt222 = encryptByPublicKey(str, publicKey);
        System.out.println("【方式二】公钥加密结果 encrypt222=" + encrypt222);

        String decrypt222 = decryptByPrivateKey(encrypt222, privateKey);
        System.out.println("【方式二】私钥解密结果 decrypt222=" + decrypt222);

    }
}

测试结果:

私钥 privateKey=MIICeAIBADANBgkqhkiG9w0BAQ【因数据太长,省略掉后面的结果】
公钥 publicKey=MIGfMA0GCSqGSIb3DQEBAQUAA4GN【因数据太长,省略掉后面的结果】
测试字符串=CSDN流放深圳。《观沧海》是东汉末年诗人曹操创作的一首四言诗。【因数据太长,省略掉后面的结果】
*********************** 【方式一:私钥加密 & 公钥解密】 *************************
【方式一】私钥加密结果 encrypt=JJLLMptoXi8YNLI+HSdOriI8【因数据太长,省略掉后面的结果】
【方式一】公钥解密结果 decrypt=CSDN流放深圳。《观沧海》是东汉末年诗人曹操创作的一【因数据太长,省略掉后面的结果】
*********************** 【方式二:公钥加密 & 私钥解密】 *************************
【方式二】公钥加密结果 encrypt222=Ko3vVYQCvZnuJcxGiHt9EIYbeKSU5ZMg【因数据太长,省略掉后面的结果】
【方式二】私钥解密结果 decrypt222=CSDN流放深圳。《观沧海》是东汉末年【因数据太长,省略掉后面的结果】
 

3、使用 Hutool 工具包编写非对称加解密工具类

这里演示的是 hutool 最新工具包 5.8.27(2024年4月13日)的工具类。hutool 工具包有很多组件,可以查看官网:https://hutool.cn/docs/#/?id=%f0%9f%93%9a%e7%ae%80%e4%bb%8b

模块介绍
hutool-aopJDK动态代理封装,提供非IOC下的切面支持
hutool-bloomFilter布隆过滤,提供一些Hash算法的布隆过滤
hutool-cache简单缓存实现
hutool-core核心,包括Bean操作、日期、各种Util等
hutool-cron定时任务模块,提供类Crontab表达式的定时任务
hutool-crypto加密解密模块,提供对称、非对称和摘要算法封装
hutool-dbJDBC封装后的数据操作,基于ActiveRecord思想
hutool-dfa基于DFA模型的多关键字查找
hutool-extra扩展模块,对第三方封装(模板引擎、邮件、Servlet、二维码、Emoji、FTP、分词等)
hutool-http基于HttpUrlConnection的Http客户端封装
hutool-log自动识别日志实现的日志门面
hutool-script脚本执行封装,例如Javascript
hutool-setting功能更强大的Setting配置文件和Properties封装
hutool-system系统参数调用封装(JVM信息等)
hutool-jsonJSON实现
hutool-captcha图片验证码实现
hutool-poi针对POI中Excel和Word的封装
hutool-socket基于Java的NIO和AIO的Socket封装
hutool-jwtJSON Web Token (JWT)封装实现

可以根据需求对每个模块单独引入,也可以通过引入 hutool-all 方式引入所有模块。 

pom.xml 依赖

        <!-- hutool 加解密工具包 https://mvnrepository.com/artifact/cn.hutool/hutool-crypto -->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-crypto</artifactId>
            <version>5.8.27</version>
        </dependency>

代码示例:

package com.study.util;

import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.RSA;

import java.util.HashMap;
import java.util.Map;

/**
 * @author CSDN 流放深圳
 * @description Hutool 非对称加解密工具类
 * @create 2024-04-13 12:50
 * 参考链接:https://hutool.cn/docs/#/crypto/非对称加密-AsymmetricCrypto?id=%e4%bb%8b%e7%bb%8d
 * @since 1.0.0
 */
public class HutoolSecurityUtil {

    /****************************** 【方式一:私钥加密 & 公钥解密】  ***********************************************/
    /**
     * 【方式一】私钥加密
     *
     * @param str        待加密字符串
     * @param privateKey 私钥
     * @return
     */
    public static String encryptByPrivateKey(String str, String privateKey) {
        if (StrUtil.isEmpty(str)) return null;
        RSA rsa = new RSA(privateKey, null);
        return rsa.encryptBase64(str, KeyType.PrivateKey);
    }

    /**
     * 【方式一】公钥解密
     *
     * @param str       待解密字符串
     * @param publicKey 公钥
     * @return
     */
    public static String decryptByPublicKey(String str, String publicKey) {
        if (StrUtil.isEmpty(str)) return null;
        RSA rsa = new RSA(null, publicKey);
        byte[] decrypt = rsa.decrypt(str, KeyType.PublicKey);
        return StrUtil.str(decrypt, CharsetUtil.CHARSET_UTF_8);
    }


    /****************************** 【方式二:公钥加密 & 私钥解密】  ***********************************************/
    /**
     * 【方式二】公钥加密
     * @param str
     * @param publicKey
     * @return
     */
    public static String encryptByPublicKey(String str, String publicKey) {
        if (StrUtil.isEmpty(str)) return null;
        RSA rsa = new RSA(null, publicKey);
        return rsa.encryptBase64(str, KeyType.PublicKey);
    }

    /**
     * 【方式二】私钥解密
     * @param str
     * @param privateKey
     * @return
     */
    public static String decryptByPrivateKey(String str, String privateKey) {
        if (StrUtil.isEmpty(str)) return null;
        RSA rsa = new RSA(privateKey, null);
        byte[] decrypt = rsa.decrypt(str, KeyType.PrivateKey);
        return StrUtil.str(decrypt, CharsetUtil.CHARSET_UTF_8);
    }



    /**
     * 测试
     *
     * @param args
     */
    public static void main(String[] args) {
        //首先生成一对公钥&私钥,记得保存下来
        Map<String, String> map = generateKey();
        String privateKey = map.get("privateKey");
        String publicKey = map.get("publicKey");
        System.out.println("私钥 privateKey=" + privateKey);
        System.out.println("公钥 publicKey=" + publicKey);

        String str = "让天下没有难写的代码 Come on baby!";
        System.out.println("测试字符串=" + str);

        System.out.println("*********************** 【方式一:私钥加密 & 公钥解密】 *************************");
        String encrypt = encryptByPrivateKey(str, privateKey);
        System.out.println("【方式一】私钥加密结果 encrypt=" + encrypt);

        String decrypt = decryptByPublicKey(encrypt, publicKey);
        System.out.println("【方式一】公钥解密结果 decrypt=" + decrypt);

        System.out.println("*********************** 【方式二:公钥加密 & 私钥解密】 *************************");
        String encrypt222 = encryptByPublicKey(str, publicKey);
        System.out.println("【方式二】公钥加密结果 encrypt222=" + encrypt222);

        String decrypt222 = decryptByPrivateKey(encrypt222, privateKey);
        System.out.println("【方式二】私钥解密结果 decrypt222=" + decrypt222);


    }

    /**
     * 生成一对公钥&私钥(仅供测试使用,实际应用上只能生成一次,然后把公钥和私钥保存下来。切忌在业务中每次都调用此方法,否则造成秘钥丢失,数据不可解密!!!)
     */
    public static Map<String, String> generateKey() {
        Map<String, String> map = new HashMap<>();
        RSA rsa = new RSA();
        String privateKey = rsa.getPrivateKeyBase64();//获得私钥
        String publicKey = rsa.getPublicKeyBase64();//获得公钥
        map.put("privateKey", privateKey);
        map.put("publicKey", publicKey);
        return map;
    }

}

测试结果:

私钥 privateKey=MIICdQIBADANBgkqhkiG9w【因数据太长,省略掉后面的结果】
公钥 publicKey=MIGfMA0GCSqGSIb3DQEBAQUAA4GN【因数据太长,省略掉后面的结果】
测试字符串=让天下没有难写的代码 Come on baby!
*********************** 【方式一:私钥加密 & 公钥解密】 *************************
【方式一】私钥加密结果 encrypt=khpoO4WQbo3TDW3+WUo【因数据太长,省略掉后面的结果】
【方式一】公钥解密结果 decrypt=让天下没有难写的代码 Come on baby!
*********************** 【方式二:公钥加密 & 私钥解密】 *************************
【方式二】公钥加密结果 encrypt222=TrXA7AjAchNwUPfIx7【因数据太长,省略掉后面的结果】
【方式二】私钥解密结果 decrypt222=让天下没有难写的代码 Come on baby!

需要注意的是,使用工具类生成一对公钥和私钥后,需要保存起来,且私钥不能对外泄露,否则后续找不到,直接导致之前的数据无法加解密。

—  end —

  • 24
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值