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。

当明文长度>秘钥长度时,需要对较长的明文(密文)进行分段加(解)密。

写在最后

  哪位大佬如若发现文章存在纰漏之处或需要补充更多内容,欢迎留言!!!

 相关推荐:

posted @ 2022-02-27 19:46  Marydon  阅读(1061)  评论(0编辑  收藏  举报