RSA算法实现
导包
import code.marydon.encapsulation.dataType.Base64Utils;
import code.marydon.encapsulation.file.IOUtils;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
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.RSAPublicKeySpec;
import java.security.spec.X509EncodedKeySpec;
代码实现
/**
* RSA算法
* @description:
* 1.公钥加密,私钥解密;(推荐使用)
* 2.私钥加密,公钥解密。
* 3.默认公钥加密,默认私钥解密(通常适用于自己调自己,也就是仅用本系统内部使用)
* 4.通过rsaSplitCodec()实现对较长的明文进行分段加密
* 5.如果觉得分段加密多余或者不需要对长文加密,可以将加解密调用rsaSplitCodec()的地方,
* 直接用:cipher.doFinal(bytes)替换即可。
* 另外,分段加密和不分段加密的结果是一致的(短文,因为rsa原则上不支持对长文加密)
* 6.公钥、私钥、加密结果使用的都是base64Encode(),也就是会自动补位;
* 如果不需要补位,请改用base64EncodeURLSafe()
* @author: Marydon
* @date: 2022-02-27 9:35
* @version: 1.0
* @email: marydon20170307@163.com
*/
@Slf4j
public final class RSAUtils extends IOUtils {
/*
* 构造方法私有化
* @attention:
* 该类将不能被实例化,也不能被继承
* @date: 2022/2/27 11:02
* @param:
* @return:
*/
private RSAUtils() {
}
public static final String CHARSET = "UTF-8";
public static final String ALGORITHM_NAME = "RSA";
public static final String ALGORITHM_NAME_ECB_PADDING = "RSA/ECB/PKCS1Padding";
// 最小:512,还可以是:1024,2048
public static final int DEFAULT_KEY_SIZE = 512;
// 默认私钥
private static final String DEFAULT_PRIVATE_KEY_STRING = "MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAocbCrurZGbC5GArEHKlAfDSZi7gFBnd4yxOt0rwTqKBFzGyhtQLu5PRKjEiOXVa95aeIIBJ6OhC2f8FjqFUpawIDAQABAkAPejKaBYHrwUqUEEOe8lpnB6lBAsQIUFnQI/vXU4MV+MhIzW0BLVZCiarIQqUXeOhThVWXKFt8GxCykrrUsQ6BAiEA4vMVxEHBovz1di3aozzFvSMdsjTcYRRo82hS5Ru2/OECIQC2fAPoXixVTVY7bNMeuxCP4954ZkXp7fEPDINCjcQDywIgcc8XLkkPcs3Jxk7uYofaXaPbg39wuJpEmzPIxi3k0OECIGubmdpOnin3HuCP/bbjbJLNNoUdGiEmFL5hDI4UdwAdAiEAtcAwbm08bKN7pwwvyqaCBC//VnEWaq39DCzxr+Z2EIk=";
// 默认公钥
public static final String DEFAULT_PUBLIC_KEY_STRING = "MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKHGwq7q2RmwuRgKxBypQHw0mYu4BQZ3eMsTrdK8E6igRcxsobUC7uT0SoxIjl1WveWniCASejoQtn/BY6hVKWsCAwEAAQ==";
@NotNull
public static String[] createKeyPair(){
return createKeyPair(DEFAULT_KEY_SIZE);
}
/*
* 生成公钥和私钥对
* @attention: 生成的base64字符串会将字符串3位一组,自动用=不全
* 如果不想自动补位,可以使用Base64Utils.encodeURLSafe()
* @date: 2022/2/27 10:54
* @param: keySize
* @return: java.lang.String[]
* base64编码格式的数组
* 0-公钥
* 1-私钥
*/
@NotNull
public static String[] createKeyPair(int keySize){
//为RSA算法创建一个KeyPairGenerator对象
KeyPairGenerator kpg;
try{
kpg = KeyPairGenerator.getInstance(ALGORITHM_NAME);
}catch(NoSuchAlgorithmException e){
throw new IllegalArgumentException("No such algorithm-->[" + ALGORITHM_NAME + "]");
}
//初始化KeyPairGenerator对象,密钥长度
kpg.initialize(keySize);
//生成密匙对
KeyPair keyPair = kpg.generateKeyPair();
//得到公钥
Key publicKey = keyPair.getPublic();
// String publicKeyStr = Base64.encodeBase64URLSafeString(publicKey.getEncoded());
//得到私钥
Key privateKey = keyPair.getPrivate();
// String privateKeyStr = Base64.encodeBase64URLSafeString(privateKey.getEncoded());
// 返回数据
String[] keyPairs = new String[2];
keyPairs[0] = Base64Utils.encode(publicKey.getEncoded());
keyPairs[1] = Base64Utils.encode(privateKey.getEncoded());
return keyPairs;
}
/*
* 得到公钥
* @attention:
* @date: 2022/2/27 11:04
* @param publicKey 密钥字符串(经过base64编码)
* @return: java.security.interfaces.RSAPublicKey
*/
public static RSAPublicKey getPublicKey(String publicKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
//通过X509编码的Key指令获得公钥对象
KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM_NAME);
// X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKey));
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(Base64Utils.decodeToBytes(publicKey));
return (RSAPublicKey) keyFactory.generatePublic(x509KeySpec);
}
/*
* 得到私钥
* @description:
* @date: 2022/2/27 11:27
* @param privateKey: 密钥字符串(经过base64编码)
* @return: java.security.interfaces.RSAPrivateKey
*/
public static RSAPrivateKey getPrivateKey(String privateKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
//通过PKCS#8编码的Key指令获得私钥对象
KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM_NAME);
// PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKey));
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(Base64Utils.decodeToBytes(privateKey));
return (RSAPrivateKey) keyFactory.generatePrivate(pkcs8KeySpec);
}
/*
* 使用默认公钥加密
* @description:
* @date: 2022/2/27 19:56
* @param: plainText
* @return: java.lang.String
*/
public static String publicKeyDefaultEncrypt(String plainText) throws Exception {
if (StringUtils.isEmpty(plainText)) return "";
return publicKeyEncrypt(plainText, getPublicKey(DEFAULT_PUBLIC_KEY_STRING));
}
/*
* 公钥加密
* @description:
* @date: 2022/2/27 11:45
* @param: plainText 明文
* @param: publicKey 公钥
* @return: java.lang.String
*/
public static String publicKeyEncrypt(String plainText, RSAPublicKey publicKey) throws Exception {
if (StringUtils.isEmpty(plainText)) return "";
Cipher cipher = Cipher.getInstance(ALGORITHM_NAME_ECB_PADDING);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
// return Base64.encodeBase64URLSafeString(rsaSplitCodec(cipher, Cipher.ENCRYPT_MODE, data.getBytes(CHARSET), publicKey.getModulus().bitLength()));
return Base64Utils.encode(rsaSplitCodec(cipher, Cipher.ENCRYPT_MODE, plainText.getBytes(CHARSET), publicKey.getModulus().bitLength()));
}
/*
* 使用默认私钥解密
* @description:
* @date: 2022/2/27 20:00
* @param: cipherText
* @return: java.lang.String
*/
@NotNull
public static String privateKeyDefaultDecrypt(String cipherText) throws Exception {
if (StringUtils.isEmpty(cipherText)) return "";
return privateKeyDecrypt(cipherText, getPrivateKey(DEFAULT_PRIVATE_KEY_STRING));
}
/*
* 私钥解密
* @description:
* @date: 2022/2/27 11:55
* @param: plainText 明文
* @param: privateKey 私钥
* @return: java.lang.String
*/
@NotNull
@Contract("_, _ -> new")
public static String privateKeyDecrypt(String cipherText, RSAPrivateKey privateKey) throws Exception {
if (StringUtils.isEmpty(cipherText)) return "";
Cipher cipher = Cipher.getInstance(ALGORITHM_NAME_ECB_PADDING);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
// return new String(rsaSplitCodec(cipher, Cipher.DECRYPT_MODE, Base64.decodeBase64(data), privateKey.getModulus().bitLength()), CHARSET);
return new String(rsaSplitCodec(cipher, Cipher.DECRYPT_MODE, Base64Utils.decodeToBytes(cipherText), privateKey.getModulus().bitLength()), CHARSET);
}
/*
* 私钥加密
* @description:
* @date: 2022/2/27 11:58
* @param: plainText
* @param: privateKey
* @return: java.lang.String
*/
@NotNull
@Contract("_, _ -> new")
public static String privateKeyEncrypt(String plainText, RSAPrivateKey privateKey) throws Exception {
if (StringUtils.isEmpty(plainText)) return "";
Cipher cipher = Cipher.getInstance(ALGORITHM_NAME_ECB_PADDING);
try {
cipher.init(Cipher.ENCRYPT_MODE, privateKey);
} catch (InvalidKeyException e) {
// 因为 IBM JDK 不支持私钥加密, 公钥解密, 所以要反转公私钥
// 也就是说对于解密, 可以通过公钥的参数伪造一个私钥对象欺骗 IBM JDK
RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec(privateKey.getModulus(), privateKey.getPrivateExponent());
Key fakePublicKey = KeyFactory.getInstance(ALGORITHM_NAME).generatePublic(publicKeySpec);
cipher = Cipher.getInstance(ALGORITHM_NAME);
cipher.init(Cipher.ENCRYPT_MODE, fakePublicKey);
}
// return Base64.encodeBase64URLSafeString(rsaSplitCodec(cipher, Cipher.ENCRYPT_MODE, data.getBytes(CHARSET), privateKey.getModulus().bitLength()));
return Base64Utils.encode(rsaSplitCodec(cipher, Cipher.ENCRYPT_MODE, plainText.getBytes(CHARSET), privateKey.getModulus().bitLength()));
}
/*
* 公钥解密
* @description:
* @date: 2022/2/27 12:00
* @param: cipherText
* @param: publicKey
* @return: java.lang.String
*/
@NotNull
@Contract("_, _ -> new")
public static String publicKeyDecrypt(String cipherText, RSAPublicKey publicKey) throws Exception {
if (StringUtils.isEmpty(cipherText)) return "";
Cipher cipher = Cipher.getInstance(ALGORITHM_NAME_ECB_PADDING);
try {
cipher.init(Cipher.DECRYPT_MODE, publicKey);
} catch (InvalidKeyException e) {
// 因为 IBM JDK 不支持私钥加密, 公钥解密, 所以要反转公私钥
// 也就是说对于解密, 可以通过公钥的参数伪造一个私钥对象欺骗 IBM JDK
RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) publicKey;
RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec(rsaPrivateKey.getModulus(), rsaPrivateKey.getPrivateExponent());
Key fakePublicKey = KeyFactory.getInstance(ALGORITHM_NAME).generatePublic(publicKeySpec);
cipher = Cipher.getInstance(ALGORITHM_NAME);//It is a stateful object. so we need to get new one.
cipher.init(Cipher.DECRYPT_MODE, fakePublicKey);
}
// return new String(rsaSplitCodec(cipher, Cipher.DECRYPT_MODE, Base64.decodeBase64(cipherText), publicKey.getModulus().bitLength()), CHARSET);
return new String(rsaSplitCodec(cipher, Cipher.DECRYPT_MODE, Base64Utils.decodeToBytes(cipherText), publicKey.getModulus().bitLength()), CHARSET);
}
/*
* 拆分编码器/解码器
* @attention: RSA加密算法对于加密数据的长度是有要求的:
* 明文长度小于等于密钥长度(Bytes)-11
* @description: 对较长的明文(密文)进行分段加(解)密
* @date: 2022/2/27 12:06
* @param: cipher
* @param: opmode
* @param: datas
* @param: keySize
* @return: byte[]
*/
@NotNull
private static byte[] rsaSplitCodec(Cipher cipher, int opmode, byte[] datas, int keySize){
int maxBlock;
if(opmode == Cipher.DECRYPT_MODE){
maxBlock = keySize / 8;
}else{
maxBlock = keySize / 8 - 11;
}
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offSet = 0;
byte[] buff;
int i = 0;
byte[] resultDatas;
try{
while(datas.length > offSet){
if(datas.length-offSet > maxBlock){
buff = cipher.doFinal(datas, offSet, maxBlock);
}else{
buff = cipher.doFinal(datas, offSet, datas.length - offSet);
}
out.write(buff, 0, buff.length);
i++;
offSet = i * maxBlock;
}
resultDatas = out.toByteArray();
} catch (Exception e) {
throw new RuntimeException("加解密阀值为[" + maxBlock + "]的数据时发生异常", e);
} finally {
closeOutputStream(out);
}
return resultDatas;
}
}
说明:
上述用到的base64工具类和IO工具类是我自己封装的,替换成你自己的就好。
测试
public static void main (String[] args) throws Exception {
String[] keys = RSAUtils.createKeyPair();
String publicKey = keys[0];
String privateKey = keys[1];
System.out.println("公钥: \n\r" + publicKey);
System.out.println("私钥: \n\r" + privateKey);
System.out.println("方式1:公钥加密——私钥解密");
String str = "\n" +
"成长带走的不只是时光\n" +
"还带走了当初那些不害怕失去的勇气\n" +
"让自己忙一点,忙到没时间去思考无关紧要的事,很多事就能悄悄淡忘了\n" +
"时间不一定能证明很多东西\n" +
"但是一定能看透很多东西\n" +
"坚信自己的选择,不动摇,使劲跑,明天会更好";
String encodedData = RSAUtils.publicKeyEncrypt(str, RSAUtils.getPublicKey(publicKey));
System.out.println(encodedData);
System.out.println(RSAUtils.privateKeyDecrypt(encodedData, RSAUtils.getPrivateKey(privateKey)));
// System.out.println("方式2:私钥加密——公钥解密");
// String str = "marydon";
// String encodedData = RSAUtils.privateKeyEncrypt(str, RSAUtils.getPrivateKey(privateKey));
// System.out.println("密文:\r\n" + encodedData);
// System.out.println(RSAUtils.publicKeyDecrypt(encodedData, RSAUtils.getPublicKey(publicKey)));
}
2022年4月6日15:30:48
说明:
RSA加密算法对于加密数据的长度是有要求的:
明文长度小于等于密钥长度(Bytes)-11(1字节=8bit);
密文长度=秘钥长度。
keySize=512bits,明文大小<512/8-11=53Bytes,密文最长=512/8=64Bytes;
keySize=1024bits,明文大小<1024/8-11=117Bytes,密文最长=1024/8=128Bytes;
keySize=2048bits,明文大小<2048/8-11=245Bytes,密文最长=2048/8=256Bytes。
当明文长度>秘钥长度时,需要对较长的明文(密文)进行分段加(解)密。
写在最后
哪位大佬如若发现文章存在纰漏之处或需要补充更多内容,欢迎留言!!!
相关推荐:
- 个人主页
- java sm4国密算法加密、解密
- java sm3加密算法
- java HMAC_SHA1加密算法
- java AES加密、解密(兼容windows和linux)
- java 实现md5加密的三种方式与解密
作者:Marydon