java RSA算法實現


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。

當明文長度>秘鑰長度時,需要對較長的明文(密文)進行分段加(解)密。

寫在最后

  哪位大佬如若發現文章存在紕漏之處或需要補充更多內容,歡迎留言!!!

 相關推薦:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM