1、什么是非对称加密
使用一对(2个)密钥:一个用于加密信息,另一个则用于解密信息。有“公钥(Public Key)”和“私钥(Private Key)”之分。
非对称加密的“公钥”和“私钥”是成对出现(就像“梁山伯”与“祝英台”一样,世界上独一无二的一对),需要使用工具一起同时生成。但是通过公钥推算不出私钥是什么,同样的,通过私钥也推算不出公钥长什么样(“梁山伯”丢失了那“祝英台”也失去意义)。
按照密钥依据性质划分,将其中的一个向外界公开,称为公钥;另一个则自己保留,称为私钥。
大多数情况下,公钥(Public key)用于数据加密,私钥(Private key)用于数据解密。但是并不是绝对的。也就是说:可以使用公钥加密数据,然后使用私钥解密数据。也可以使用私钥加密数据,然后使用公钥解密数据。
但是:
1、私钥加密的数据,只能对应的公钥才能解密出来,私钥自己无法解密。
2、公钥加密的数据,也是只有对应的私钥才能解密出来,公钥自己也无法解密。
2、使用 Java 原生编写非对称加解密工具类
package com.study.util;
import java.security.*;
import javax.crypto.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
/**
* @author CSDN 流放深圳
* @description Java原生的非对称加密工具类
* @create 2024-04-13 12:30
* @since 1.0.0
*/
public class NativeSecurityUtil {
/**
* 生成一对公钥&私钥(仅供测试使用,实际应用上只能生成一次,然后把公钥和私钥保存下来。切忌在业务中每次都调用此方法,否则造成秘钥丢失,数据不可解密!!!)
* @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 cipherForEncryption = Cipher.getInstance("RSA");
cipherForEncryption.init(Cipher.ENCRYPT_MODE, privateObj);
//要加密的字符串转为 byte 类型
byte[] originalBytes = str.getBytes("UTF-8");
byte[] encryptedBytes = cipherForEncryption.doFinal(originalBytes);
result = Base64.getEncoder().encodeToString(encryptedBytes);
}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 cipherForDecryption = Cipher.getInstance("RSA");
cipherForDecryption.init(Cipher.DECRYPT_MODE, publicObj);
byte[] decryptedBytes = cipherForDecryption.doFinal(Base64.getDecoder().decode(str));
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 cipherForEncryption = Cipher.getInstance("RSA");
cipherForEncryption.init(Cipher.ENCRYPT_MODE, publicObj);
//要加密的字符串转为 byte 类型
byte[] originalBytes = str.getBytes("UTF-8");
byte[] encryptedBytes = cipherForEncryption.doFinal(originalBytes);
result = Base64.getEncoder().encodeToString(encryptedBytes);
}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 cipherForDecryption = Cipher.getInstance("RSA");
cipherForDecryption.init(Cipher.DECRYPT_MODE, privateObj);
byte[] decryptedBytes = cipherForDecryption.doFinal(Base64.getDecoder().decode(str));
result = new String(decryptedBytes, "UTF-8");
}catch (Exception e){
e.printStackTrace();
}
return result;
}
/**
* 测试
* @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流放深圳666";
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=MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJd【因数据太长,省略掉后面的结果】
公钥 publicKey=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC5b【因数据太长,省略掉后面的结果】
测试字符串=CSDN流放深圳666
*********************** 【方式一:私钥加密 & 公钥解密】 *************************
【方式一】私钥加密结果 encrypt=Zx5loAwOM2C2mxHz8Yz/rn0e9+/y/9OX【因数据太长,省略掉后面的结果】
【方式一】公钥解密结果 decrypt=CSDN流放深圳666
*********************** 【方式二:公钥加密 & 私钥解密】 *************************
【方式二】公钥加密结果 encrypt222=H7dL/HArwa7QqXT8+hi25pC69w【因数据太长,省略掉后面的结果】
【方式二】私钥解密结果 decrypt222=CSDN流放深圳666
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-aop | JDK动态代理封装,提供非IOC下的切面支持 |
hutool-bloomFilter | 布隆过滤,提供一些Hash算法的布隆过滤 |
hutool-cache | 简单缓存实现 |
hutool-core | 核心,包括Bean操作、日期、各种Util等 |
hutool-cron | 定时任务模块,提供类Crontab表达式的定时任务 |
hutool-crypto | 加密解密模块,提供对称、非对称和摘要算法封装 |
hutool-db | JDBC封装后的数据操作,基于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-json | JSON实现 |
hutool-captcha | 图片验证码实现 |
hutool-poi | 针对POI中Excel和Word的封装 |
hutool-socket | 基于Java的NIO和AIO的Socket封装 |
hutool-jwt | JSON 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 —