今天和客户端对接RSA算法的加解密功能,后端是Java,客户端是C#实现,C#只支持PKCS#1格式的密钥(PKCS#8受限于Unity环境不支持),Java只支持PKCS#8格式的密钥,这就很尴尬了,于是在GitHub上找有没有类库实现,还好找到了个C#版的PKCS#8支持实现,于是我就将其改造成Java的工具类。

改造自C#项目:https://github.com/xiangyuecn/RSA-csharp

源码见下方,先上使用方法

import com.leocool.common.util.meta.RSAKey;
import org.junit.Test;

import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;

public class RSATest {
    @Test
    public void test() throws Exception {
        String pemPubKey = "-----BEGIN RSA PUBLIC KEY-----\n" +
                "MIGJAoGBAM6boxzV/SlREU0bTwIoWswwBsJCvSxbaBivvwr539OWHyJlke2cn3Yw\n" +
                "1wS0Y3Jd3zG/vaCHFYDMH/ClCtVVBAIqOVWLivdaJE6dv90gVMoZwlfb+ok6psks\n" +
                "a/xRd/0eJY9AvKWDX1lEwhgNy0NmMwCIn4ZAKX4sIdgOrbuMyM2tAgMBAAE=\n" +
                "-----END RSA PUBLIC KEY-----";
        String pemPriKey = "-----BEGIN RSA PRIVATE KEY-----\n" +
                "MIICWwIBAAKBgQDOm6Mc1f0pURFNG08CKFrMMAbCQr0sW2gYr78K+d/Tlh8iZZHt\n" +
                "nJ92MNcEtGNyXd8xv72ghxWAzB/wpQrVVQQCKjlVi4r3WiROnb/dIFTKGcJX2/qJ\n" +
                "OqbJLGv8UXf9HiWPQLylg19ZRMIYDctDZjMAiJ+GQCl+LCHYDq27jMjNrQIDAQAB\n" +
                "AoGABqLXHWD+LaqN7LUY14/mQrK0NN8h5l/VOx3B/B9Egi5LN7addk1WjdxJu7vz\n" +
                "icPcHGP1Oke8eBOWXwteCrYdV8mmU9VzCfafPzFlyIxQXBmvP6+ePe1EwiqLOLt5\n" +
                "eufTD5zeI0SZJAJNHGCtYTBIqr6OfXP6CMOHAd7Y5EX5TgECQQD2XKMNJLogvPis\n" +
                "yqBtf6W8jZ7hOdOabjUr0B0TlPBrPk1hc0eo48UfAif09NuP6TLR3MBMhyaaAWDd\n" +
                "jcCPe96BAkEA1rDbYjM7cPTL84PG13MxAbV3z1hMH5ckUqbnyrEFAALusjKbqoRC\n" +
                "2tH52h0UklKtlI72yMQuwuwfZ0XJwGwxLQJANzfCL/L4aNgSIKB13rUKj71qS+7r\n" +
                "F6T18/D8Y2QxksfvDhWJjTgn+XBBRm0PPS02f8UrsLtmEoSWknHwI/jOgQJAZCWN\n" +
                "wMobQT6/4LEo0RnH038+CzeqEtZjODbpSzC2H2I+zFs1KB6YPRDT1v3XucXdeL4T\n" +
                "umVhVsXkhD0Wtq8BZQJAaS6xMd3h5lm3pLVrceRsAma862PAsZlo9TV1EdnD9ckl\n" +
                "cDevR/7oKyjSVCR7GCD05UxoDZP13gFQodCS5MXv0w==\n" +
                "-----END RSA PRIVATE KEY-----";

        RSAPublicKey pubKey1 = RSAKey.fromPem(pemPubKey).toPublicKey(); //  根据pem标签自动识别转换私钥还是公钥,同时支持PKCS#8和PKCS#1
        RSAPublicKey pubKey2 = RSAKey.fromPem(pemPubKey, true).toPublicKey();   //  指定转换公钥,同时支持PKCS#8和PKCS#1

        RSAPrivateKey priKey1 = RSAKey.fromPem(pemPriKey).toPrivateKey();
        RSAPrivateKey priKey2 = RSAKey.fromPem(pemPriKey, false).toPrivateKey();    //  指定转换私钥

        String pubKey = "MIGJAoGBAM6boxzV/SlREU0bTwIoWswwBsJCvSxbaBivvwr539OWHyJlke2cn3Yw1wS0Y3Jd3zG/vaCHFYDMH/ClCtVVBAIqOVWLivdaJE6dv90gVMoZwlfb+ok6psksa/xRd/0eJY9AvKWDX1lEwhgNy0NmMwCIn4ZAKX4sIdgOrbuMyM2tAgMBAAE=";
        String priKey = "MIICWwIBAAKBgQDOm6Mc1f0pURFNG08CKFrMMAbCQr0sW2gYr78K+d/Tlh8iZZHtnJ92MNcEtGNyXd8xv72ghxWAzB/wpQrVVQQCKjlVi4r3WiROnb/dIFTKGcJX2/qJOqbJLGv8UXf9HiWPQLylg19ZRMIYDctDZjMAiJ+GQCl+LCHYDq27jMjNrQIDAQABAoGABqLXHWD+LaqN7LUY14/mQrK0NN8h5l/VOx3B/B9Egi5LN7addk1WjdxJu7vzicPcHGP1Oke8eBOWXwteCrYdV8mmU9VzCfafPzFlyIxQXBmvP6+ePe1EwiqLOLt5eufTD5zeI0SZJAJNHGCtYTBIqr6OfXP6CMOHAd7Y5EX5TgECQQD2XKMNJLogvPisyqBtf6W8jZ7hOdOabjUr0B0TlPBrPk1hc0eo48UfAif09NuP6TLR3MBMhyaaAWDdjcCPe96BAkEA1rDbYjM7cPTL84PG13MxAbV3z1hMH5ckUqbnyrEFAALusjKbqoRC2tH52h0UklKtlI72yMQuwuwfZ0XJwGwxLQJANzfCL/L4aNgSIKB13rUKj71qS+7rF6T18/D8Y2QxksfvDhWJjTgn+XBBRm0PPS02f8UrsLtmEoSWknHwI/jOgQJAZCWNwMobQT6/4LEo0RnH038+CzeqEtZjODbpSzC2H2I+zFs1KB6YPRDT1v3XucXdeL4TumVhVsXkhD0Wtq8BZQJAaS6xMd3h5lm3pLVrceRsAma862PAsZlo9TV1EdnD9cklcDevR/7oKyjSVCR7GCD05UxoDZP13gFQodCS5MXv0w==";

        RSAPublicKey pubKey3 = RSAKey.fromPublicKey(pubKey).toPublicKey();//    同时支持PKCS#8和PKCS#1
        RSAPrivateKey priKey3 = RSAKey.fromPrivateKey(priKey).toPrivateKey();// 同时支持PKCS#8和PKCS#1

        String xmlPubKey = "<RSAKeyValue><Modulus>krLQsKonOkZ0NfB3TqFOKCoCmKWh8OjfmQ0kx1774aOtxqCnnyMQizOTUtfYF4i1WdhNZjJxxpJG4yPU+eyo6Tr1zrq1GjyU/HZpqmlUGBYIHpi7ppyQ0UsEO/VpM74ZxXg5908Hqc5G1q6i0kK/vDKKBoB0Cjm+gct5xqTtux8=</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>";
        String xmlPriKey = "<RSAKeyValue><Modulus>krLQsKonOkZ0NfB3TqFOKCoCmKWh8OjfmQ0kx1774aOtxqCnnyMQizOTUtfYF4i1WdhNZjJxxpJG4yPU+eyo6Tr1zrq1GjyU/HZpqmlUGBYIHpi7ppyQ0UsEO/VpM74ZxXg5908Hqc5G1q6i0kK/vDKKBoB0Cjm+gct5xqTtux8=</Modulus><Exponent>AQAB</Exponent><P>ppPl+iyrZRKbmkAiztHZPcHswmVj5kZ/OLRkdJ4J/rn64DeDp9xAZRdLUy3owK4LgQkcXyU9BfZqdls78aCcAQ==</P><Q>4XL+pvHmtBuS9HrJymidXkGSIE8v7rr7IaqMrULXwEms77TF8DiUHTtYr3bKue7B3BwSu5/zxxH//+GKl3bXHw==</Q><DP>iPTSweUWKiypaKJQvBDG90LJIW9xOnQ9x2ZxrXv+pcNNZCngghPRkgwb71CwrDKs3LOb8xJP4OYf1B3g5CH0AQ==</DP><DQ>uxC64fU4M1vp3PgBdfTGFw6bXDm4kQMPG8ky/xTGhqPbXe6GiyO3KmYy2SzdW9gTfTrCBHLdSOHTpBMV90XBiw==</DQ><InverseQ>mhIfMHFrogmBT1R2ZjT2Te91dDDRTP/qbhd8s4QgNFaJThl5HHufAOoupZ0uKeWwVKOLmy1SqfudL/kuyEU8sQ==</InverseQ><D>fHwLBW9OXHA+2yrUQ0A0b3a+v0QGeMVUQK9bn2dlvOLFawEXXL5HoqhAgxMwhz/2DGi1vVxfGg804jic2PxPlFZN0Q+nopzP9zh8GwyFQ3akh9bSh8gaRb9ttGJuko4w+TfBuwC9bezHK1yyBvL0WdFxgisCRfSvTHuDiR0WKAE=</D></RSAKeyValue>";

        RSAPublicKey pubKey4 = RSAKey.fromXml(xmlPubKey).toPublicKey(); //  Xml格式的转换
        RSAPrivateKey priKey4 = RSAKey.fromXml(xmlPriKey).toPrivateKey();

        String xmlPubKey1 = new RSAKey(pubKey1).toXml(true);    //  生成xml格式的公钥
        String xmlPriKey1 = new RSAKey(pubKey1, priKey1).toXml(false);  //  生成xml格式的私钥
    }
}

RSAKey.java工具类源码

import java.io.ByteArrayOutputStream;
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.RSAPrivateCrtKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.util.Base64;
import java.util.UnknownFormatConversionException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * description: RSA pem工具类
 * date: 14:51 2023/2/10
 *
 * @author beifengtz
 */
public class RSAKey {

    private static final Pattern PEM_CODE_PATTERN = Pattern.compile("--+.+?--+|[\\s\\r\\n]+");
    private static final byte[] _SeqOID = new byte[]{0x30, 0x0D, 0x06, 0x09, 0x2A, (byte) 0x86, 0x48, (byte) 0x86, (byte) 0xF7, 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00};
    private static final byte[] _Ver = new byte[]{0x02, 0x01, 0x00};
    private static final Pattern XML_PATTERN = Pattern.compile("\\s*<RSAKeyValue>([<>\\/\\+=\\w\\s]+)</RSAKeyValue>\\s*");
    private static final Pattern XML_TAG_PATTERN = Pattern.compile("<(.+?)>\\s*([^<]+?)\\s*</");

    /**
     * modulus 模数,公钥、私钥都有
     **/
    private byte[] modulus;
    /**
     * publicExponent 公钥指数,公钥、私钥都有
     **/
    private byte[] publicExponent;
    /**
     * privateExponent 私钥指数,只有私钥的时候才有
     **/
    private byte[] privateExponent;

    //以下参数只有私钥才有 https://docs.microsoft.com/zh-cn/dotnet/api/system.security.cryptography.rsaparameters?redirectedfrom=MSDN&view=netframework-4.8
    private byte[] prime1;
    private byte[] prime2;
    private byte[] exponent1;
    private byte[] exponent2;
    private byte[] coefficient;

    private RSAKey() {
    }

    public RSAKey(RSAPublicKey publicKey) {
        this(publicKey, null);
    }

    public RSAKey(RSAPublicKey publicKey, RSAPrivateKey privateKey) {
        this(
                bigB(publicKey.getModulus()),
                bigB(publicKey.getPublicExponent()),
                privateKey == null ? null : bigB(privateKey.getPrivateExponent())
        );
    }

    /**
     * 通过全量的PEM字段数据构造一个密钥。
     * 公钥需传入:modulus, exponent
     * 私钥需传入:所有参数
     * 注意:所有参数首字节如果是0,必须先去掉
     *
     * @param modulus  模数
     * @param exponent 公钥指数
     * @param d        私钥指数
     * @param p        prime1
     * @param q        prime2
     * @param dp       exponent1
     * @param dq       exponent2
     * @param inverseQ coefficient
     */
    public RSAKey(byte[] modulus, byte[] exponent, byte[] d, byte[] p, byte[] q, byte[] dp, byte[] dq, byte[] inverseQ) {
        this.modulus = modulus;
        this.publicExponent = exponent;

        if (d != null) {
            this.privateExponent = bigL(d, modulus.length);

            int keyLen = modulus.length / 2;
            this.prime1 = bigL(p, keyLen);
            this.prime2 = bigL(q, keyLen);
            this.exponent1 = bigL(dp, keyLen);
            this.exponent2 = bigL(dq, keyLen);
            this.coefficient = bigL(inverseQ, keyLen);
        }
    }

    /**
     * 构造公钥
     *
     * @param modulus  模数
     * @param exponent 公钥指数
     */
    public RSAKey(byte[] modulus, byte[] exponent) {
        this(modulus, exponent, null);
    }

    /***
     * 通过公钥指数和私钥指数构造一个PEM,会反推计算出P、Q但和原始生成密钥的P、Q极小可能相同
     * 注意:所有参数首字节如果是0,必须先去掉
     * @param modulus 模数
     * @param exponent 公钥指数
     * @param privateExponent 私钥指数可以不提供,导出的PEM就只包含公钥
     **/
    public RSAKey(byte[] modulus, byte[] exponent, byte[] privateExponent) {
        this.modulus = modulus;
        this.publicExponent = exponent;

        if (privateExponent != null) {
            this.privateExponent = bigL(privateExponent, modulus.length);

            //反推P、Q
            BigInteger n = bigX(modulus);
            BigInteger e = bigX(exponent);
            BigInteger d = bigX(privateExponent);
            BigInteger p = findFactor(e, d, n);
            BigInteger q = n.divide(p);
            if (p.compareTo(q) > 0) {
                BigInteger t = p;
                p = q;
                q = t;
            }
            BigInteger exp1 = d.mod(p.subtract(BigInteger.ONE));
            BigInteger exp2 = d.mod(q.subtract(BigInteger.ONE));
            BigInteger coeff = q.modInverse(p);

            int keyLen = modulus.length / 2;
            prime1 = bigL(bigB(p), keyLen);
            prime2 = bigL(bigB(q), keyLen);
            exponent1 = bigL(bigB(exp1), keyLen);
            exponent2 = bigL(bigB(exp2), keyLen);
            coefficient = bigL(bigB(coeff), keyLen);
        }
    }

    /**
     * 转成正整数,如果是负数,需要加前导0转成正整数
     **/
    public static BigInteger bigX(byte[] bigb) {
        if (bigb[0] < 0) {
            byte[] c = new byte[bigb.length + 1];
            System.arraycopy(bigb, 0, c, 1, bigb.length);
            bigb = c;
        }
        return new BigInteger(bigb);
    }

    /**
     * BigInt导出byte整数首字节>0x7F的会加0前导,保证正整数,因此需要去掉0
     **/
    public static byte[] bigB(BigInteger bigx) {
        byte[] val = bigx.toByteArray();
        if (val[0] == 0) {
            byte[] c = new byte[val.length - 1];
            System.arraycopy(val, 1, c, 0, c.length);
            val = c;
        }
        return val;
    }

    /**
     * 某些密钥参数可能会少一位(32个byte只有31个,目测是密钥生成器的问题,只在c#生成的密钥中发现这种参数,java中生成的密钥没有这种现象),
     * 直接修正一下就行;这个问题与BigB有本质区别,不能动BigB
     **/
    public static byte[] bigL(byte[] bytes, int keyLen) {
        if (keyLen - bytes.length == 1) {
            byte[] c = new byte[bytes.length + 1];
            System.arraycopy(bytes, 0, c, 1, bytes.length);
            bytes = c;
        }
        return bytes;
    }

    /**
     * 由n e d 反推 P Q
     * <a href="https://stackoverflow.com/questions/43136036/how-to-get-a-rsaprivatecrtkey-from-a-rsaprivatekey">https://stackoverflow.com/questions/43136036/how-to-get-a-rsaprivatecrtkey-from-a-rsaprivatekey</a>
     **/
    private static BigInteger findFactor(BigInteger e, BigInteger d, BigInteger n) {
        BigInteger edMinus1 = e.multiply(d).subtract(BigInteger.ONE);
        int s = edMinus1.getLowestSetBit();
        BigInteger t = edMinus1.shiftRight(s);

        long now = System.currentTimeMillis();
        for (int aInt = 2; true; aInt++) {
            if (aInt % 10 == 0 && System.currentTimeMillis() - now > 3000) {
                throw new RuntimeException("Estimated RSA.P timeout");//测试最多循环2次,1024位的速度很快 8ms
            }

            BigInteger aPow = BigInteger.valueOf(aInt).modPow(t, n);
            for (int i = 1; i <= s; i++) {
                if (aPow.equals(BigInteger.ONE)) {
                    break;
                }
                if (aPow.equals(n.subtract(BigInteger.ONE))) {
                    break;
                }
                BigInteger aPowSquared = aPow.multiply(aPow).mod(n);
                if (aPowSquared.equals(BigInteger.ONE)) {
                    return aPow.subtract(BigInteger.ONE).gcd(n);
                }
                aPow = aPowSquared;
            }
        }
    }

    /**
     * 用PEM格式密钥对创建RSA,支持PKCS#1、PKCS#8格式的PEM
     *
     * @param pem pem格式密钥,支持PKCS#1、PKCS#8格式,且必须包含---PUBLIC KEY---或---PRIVATE KEY---标签
     * @return {@link RSAKey}
     * @throws InvalidKeySpecException 转换失败抛出
     */
    public static RSAKey fromPem(String pem) throws InvalidKeySpecException {
        if (pem.contains("PUBLIC KEY")) {
            return fromPem(pem, true);
        } else if (pem.contains("PRIVATE KEY")) {
            return fromPem(pem, false);
        } else {
            throw new IllegalArgumentException("Wrong pem format");
        }
    }

    /**
     * 用PEM格式密钥对创建RSA,支持PKCS#1、PKCS#8格式的PEM
     *
     * @param pem           pem格式密钥,支持PKCS#1、PKCS#8格式,可不用包含PEM标签
     * @param convertPublic true-按照公钥规则解析   false-按照私钥规则解析
     * @return {@link RSAKey}
     * @throws InvalidKeySpecException 转换失败抛出
     */
    public static RSAKey fromPem(String pem, boolean convertPublic) throws InvalidKeySpecException {
        String base64 = PEM_CODE_PATTERN.matcher(pem).replaceAll("");
        byte[] dataX = Base64.getDecoder().decode(base64);
        if (dataX == null) {
            throw new IllegalArgumentException("Invalid pem format");
        }
        short[] data = new short[dataX.length];
        for (int i = 0; i < dataX.length; i++) {
            data[i] = (short) (dataX[i] & 0xff);
        }
        if (convertPublic) {
            return fromPublicKey(data);
        } else {
            return fromPrivateKey(data);
        }
    }

    public static RSAKey fromPublicKey(String pem) throws InvalidKeySpecException {
        byte[] dataX = Base64.getDecoder().decode(pem);
        short[] data = new short[dataX.length];
        for (int i = 0; i < dataX.length; i++) {
            data[i] = (short) (dataX[i] & 0xff);
        }
        return fromPublicKey(data);
    }

    private static RSAKey fromPublicKey(short[] data) throws InvalidKeySpecException {
        RSAKey rsa = new RSAKey();
        int[] idx = new int[]{0};

        readLen(0x30, data, idx);

        //检测PKCS8
        int[] idx2 = new int[]{idx[0]};
        if (eq(_SeqOID, data, idx)) {
            //读取1长度
            readLen(0x03, data, idx);
            idx[0]++;//跳过0x00
            //读取2长度
            readLen(0x30, data, idx);
        } else {
            idx = idx2;
        }

        //Modulus
        rsa.modulus = readBlock(data, idx);

        //Exponent
        rsa.publicExponent = readBlock(data, idx);
        return rsa;
    }

    public static RSAKey fromPrivateKey(String pem) throws InvalidKeySpecException {
        byte[] dataX = Base64.getDecoder().decode(pem);
        short[] data = new short[dataX.length];
        for (int i = 0; i < dataX.length; i++) {
            data[i] = (short) (dataX[i] & 0xff);
        }
        return fromPrivateKey(data);
    }

    private static RSAKey fromPrivateKey(short[] data) throws InvalidKeySpecException {
        RSAKey rsa = new RSAKey();
        int[] idx = new int[]{0};
        readLen(0x30, data, idx);

        if (!eq(_Ver, data, idx)) {
            throw new UnknownFormatConversionException("Unknown pem version");
        }

        //检测PKCS8
        int[] idx2 = new int[]{idx[0]};
        if (eq(_SeqOID, data, idx)) {
            readLen(0x04, data, idx);
            readLen(0x30, data, idx);

            if (!eq(_Ver, data, idx)) {
                throw new UnknownFormatConversionException("Invalid pem version");
            }
        } else {
            idx = idx2;
        }

        rsa.modulus = readBlock(data, idx);
        rsa.publicExponent = readBlock(data, idx);
        int keyLen = rsa.modulus.length;
        rsa.privateExponent = bigL(readBlock(data, idx), keyLen);
        keyLen = keyLen / 2;
        rsa.prime1 = bigL(readBlock(data, idx), keyLen);
        rsa.prime2 = bigL(readBlock(data, idx), keyLen);
        rsa.exponent1 = bigL(readBlock(data, idx), keyLen);
        rsa.exponent2 = bigL(readBlock(data, idx), keyLen);
        rsa.coefficient = bigL(readBlock(data, idx), keyLen);
        return rsa;
    }

    /**
     * 读取长度
     **/
    private static int readLen(int first, short[] data, int[] idxO) throws InvalidKeySpecException {
        int idx = idxO[0];
        try {
            if (data[idx] == first) {
                idx++;
                if (data[idx] == 0x81) {
                    idx++;
                    return data[idx++];
                } else if (data[idx] == 0x82) {
                    idx++;
                    return (((int) data[idx++]) << 8) + data[idx++];
                } else if (data[idx] < 0x80) {
                    return data[idx++];
                }
            }
            throw new InvalidKeySpecException("Failed to extract data from PEM");
        } finally {
            idxO[0] = idx;
        }
    }

    /**
     * 读取块数据
     **/
    private static byte[] readBlock(short[] data, int[] idxO) throws InvalidKeySpecException {
        int idx = idxO[0];
        try {
            int len = readLen(0x02, data, idxO);
            idx = idxO[0];
            if (data[idx] == 0x00) {
                idx++;
                len--;
            }
            byte[] val = new byte[len];
            for (int i = 0; i < len; i++) {
                val[i] = (byte) data[idx + i];
            }
            idx += len;
            return val;
        } finally {
            idxO[0] = idx;
        }
    }

    /**
     * 比较data从idx位置开始是否是bytes内容
     **/
    private static boolean eq(byte[] byts, short[] data, int[] idxO) {
        int idx = idxO[0];
        try {
            for (int i = 0; i < byts.length; i++, idx++) {
                if (idx >= data.length) {
                    return false;
                }
                if ((byts[i] & 0xff) != data[idx]) {
                    return false;
                }
            }
            return true;
        } finally {
            idxO[0] = idx;
        }
    }

    /***
     * 将RSA中的密钥对转换成PEM PKCS#8格式
     * @param convertToPublic 等于true时含私钥的RSA将只返回公钥,仅含公钥的RSA不受影响
     * @return 公钥如:-----BEGIN RSA PUBLIC KEY-----,私钥如:-----BEGIN RSA PRIVATE KEY-----
     */
    public String toPemPKCS1(boolean convertToPublic) throws Exception {
        return toPem(convertToPublic, false, false);
    }

    /***
     * 将RSA中的密钥对转换成PEM PKCS#8格式
     * @param convertToPublic 等于true时含私钥的RSA将只返回公钥,仅含公钥的RSA不受影响
     * @return 公钥如:-----BEGIN PUBLIC KEY-----,私钥如:-----BEGIN PRIVATE KEY-----
     */
    public String toPemPKCS18(boolean convertToPublic) throws Exception {
        return toPem(convertToPublic, true, true);
    }

    /***
     * 将RSA中的密钥对转换成PEM格式
     * @param convertToPublic 等于true时含私钥的RSA将只返回公钥,仅含公钥的RSA不受影响
     * @param privateUsePKCS8 私钥的返回格式,等于true时返回PKCS#8格式(-----BEGIN PRIVATE KEY-----),否则返回PKCS#1格式(-----BEGIN RSA PRIVATE KEY-----),返回公钥时此参数无效;两种格式使用都比较常见
     * @param publicUsePKCS8 公钥的返回格式,等于true时返回PKCS#8格式(-----BEGIN PUBLIC KEY-----),否则返回PKCS#1格式(-----BEGIN RSA PUBLIC KEY-----),返回私钥时此参数无效;一般用的多的是true PKCS#8格式公钥,PKCS#1格式公钥似乎比较少见
     * @return 公钥如:-----BEGIN PUBLIC KEY-----,私钥如:-----BEGIN PRIVATE KEY-----
     */
    public String toPem(boolean convertToPublic, boolean privateUsePKCS8, boolean publicUsePKCS8) throws Exception {
        //https://www.jianshu.com/p/25803dd9527d
        //https://www.cnblogs.com/ylz8401/p/8443819.html
        //https://blog.csdn.net/jiayanhui2877/article/details/47187077
        //https://blog.csdn.net/xuanshao_/article/details/51679824
        //https://blog.csdn.net/xuanshao_/article/details/51672547

        ByteArrayOutputStream ms = new ByteArrayOutputStream();

        if (this.privateExponent == null || convertToPublic) {
            //  生成公钥
            //  写入总字节数,不含本段长度,额外需要24字节的头,后续计算好填入
            ms.write(0x30);
            int index1 = ms.size();

            //PKCS8 多一段数据
            int index2 = -1, index3 = -1;
            if (publicUsePKCS8) {
                //  固定内容
                // encoded OID sequence for PKCS #1 rsaEncryption szOID_RSA_RSA = "1.2.840.113549.1.1.1"
                ms.write(_SeqOID);

                //从0x00开始的后续长度
                ms.write(0x03);
                index2 = ms.size();
                ms.write(0x00);

                //后续内容长度
                ms.write(0x30);
                index3 = ms.size();
            }

            //写入Modulus
            writeBlock(modulus, ms);

            //写入Exponent
            writeBlock(publicExponent, ms);


            //计算空缺的长度
            byte[] byts = ms.toByteArray();

            if (index2 != -1) {
                byts = writeLen(index3, byts, ms);
                byts = writeLen(index2, byts, ms);
            }
            byts = writeLen(index1, byts, ms);


            String flag = " PUBLIC KEY";
            if (!publicUsePKCS8) {
                flag = " RSA" + flag;
            }
            return "-----BEGIN" + flag + "-----\n" + textBreak(Base64.getEncoder().encodeToString(byts), 64) + "\n-----END" + flag + "-----";
        } else {
            //  生成私钥
            //  写入总字节数,后续写入
            ms.write(0x30);
            int index1 = ms.size();

            //写入版本号
            ms.write(_Ver);

            //PKCS8 多一段数据
            int index2 = -1, index3 = -1;
            if (privateUsePKCS8) {
                //固定内容
                ms.write(_SeqOID);

                //后续内容长度
                ms.write(0x04);
                index2 = ms.size();

                //后续内容长度
                ms.write(0x30);
                index3 = ms.size();

                //写入版本号
                ms.write(_Ver);
            }

            //写入数据
            writeBlock(modulus, ms);
            writeBlock(publicExponent, ms);
            writeBlock(privateExponent, ms);
            writeBlock(prime1, ms);
            writeBlock(prime2, ms);
            writeBlock(exponent1, ms);
            writeBlock(exponent2, ms);
            writeBlock(coefficient, ms);


            //计算空缺的长度
            byte[] byts = ms.toByteArray();

            if (index2 != -1) {
                byts = writeLen(index3, byts, ms);
                byts = writeLen(index2, byts, ms);
            }
            byts = writeLen(index1, byts, ms);


            String flag = " PRIVATE KEY";
            if (!privateUsePKCS8) {
                flag = " RSA" + flag;
            }
            return "-----BEGIN" + flag + "-----\n" + textBreak(Base64.getEncoder().encodeToString(byts), 64) + "\n-----END" + flag + "-----";
        }
    }

    /**
     * 写入一个长度字节码
     **/
    private static void writeLenByte(int len, ByteArrayOutputStream ms) {
        if (len < 0x80) {
            ms.write((byte) len);
        } else if (len <= 0xff) {
            ms.write(0x81);
            ms.write((byte) len);
        } else {
            ms.write(0x82);
            ms.write((byte) (len >> 8 & 0xff));
            ms.write((byte) (len & 0xff));
        }
    }

    /**
     * 写入一块数据
     **/
    private static void writeBlock(byte[] byts, ByteArrayOutputStream ms) throws Exception {
        boolean addZero = ((byts[0] & 0xff) >> 4) >= 0x8;
        ms.write(0x02);
        int len = byts.length + (addZero ? 1 : 0);
        writeLenByte(len, ms);

        if (addZero) {
            ms.write(0x00);
        }
        ms.write(byts);
    }

    /**
     * 根据后续内容长度写入长度数据
     **/
    private static byte[] writeLen(int index, byte[] byts, ByteArrayOutputStream ms) {
        int len = byts.length - index;

        ms.reset();
        ms.write(byts, 0, index);
        writeLenByte(len, ms);
        ms.write(byts, index, len);

        return ms.toByteArray();
    }

    /**
     * 把字符串按每行多少个字断行
     **/
    private static String textBreak(String text, int line) {
        int idx = 0;
        int len = text.length();
        StringBuilder str = new StringBuilder();
        while (idx < len) {
            if (idx > 0) {
                str.append('\n');
            }
            if (idx + line >= len) {
                str.append(text.substring(idx));
            } else {
                str.append(text, idx, idx + line);
            }
            idx += line;
        }
        return str.toString();
    }

    /***
     * 将XML格式密钥转成PEM,支持公钥xml、私钥xml
     *  xml格式:
     *  <RSAKeyValue>
     *      <Modulus>modulus encode with base64</Modulus>
     *      <Exponent>publicExponent encode with base64</Exponent>
     *      <D>privateExponent encode with base64</D>
     *      <P>prime1 encode with base64</P>
     *      <Q>prime2 encode with base64</Q>
     *      <DP>exponent1 encode with base64</DP>
     *      <DQ>exponent2 encode with base64</DQ>
     *      <InverseQ>coefficient encode with base64</InverseQ>
     *  </RSAKeyValue>
     *
     */
    public static RSAKey fromXml(String xml) throws UnknownFormatConversionException {
        RSAKey rtv = new RSAKey();

        Matcher xmlM = XML_PATTERN.matcher(xml);
        if (!xmlM.find()) {
            throw new UnknownFormatConversionException("XML content does not meet requirements: there is no 'RSAKeyValue' tag or there is no content in the tag");
        }

        Matcher tagM = XML_TAG_PATTERN.matcher(xmlM.group(1));
        Base64.Decoder dec = Base64.getDecoder();
        while (tagM.find()) {
            String tag = tagM.group(1);
            String b64 = tagM.group(2);
            byte[] val = dec.decode(b64);
            switch (tag) {
                case "Modulus":
                    rtv.modulus = val;
                    break;
                case "Exponent":
                    rtv.publicExponent = val;
                    break;
                case "D":
                    rtv.privateExponent = val;
                    break;
                case "P":
                    rtv.prime1 = val;
                    break;
                case "Q":
                    rtv.prime2 = val;
                    break;
                case "DP":
                    rtv.exponent1 = val;
                    break;
                case "DQ":
                    rtv.exponent2 = val;
                    break;
                case "InverseQ":
                    rtv.coefficient = val;
                    break;
            }
        }

        if (rtv.modulus == null || rtv.publicExponent == null) {
            throw new UnknownFormatConversionException("XML public key missing");
        }
        if (rtv.privateExponent != null) {
            if (rtv.prime1 == null || rtv.prime2 == null || rtv.exponent1 == null || rtv.exponent2 == null || rtv.coefficient == null) {
                return new RSAKey(rtv.modulus, rtv.publicExponent, rtv.privateExponent);
            }
        }

        return rtv;
    }


    /***
     * 将RSA中的密钥对转换成XML格式
     * ,如果convertToPublic含私钥的RSA将只返回公钥,仅含公钥的RSA不受影响
     */
    public String toXml(boolean convertToPublic) {
        Base64.Encoder enc = Base64.getEncoder();
        StringBuilder str = new StringBuilder();
        str.append("<RSAKeyValue>");
        str.append("<Modulus>").append(enc.encodeToString(modulus)).append("</Modulus>");
        str.append("<Exponent>").append(enc.encodeToString(publicExponent)).append("</Exponent>");
        //  公钥没有后面部分
        if (this.privateExponent != null && !convertToPublic) {
            str.append("<P>").append(enc.encodeToString(prime1)).append("</P>");
            str.append("<Q>").append(enc.encodeToString(prime2)).append("</Q>");
            str.append("<DP>").append(enc.encodeToString(exponent1)).append("</DP>");
            str.append("<DQ>").append(enc.encodeToString(exponent2)).append("</DQ>");
            str.append("<InverseQ>").append(enc.encodeToString(coefficient)).append("</InverseQ>");
            str.append("<D>").append(enc.encodeToString(privateExponent)).append("</D>");
        }
        str.append("</RSAKeyValue>");
        return str.toString();
    }


    public RSAPublicKey toPublicKey() throws NoSuchAlgorithmException, InvalidKeySpecException {
        BigInteger m = new BigInteger(1, modulus);
        BigInteger e = new BigInteger(1, publicExponent);
        return (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(new RSAPublicKeySpec(m, e));
    }

    public RSAPrivateKey toPrivateKey() throws NoSuchAlgorithmException, InvalidKeySpecException {
        RSAPrivateCrtKeySpec keySpec = new RSAPrivateCrtKeySpec(
                new BigInteger(1, modulus),
                new BigInteger(1, publicExponent),
                new BigInteger(1, privateExponent),
                new BigInteger(1, prime1),
                new BigInteger(1, prime2),
                new BigInteger(1, exponent1),
                new BigInteger(1, exponent2),
                new BigInteger(1, coefficient)
        );
        return (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(keySpec);
    }
}
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐