Java使用RSA加密解密
简介
项目需求中要求前端传输的隐私信息需要进行加密传参,后台需要接收加密参数进行解密,所以查找资料学习如何解密。在过程中学到了相关知识,在这里记录下学习笔记。话不多说上代码。
package com.gxwljs.common.utils.sign;
import com.gxwljs.common.enums.CptError;
import com.gxwljs.common.exception.ServiceException;
import org.apache.commons.codec.binary.Base64;
import org.apache.poi.util.IOUtils;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;
/**
* RSA 加密工具
*
* @author CPT
* @date 2023/4/10 11:32
*/
public class RSAEncryptUtil {
public static final String RSA_ALGORITHM = "RSA";
/**
* 生成公钥密钥
*
* @author CPT
* @date 2023/4/10 16:15
* @param keySize 密钥长度
* @return 结果
*/
public static Map<String, String> createKeys(int keySize) {
try {
// 安全密钥生成器
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(RSA_ALGORITHM);
// 初始化密钥长度 长度一般是2的n次方
// 需要注意的是,使用更长的密钥长度会增加加密和解密的计算量。
// 同时,密钥长度也会影响密文的大小。
// 例如,2048位的RSA密钥可以加密的最大数据块大小为245字节,而3072位的RSA密钥可以加密的最大数据块大小为371字节。
// 因此,在选择RSA密钥长度时,需要综合考虑安全性、性能和应用需求等因素,并选择合适的密钥长度。
keyPairGenerator.initialize(keySize);
// 生成密钥对
KeyPair keyPair = keyPairGenerator.generateKeyPair();
PublicKey publicKey = keyPair.getPublic();
// 将字节数组进行URL安全的Base64编码
String publicKeyStr = Base64.encodeBase64URLSafeString(publicKey.getEncoded());
PrivateKey privateKey = keyPair.getPrivate();
String privateKeyStr = Base64.encodeBase64URLSafeString(privateKey.getEncoded());
// 返回数据
Map<String, String> result = new HashMap<>();
result.put("publicKey", publicKeyStr);
result.put("privateKey" ,privateKeyStr);
return result;
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
throw new ServiceException("No such algorithm -->[" + RSA_ALGORITHM + "]");
}
}
/**
* 获取公钥
*
* @author CPT
* @date 2023/4/10 14:05
* @param publicKey 公钥
* @return 公钥
*/
public static RSAPublicKey getPublicKey(String publicKey) {
try {
// 获取密钥工厂实例
KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
// 将公钥转换为一种标准格式
// 因为我们创建密钥的时候是进行URL安全的Base64编码,所以在使用时需要解析原本的内容
X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKey));
// 将上面的密钥规范转换成公钥对象,并返回
return (RSAPublicKey) keyFactory.generatePublic(x509EncodedKeySpec);
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
e.printStackTrace();
throw new ServiceException("Get public key error!");
}
}
/**
* 获取私钥
*
* @author CPT
* @date 2023/4/10 14:05
* @param privateKey 私钥
* @return 私钥
*/
public static RSAPrivateKey getPrivateKey(String privateKey) {
try {
// 获取密钥工厂实例
KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
// 将私钥转换为一种标准格式
// 因为我们拿到密钥后进行URL安全的Base64编码,所以在使用时需要解析原本的内容
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKey));
// 将上面的密钥规范转换成私钥对象,并返回
return (RSAPrivateKey) keyFactory.generatePrivate(pkcs8EncodedKeySpec);
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
e.printStackTrace();
throw new ServiceException("Get private key error!");
}
}
/**
* 加密
*
* @author CPT
* @date 2023/4/10 14:45
* @param text 明文
* @return 密文
*/
public static String encrypt(String text, RSAPublicKey publicKey) {
try {
// 获取一个Cipher对象,该对象可以执行各种加密和解密操作
Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
// 第一个参数决定执行加密操作
// 第二参数使用公钥执行
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
// 将字节数组进行URL安全的Base64编码,并返回
return Base64.encodeBase64URLSafeString(rsaSplitCodec(cipher, Cipher.ENCRYPT_MODE, text.getBytes(StandardCharsets.UTF_8),
publicKey.getModulus().bitLength()));
} catch (NoSuchPaddingException | InvalidKeyException | NoSuchAlgorithmException e) {
throw new ServiceException(CptError.ENCRYPT_ERROR.getMsg(), CptError.ENCRYPT_ERROR.getCode());
}
}
/**
* 解密
*
* @author CPT
* @date 2023/4/10 15:33
* @param text 密文
* @return 明文
*/
public static String decrypt(String text, RSAPrivateKey privateKey) {
try {
// 获取一个Cipher对象,该对象可以执行各种加密和解密操作
Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
// 第一个参数决定执行解密操作
// 第二参数使用私钥执行
cipher.init(Cipher.DECRYPT_MODE, privateKey);
// 将解密出来的内容转成字符串
return new String(rsaSplitCodec(cipher, Cipher.DECRYPT_MODE, text.getBytes(StandardCharsets.UTF_8),
privateKey.getModulus().bitLength()));
} catch (NoSuchPaddingException | InvalidKeyException | NoSuchAlgorithmException e) {
throw new ServiceException(CptError.DECRYPT_ERROR.getMsg(), CptError.DECRYPT_ERROR.getCode());
}
}
/**
* 分割加密/解密
*
* @author CPT
* @date 2023/4/10 15:29
* @param cipher 加密器,可以是加密模式或解密模式。
* @param opMode 操作模式,可以是加密模式或解密模式。
* @param data 待加密或解密的数据。
* @param keySize 密钥的长度。
* @return 加密后密文
*/
private static byte[] rsaSplitCodec(Cipher cipher, int opMode, byte[] data, int keySize) {
// 计算最大分块大小:如果是解密模式,则需要先对数据进行Base64解码;根据操作模式和密钥长度计算出最大分块大小。
int maxBlock = 0;
if (opMode == Cipher.DECRYPT_MODE) {
data = Base64.decodeBase64(data);
maxBlock = keySize / 8;
} else {
maxBlock = keySize / 8 - 11;
}
// 初始化输出流和偏移量:准备一个字节数组输出流和偏移量变量。
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int offSet = 0;
byte[] buff;
int i = 0;
try {
// 分块处理数据:使用cipher对数据进行分块加密或解密,分块大小为最大分块大小;将每个分块写入输出流。
while (data.length > offSet) {
if (data.length - offSet > maxBlock) {
buff = cipher.doFinal(data, offSet, maxBlock);
} else {
buff = cipher.doFinal(data, offSet, data.length - offSet);
}
baos.write(buff, 0, buff.length);
i++;
offSet = i * maxBlock;
}
} catch (IllegalBlockSizeException | BadPaddingException e) {
e.printStackTrace();
throw new RuntimeException("encrypt/decrypt error!");
}
// 返回处理后的数据:将输出流中的数据转换为字节数组并返回。
byte[] resultData = baos.toByteArray();
IOUtils.closeQuietly(baos);
return resultData;
}
}