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。
當明文長度>秘鑰長度時,需要對較長的明文(密文)進行分段加(解)密。
寫在最后
哪位大佬如若發現文章存在紕漏之處或需要補充更多內容,歡迎留言!!!
