RSA 工具包

CreatedAt: 20200813
JDK Version: Oracle JDK 1.8.0_202

package com.mrathena.toolkit;

import com.mrathena.exception.ServiceException;

import javax.crypto.Cipher;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.RSAPrivateKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

/**
 * RSA 加密工具
 * <p>
 * 公钥加密/私钥加密/公钥解密/私钥解密
 * 私钥签名/公钥验签
 * 密钥生成/密钥转换
 * <p>
 * *******************************************
 * 字符串格式的密钥在没有特殊说明时都为BASE64编码格式 *
 * 字符串格式的签名在没有特殊说明时都为BASE64编码格式 *
 * 字符串格式的密文在没有特殊说明时都为BASE64编码格式 *
 * *******************************************
 * <p>
 * Java Cryptography Architecture
 * Standard Algorithm Name Documentation for JDK 8
 * Java密码体系结构 JDK 8的标准算法名称文档
 * https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html
 * https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#KeyPairGenerator
 * https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#KeyFactory
 * https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#algspec
 * https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#Cipher
 * https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#Signature
 * <p>
 * 参考
 * java RSA加密解密实现(含分段加密)
 * https://www.cnblogs.com/jiafuwei/p/7054500.html
 * JDK安全模块JCE核心Cipher使用详解
 * https://blog.csdn.net/zcmain/article/details/90640797
 * 加密与安全:非对称加密算法 RSA 1024 公钥、秘钥、明文和密文长度
 * https://blog.csdn.net/liwei16611/article/details/83751851
 */
public final class RSAKit {

    private RSAKit() {}

    /**
     * 加密算法
     */
    private static final String ALGORITHM = "RSA";
    /**
     * 默认密钥长度(位),RSA密钥长度必须是8的倍数且必须大于等于512,建议1024起步(1024以下已有破解先例)
     */
    private static final int DEFAULT_KEY_SIZE = 1024;
    /**
     * 单例 KeyFactory, 用于转换字符串公私钥到标准公私钥
     */
    private static volatile KeyFactory instance;
    /**
     * UTF-8编码
     */
    private static final Charset UTF8 = StandardCharsets.UTF_8;

    // 以下是密钥相关方法 ---------- ---------- ---------- ---------- ----------

    /**
     * 生成公私钥对
     *
     * @param keySize 密钥长度, RSA密钥长度必须是8的倍数且必须大于等于512,建议1024起步(1024以下已有破解先例)
     */
    public static KeyPair generateKeyPair(int keySize) {
        try {
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(ALGORITHM);
            keyPairGenerator.initialize(keySize);
            return keyPairGenerator.generateKeyPair();
        } catch (Throwable cause) {
            throw new ServiceException(cause);
        }
    }

    /**
     * 生成公私钥对(1024位)
     */
    public static KeyPair generateKeyPair() {
        return generateKeyPair(DEFAULT_KEY_SIZE);
    }

    /**
     * 生成公私钥对(2048位)
     */
    public static KeyPair generateKeyPairWithKeySize2048() {
        return generateKeyPair(2048);
    }

    /**
     * 生成公私钥对(4096位)
     */
    public static KeyPair generateKeyPairWithKeySize4096() {
        return generateKeyPair(4096);
    }

    /**
     * 单例获取 KeyFactory
     */
    private static KeyFactory getKeyFactory() {
        try {
            if (null == instance) {
                synchronized (RSAKit.class) {
                    if (null == instance) {
                        instance = KeyFactory.getInstance(ALGORITHM);
                    }
                }
            }
            return instance;
        } catch (Throwable cause) {
            throw new ServiceException(cause);
        }
    }

    /**
     * 字符串公钥转标准公钥
     */
    public static PublicKey toPublicKey(String publicKeyStr) {
        try {
            return getKeyFactory().generatePublic(new X509EncodedKeySpec(decode(publicKeyStr)));
        } catch (Throwable cause) {
            throw new ServiceException(cause);
        }
    }

    /**
     * 字符串私钥转标准私钥
     */
    public static PrivateKey toPrivateKey(String privateKeyStr) {
        try {
            return getKeyFactory().generatePrivate(new PKCS8EncodedKeySpec(decode(privateKeyStr)));
        } catch (Throwable cause) {
            throw new ServiceException(cause);
        }
    }

    /**
     * 标准密钥转字符串密钥
     */
    public static String toKeyStr(Key key) {
        return encode(key.getEncoded());
    }

    // 以下是公钥加密相关方法 ---------- ---------- ---------- ---------- ----------

    /**
     * 公钥加密
     */
    public static byte[] encryptByPublicKey(PublicKey publicKey, byte[] data) {
        return encryptByKey(publicKey, data);
    }

    /**
     * 公钥加密
     */
    public static byte[] encryptByPublicKey(PublicKey publicKey, String data) {
        return encryptByKey(publicKey, data.getBytes(UTF8));
    }

    /**
     * 公钥加密
     */
    public static byte[] encryptByPublicKey(String publicKeyStr, byte[] data) {
        return encryptByKey(toPublicKey(publicKeyStr), data);
    }

    /**
     * 公钥加密
     */
    public static byte[] encryptByPublicKey(String publicKeyStr, String data) {
        return encryptByKey(toPublicKey(publicKeyStr), data.getBytes(UTF8));
    }

    /**
     * 公钥加密
     */
    public static String encryptToStringByPublicKey(PublicKey publicKey, byte[] data) {
        return encode(encryptByKey(publicKey, data));
    }

    /**
     * 公钥加密
     */
    public static String encryptToStringByPublicKey(PublicKey publicKey, String data) {
        return encode(encryptByKey(publicKey, data.getBytes(UTF8)));
    }

    /**
     * 公钥加密
     */
    public static String encryptToStringByPublicKey(String publicKeyStr, byte[] data) {
        return encode(encryptByKey(toPublicKey(publicKeyStr), data));
    }

    /**
     * 公钥加密
     */
    public static String encryptToStringByPublicKey(String publicKeyStr, String data) {
        return encode(encryptByKey(toPublicKey(publicKeyStr), data.getBytes(UTF8)));
    }

    // 以下是公钥解密相关方法 ---------- ---------- ---------- ---------- ----------

    /**
     * 公钥解密
     */
    public static byte[] decryptByPublicKey(PublicKey publicKey, byte[] data) {
        return decryptByKey(publicKey, data);
    }

    /**
     * 公钥解密
     */
    public static byte[] decryptByPublicKey(PublicKey publicKey, String data) {
        return decryptByKey(publicKey, decode(data));
    }

    /**
     * 公钥解密
     */
    public static byte[] decryptByPublicKey(String publicKeyStr, byte[] data) {
        return decryptByKey(toPublicKey(publicKeyStr), data);
    }

    /**
     * 公钥解密
     */
    public static byte[] decryptByPublicKey(String publicKeyStr, String data) {
        return decryptByKey(toPublicKey(publicKeyStr), decode(data));
    }

    /**
     * 公钥解密
     */
    public static String decryptToStringByPublicKey(PublicKey publicKey, byte[] data) {
        return new String(decryptByKey(publicKey, data), UTF8);
    }

    /**
     * 公钥解密
     */
    public static String decryptToStringByPublicKey(PublicKey publicKey, String data) {
        return new String(decryptByKey(publicKey, decode(data)), UTF8);
    }

    /**
     * 公钥解密
     */
    public static String decryptToStringByPublicKey(String publicKeyStr, byte[] data) {
        return new String(decryptByKey(toPublicKey(publicKeyStr), data), UTF8);
    }

    /**
     * 公钥解密
     */
    public static String decryptToStringByPublicKey(String publicKeyStr, String data) {
        return new String(decryptByKey(toPublicKey(publicKeyStr), decode(data)), UTF8);
    }

    // 以下是私钥加密相关方法 ---------- ---------- ---------- ---------- ----------

    /**
     * 私钥加密
     */
    public static byte[] encryptByPrivateKey(PrivateKey privateKey, byte[] data) {
        return encryptByKey(privateKey, data);
    }

    /**
     * 私钥加密
     */
    public static byte[] encryptByPrivateKey(PrivateKey privateKey, String data) {
        return encryptByKey(privateKey, data.getBytes(UTF8));
    }

    /**
     * 私钥加密
     */
    public static byte[] encryptByPrivateKey(String privateKeyStr, byte[] data) {
        return encryptByKey(toPrivateKey(privateKeyStr), data);
    }

    /**
     * 私钥加密
     */
    public static byte[] encryptByPrivateKey(String privateKeyStr, String data) {
        return encryptByKey(toPrivateKey(privateKeyStr), data.getBytes(UTF8));
    }

    /**
     * 私钥加密
     */
    public static String encryptToStringByPrivateKey(PrivateKey privateKey, byte[] data) {
        return encode(encryptByKey(privateKey, data));
    }

    /**
     * 私钥加密
     */
    public static String encryptToStringByPrivateKey(PrivateKey privateKey, String data) {
        return encode(encryptByKey(privateKey, data.getBytes(UTF8)));
    }

    /**
     * 私钥加密
     */
    public static String encryptToStringByPrivateKey(String privateKeyStr, byte[] data) {
        return encode(encryptByKey(toPrivateKey(privateKeyStr), data));
    }

    /**
     * 私钥加密
     */
    public static String encryptToStringByPrivateKey(String privateKeyStr, String data) {
        return encode(encryptByKey(toPrivateKey(privateKeyStr), data.getBytes(UTF8)));
    }

    // 以下是私钥解密相关方法 ---------- ---------- ---------- ---------- ----------

    /**
     * 私钥解密
     */
    public static byte[] decryptByPrivateKey(PrivateKey privateKey, byte[] data) {
        return decryptByKey(privateKey, data);
    }

    /**
     * 私钥解密
     */
    public static byte[] decryptByPrivateKey(PrivateKey privateKey, String data) {
        return decryptByKey(privateKey, decode(data));
    }

    /**
     * 私钥解密
     */
    public static byte[] decryptByPrivateKey(String privateKeyStr, byte[] data) {
        return decryptByKey(toPrivateKey(privateKeyStr), data);
    }

    /**
     * 私钥解密
     */
    public static byte[] decryptByPrivateKey(String privateKeyStr, String data) {
        return decryptByKey(toPrivateKey(privateKeyStr), decode(data));
    }

    /**
     * 私钥解密
     */
    public static String decryptToStringByPrivateKey(PrivateKey privateKey, byte[] data) {
        return new String(decryptByKey(privateKey, data), UTF8);
    }

    /**
     * 私钥解密
     */
    public static String decryptToStringByPrivateKey(PrivateKey privateKey, String data) {
        return new String(decryptByKey(privateKey, decode(data)), UTF8);
    }

    /**
     * 私钥解密
     */
    public static String decryptToStringByPrivateKey(String privateKeyStr, byte[] data) {
        return new String(decryptByKey(toPrivateKey(privateKeyStr), data), UTF8);
    }

    /**
     * 私钥解密
     */
    public static String decryptToStringByPrivateKey(String privateKeyStr, String data) {
        return new String(decryptByKey(toPrivateKey(privateKeyStr), decode(data)), UTF8);
    }

    // 以下是密钥加密解密通用依赖方法 ---------- ---------- ---------- ---------- ----------

    /**
     * 密钥加密
     * <p>
     * RSA加密时最大分段长度
     * 512  - 512/8-11=64-11=53
     * 1024 - 1024/8-11=128-11=117
     * 2048 - 2048/8-11=256-11=245
     * 4096 - 4096/8-11=512-11=501
     * 加密时,最大分段长度,是密钥长度的八分之一减十一,加密分段你长度有一个可选范围
     * 1024可以使用117,2048也可以使用117,但最大可使用245,4096也可以使用117,但最大可使用501
     */
    private static byte[] encryptByKey(Key key, byte[] data) {
        try {
            Cipher cipher = Cipher.getInstance(ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, key);
            return encryptByCipher(cipher, getBlockLength(key) / 8 - 11, data);
        } catch (Throwable cause) {
            throw new ServiceException(cause);
        }
    }

    /**
     * 密钥解密
     * <p>
     * RSA解密时分段长度
     * 512  - 512/8=64
     * 1024 - 1024/8=128
     * 2048 - 2048/8=256
     * 4096 - 4096/8=512
     * 这个长度是固定的,正好是密钥长度的八分之一,不像加密时是一个范围
     */
    private static byte[] decryptByKey(Key key, byte[] data) {
        try {
            Cipher cipher = Cipher.getInstance(ALGORITHM);
            cipher.init(Cipher.DECRYPT_MODE, key);
            return encryptByCipher(cipher, getBlockLength(key) / 8, data);
        } catch (Throwable cause) {
            throw new ServiceException(cause);
        }
    }

    /**
     * 获取加解密基础分段长度
     */
    private static int getBlockLength(Key key) {
        try {
            if (key instanceof PublicKey) {
                // 获取公钥长度(位)
                return getKeyFactory().getKeySpec(key, RSAPublicKeySpec.class).getModulus().toString(2).length();
            } else {
                // 获取私钥长度(位)
                return getKeyFactory().getKeySpec(key, RSAPrivateKeySpec.class).getModulus().toString(2).length();
            }
        } catch (Throwable cause) {
            throw new ServiceException(cause);
        }
    }

    /**
     * 加密解密流程
     * 加密过程使用二维数组存储分块加密结果,最终转换为一维数组存储完整加密结果
     * 也可使用ByteArrayOutputStream的write()方法把分块解密结果写到流,toByteArray()方法转换称为完成加密结果
     *
     * @param data           数据
     * @param cipher         密码器
     * @param maxBlockLength 加密/解密块大小, 加密117, 解密128
     * @return 加密/解密结果
     */
    private static byte[] encryptByCipher(Cipher cipher, int maxBlockLength, byte[] data) {
        try {
            int lines = data.length % maxBlockLength == 0 ? data.length / maxBlockLength : data.length / maxBlockLength + 1;
            byte[][] tempArray = new byte[lines][];
            for (int i = 0; i < lines; i++) {
                int offset = i * maxBlockLength;
                tempArray[i] = cipher.doFinal(data, offset, i == lines - 1 ? data.length - offset : maxBlockLength);
            }
            int tempArrayTotalLength = 0;
            for (byte[] bytes : tempArray) {
                tempArrayTotalLength = tempArrayTotalLength + bytes.length;
            }
            byte[] targetArray = new byte[tempArrayTotalLength];
            int index = 0;
            for (byte[] byteArray : tempArray) {
                for (byte aByte : byteArray) {
                    targetArray[index] = aByte;
                    index++;
                }
            }
            return targetArray;
        } catch (Throwable cause) {
            throw new ServiceException(cause);
        }
    }

    // 以下是私钥签名相关方法 ---------- ---------- ---------- ---------- ----------

    /**
     * 私钥签名
     *
     * @param privateKey 私钥
     * @param algorithm  签名算法,NONEwithRSA,MD2withRSA,MD5withRSA,SHA1withRSA,SHA224withRSA,SHA256withRSA,SHA384withRSA,SHA512withRSA,SHA512/224withRSA,SHA512/256withRSA
     * @param data       待签名数据
     */
    private static byte[] sign(PrivateKey privateKey, String algorithm, byte[] data) {
        try {
            Signature signature = Signature.getInstance(algorithm);
            signature.initSign(privateKey);
            signature.update(data);
            return signature.sign();
        } catch (Throwable cause) {
            throw new ServiceException(cause);
        }
    }

    /**
     * 私钥签名
     */
    public static String signToString(PrivateKey privateKey, String algorithm, byte[] data) {
        return encode(sign(privateKey, algorithm, data));
    }

    /**
     * 私钥签名
     */
    public static String signToString(String privateKeyStr, String algorithm, byte[] data) {
        return encode(sign(toPrivateKey(privateKeyStr), algorithm, data));
    }

    /**
     * 私钥签名
     */
    public static String signToString(PrivateKey privateKey, String algorithm, String data) {
        return encode(sign(privateKey, algorithm, data.getBytes(UTF8)));
    }

    /**
     * 私钥签名
     */
    public static String signToString(String privateKeyStr, String algorithm, String data) {
        return encode(sign(toPrivateKey(privateKeyStr), algorithm, data.getBytes(UTF8)));
    }

    // 以下是公钥验签相关方法 ---------- ---------- ---------- ---------- ----------

    /**
     * 公钥验签
     *
     * @param publicKey 公钥
     * @param algorithm 签名算法,NONEwithRSA,MD2withRSA,MD5withRSA,SHA1withRSA,SHA224withRSA,SHA256withRSA,SHA384withRSA,SHA512withRSA,SHA512/224withRSA,SHA512/256withRSA
     * @param data      待验签数据
     * @param sign      签名字符串(BASE64编码)
     */
    public static boolean verify(PublicKey publicKey, String algorithm, byte[] data, String sign) {
        try {
            Signature signature = Signature.getInstance(algorithm);
            signature.initVerify(publicKey);
            signature.update(data);
            return signature.verify(decode(sign));
        } catch (Throwable cause) {
            throw new ServiceException(cause);
        }
    }

    /**
     * 公钥验签
     */
    public static boolean verify(String publicKeyStr, String algorithm, byte[] data, String sign) {
        return verify(toPublicKey(publicKeyStr), algorithm, data, sign);
    }

    /**
     * 公钥验签
     */
    public static boolean verify(PublicKey publicKey, String algorithm, String data, String sign) {
        return verify(publicKey, algorithm, data.getBytes(UTF8), sign);
    }

    /**
     * 公钥验签
     */
    public static boolean verify(String publicKeyStr, String algorithm, String data, String sign) {
        return verify(toPublicKey(publicKeyStr), algorithm, data.getBytes(UTF8), sign);
    }

    /**
     * 编码
     */
    private static String encode(byte[] data) {
        return Base64.getEncoder().encodeToString(data);
    }

    /**
     * 解码
     */
    private static byte[] decode(String data) {
        return Base64.getDecoder().decode(data);
    }

}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343