diff --git a/README.md b/README.md index af76e07..b6fec29 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,8 @@ 此文件不依赖任何文件,可以直接copy这个文件到你项目中用;通过`FromPEM`、`ToPEM` 和`FromXML`、`ToXML`这两对方法,可以实现PEM`PKCS#1`、`PKCS#8`相互转换,PEM、XML的相互转换。 -注:openssl `RSAPublicKey_out`导出的公钥,字节码内并不带[OID](http://www.oid-info.com/get/1.2.840.113549.1.1.1)(目测是因为不带OID所以openssl自己都不支持用这个公钥来加密数据),RSA_PEM支持此格式公钥的导入,但不提供此种格式公钥的导出。 +注:`openssl rsa -in 私钥文件 -pubout`导出的是PKCS#8格式公钥(用的比较多),`openssl rsa -pubin -in PKCS#8公钥文件 -RSAPublicKey_out`导出的是PKCS#1格式公钥(用的比较少)。 + ### 构造方法 @@ -61,7 +62,11 @@ boolean:**hasPrivate()**(是否包含私钥) **RSAPrivateKey getRSAPrivateKey()**:得到私钥Java对象,如果此PEM不含私钥会直接报错。 -**String ToPEM(boolean convertToPublic, boolean usePKCS8)**:将RSA中的密钥对转换成PEM格式,usePKCS8=false时返回PKCS#1格式,否则返回PKCS#8格式,如果convertToPublic含私钥的RSA将只返回公钥,仅含公钥的RSA不受影响。 +**String ToPEM(boolean convertToPublic, boolean privateUsePKCS8, boolean publicUsePKCS8)**:将RSA中的密钥对转换成PEM格式。convertToPublic:等于true时含私钥的RSA将只返回公钥,仅含公钥的RSA不受影响 。**privateUsePKCS8**:私钥的返回格式,等于true时返回PKCS#8格式(`-----BEGIN PRIVATE KEY-----`),否则返回PKCS#1格式(`-----BEGIN RSA PRIVATE KEY-----`),返回公钥时此参数无效;两种格式使用都比较常见。**publicUsePKCS8**:公钥的返回格式,等于true时返回PKCS#8格式(`-----BEGIN PUBLIC KEY-----`),否则返回PKCS#1格式(`-----BEGIN RSA PUBLIC KEY-----`),返回私钥时此参数无效;一般用的多的是true PKCS#8格式公钥,PKCS#1格式公钥似乎比较少见。 + +**String ToPEM_PKCS1(boolean convertToPublic)**:ToPEM方法的简化写法,不管公钥还是私钥都返回PKCS#1格式;似乎导出PKCS#1公钥用的比较少,PKCS#8的公钥用的多些,私钥#1#8都差不多。 + +**String ToPEM_PKCS8(boolean convertToPublic)**:ToPEM方法的简化写法,不管公钥还是私钥都返回PKCS#8格式。 **String ToXML(boolean convertToPublic)**:将RSA中的密钥对转换成XML格式,如果convertToPublic含私钥的RSA将只返回公钥,仅含公钥的RSA不受影响。 @@ -121,7 +126,7 @@ RSA工具(非开源): 请移步到[RSA-csharp](https://github.com/xiangyuecn/RSA-csharp)阅读知识库部分,知识库内包含了详细的PEM格式解析,和部分ASN.1语法;然后逐字节分解PEM字节码教程。 -本库的诞生是由于微信付款到银行卡的功能,然后微信提供的RSA公钥接口返回的公钥和openssl -RSAPublicKey_out生成的一样,公钥 PEM 字节码内没有OID(目测是因为不带 OID 所以openssl 自己都不支持用这个公钥来加密数据),这种是不是PKCS#1 格式不清楚,正反都是难用,所以就撸了一个java版转换代码,也不是难事以前撸过C#的,copy C#的代码过来改改就上线使用了。 +本库的诞生是由于微信付款到银行卡的功能,然后微信提供的RSA公钥接口返回的公钥和openssl -RSAPublicKey_out生成的一样,公钥 PEM 字节码内没有OID(目测是因为不带 OID 所以openssl 自己都不支持用这个公钥来加密数据),这种是不是PKCS#1 格式不清楚(目测是,大部分文章也说是),正反都是难用,所以就撸了一个java版转换代码,也不是难事以前撸过C#的,copy C#的代码过来改改就上线使用了。 本库的代码整理未使用IDE,RSA_PEM.java copy过来的,Test.java直接用的文本编辑器编写,*.java文件全部丢到根目录,没有创建包名目录,源码直接根目录裸奔,简单粗暴;这样的项目结构肉眼看去也算是简洁,也方便copy文件使用。 diff --git a/RSA_PEM.java b/RSA_PEM.java index fba29be..0dc18c5 100644 --- a/RSA_PEM.java +++ b/RSA_PEM.java @@ -214,7 +214,7 @@ static public RSA_PEM FromPEM(String pem) throws Exception { //读取数据总长度 readLen(0x30, data, idx); - //看看有没有oid + //检测PKCS8 int[] idx2 = new int[] {idx[0]}; if (eq(_SeqOID, data, idx)) { //读取1长度 @@ -358,12 +358,30 @@ static private boolean eq(byte[] byts, short[] data, int[] idxO) { + /*** + * 将RSA中的密钥对转换成PEM PKCS#8格式 + * 。convertToPublic:等于true时含私钥的RSA将只返回公钥,仅含公钥的RSA不受影响 + * 。公钥如:-----BEGIN RSA PUBLIC KEY-----,私钥如:-----BEGIN RSA PRIVATE KEY----- + * 。似乎导出PKCS#1公钥用的比较少,PKCS#8的公钥用的多些,私钥#1#8都差不多 + */ + public String ToPEM_PKCS1(boolean convertToPublic) throws Exception { + return ToPEM(convertToPublic, false, false); + } + /*** + * 将RSA中的密钥对转换成PEM PKCS#8格式 + * 。convertToPublic:等于true时含私钥的RSA将只返回公钥,仅含公钥的RSA不受影响 + * 。公钥如:-----BEGIN PUBLIC KEY-----,私钥如:-----BEGIN PRIVATE KEY----- + */ + public String ToPEM_PKCS8(boolean convertToPublic) throws Exception { + return ToPEM(convertToPublic, true, true); + } /*** * 将RSA中的密钥对转换成PEM格式 - * ,usePKCS8=false时返回PKCS#1格式,否则返回PKCS#8格式 - * ,如果convertToPublic含私钥的RSA将只返回公钥,仅含公钥的RSA不受影响 + * 。convertToPublic:等于true时含私钥的RSA将只返回公钥,仅含公钥的RSA不受影响 + * 。privateUsePKCS8:私钥的返回格式,等于true时返回PKCS#8格式(-----BEGIN PRIVATE KEY-----),否则返回PKCS#1格式(-----BEGIN RSA PRIVATE KEY-----),返回公钥时此参数无效;两种格式使用都比较常见 + * 。publicUsePKCS8:公钥的返回格式,等于true时返回PKCS#8格式(-----BEGIN PUBLIC KEY-----),否则返回PKCS#1格式(-----BEGIN RSA PUBLIC KEY-----),返回私钥时此参数无效;一般用的多的是true PKCS#8格式公钥,PKCS#1格式公钥似乎比较少见 */ - public String ToPEM(boolean convertToPublic, boolean usePKCS8) throws Exception { + 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 @@ -380,18 +398,22 @@ public String ToPEM(boolean convertToPublic, boolean usePKCS8) throws Exception ms.write(0x30); int index1 = ms.size(); - //固定内容 - // encoded OID sequence for PKCS #1 rsaEncryption szOID_RSA_RSA = "1.2.840.113549.1.1.1" - ms.write(_SeqOID); + //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); - int index2 = ms.size(); - ms.write(0x00); + //从0x00开始的后续长度 + ms.write(0x03); + index2 = ms.size(); + ms.write(0x00); - //后续内容长度 - ms.write(0x30); - int index3 = ms.size(); + //后续内容长度 + ms.write(0x30); + index3 = ms.size(); + } //写入Modulus writeBlock(Key_Modulus, ms); @@ -403,12 +425,18 @@ public String ToPEM(boolean convertToPublic, boolean usePKCS8) throws Exception //计算空缺的长度 byte[] byts = ms.toByteArray(); - byts = writeLen(index3, byts, ms); - byts = writeLen(index2, byts, ms); + if (index2 != -1) { + byts = writeLen(index3, byts, ms); + byts = writeLen(index2, byts, ms); + } byts = writeLen(index1, byts, ms); - return "-----BEGIN PUBLIC KEY-----\n" + TextBreak(Base64.getEncoder().encodeToString(byts), 64) + "\n-----END PUBLIC KEY-----"; + String flag = " PUBLIC KEY"; + if (!publicUsePKCS8) { + flag = " RSA" + flag; + } + return "-----BEGIN" + flag + "-----\n" + TextBreak(Base64.getEncoder().encodeToString(byts), 64) + "\n-----END" + flag + "-----"; } else { /****生成私钥****/ @@ -421,7 +449,7 @@ public String ToPEM(boolean convertToPublic, boolean usePKCS8) throws Exception //PKCS8 多一段数据 int index2 = -1, index3 = -1; - if (usePKCS8) { + if (privateUsePKCS8) { //固定内容 ms.write(_SeqOID); @@ -459,7 +487,7 @@ public String ToPEM(boolean convertToPublic, boolean usePKCS8) throws Exception String flag = " PRIVATE KEY"; - if (!usePKCS8) { + if (!privateUsePKCS8) { flag = " RSA" + flag; } return "-----BEGIN" + flag + "-----\n" + TextBreak(Base64.getEncoder().encodeToString(byts), 64) + "\n-----END" + flag + "-----"; diff --git a/Test.java b/Test.java index 18859c1..e8cab78 100644 --- a/Test.java +++ b/Test.java @@ -25,16 +25,16 @@ static void RSATest() throws Exception{ //使用PEM PKCS#8文件的文本构造出pem对象 RSA_PEM pem=RSA_PEM.FromPEM(pemRawTxt); - boolean isEqRaw=pem.ToPEM(false,true).replaceAll("\\r|\\n","").equals(pemRawTxt); + boolean isEqRaw=pem.ToPEM_PKCS8(false).replaceAll("\\r|\\n","").equals(pemRawTxt); //生成PKCS#1和XML System.out.println("【" + pem.keySize() + "私钥(XML)】:"); System.out.println(pem.ToXML(false)); System.out.println(); - System.out.println("【" + pem.keySize() + "私钥(PEM)】:是否和KeyPair生成的相同"+(isEqRaw)); - System.out.println(pem.ToPEM(false,false)); + System.out.println("【" + pem.keySize() + "私钥(PKCS#1)】:是否和KeyPair生成的相同"+(isEqRaw)); + System.out.println(pem.ToPEM_PKCS1(false)); System.out.println(); - System.out.println("【" + pem.keySize() + "公钥(PEM)】:"); - System.out.println(pem.ToPEM(true,false)); + System.out.println("【" + pem.keySize() + "公钥(PKCS#8)】:"); + System.out.println(pem.ToPEM_PKCS8(true)); System.out.println(); @@ -73,18 +73,18 @@ static void RSATest() throws Exception{ //使用PEM PKCS#1构造pem对象 - RSA_PEM pem2=RSA_PEM.FromPEM(pem.ToPEM(false,false)); + RSA_PEM pem2=RSA_PEM.FromPEM(pem.ToPEM_PKCS1(false)); System.out.println("【用PEM新创建的RSA是否和上面的一致】:"); System.out.println("XML:" + (pem2.ToXML(false) .equals( pem.ToXML(false) ))); - System.out.println("PKCS1:" + (pem2.ToPEM(false,false) .equals( pem.ToPEM(false,false) ))); - System.out.println("PKCS8:" + (pem2.ToPEM(false,true) .equals( pem.ToPEM(false,true) ))); + System.out.println("PKCS1:" + (pem2.ToPEM_PKCS1(false) .equals( pem.ToPEM_PKCS1(false) ))); + System.out.println("PKCS8:" + (pem2.ToPEM_PKCS8(false) .equals( pem.ToPEM_PKCS8(false) ))); //使用XML构造pem对象 RSA_PEM pem3=RSA_PEM.FromXML(pem.ToXML(false)); System.out.println("【用XML新创建的RSA是否和上面的一致】:"); System.out.println("XML:" + (pem3.ToXML(false) .equals( pem.ToXML(false) ))); - System.out.println("PKCS1:" + (pem3.ToPEM(false,false) .equals( pem.ToPEM(false,false) ))); - System.out.println("PKCS8:" + (pem3.ToPEM(false,true) .equals( pem.ToPEM(false,true) ))); + System.out.println("PKCS1:" + (pem3.ToPEM_PKCS1(false) .equals( pem.ToPEM_PKCS1(false) ))); + System.out.println("PKCS8:" + (pem3.ToPEM_PKCS8(false) .equals( pem.ToPEM_PKCS8(false) ))); //--------RSA_PEM验证--------- @@ -94,12 +94,12 @@ static void RSATest() throws Exception{ System.out.println("【RSA_PEM是否和原始RSA一致】:"); System.out.println(pem.keySize() + "位"); System.out.println("XML:" + (pemX.ToXML(false) .equals( pem.ToXML(false) ))); - System.out.println("PKCS1:" + (pemX.ToPEM(false, false) .equals( pem.ToPEM(false, false) ))); - System.out.println("PKCS8:" + (pemX.ToPEM(false, true) .equals( pem.ToPEM(false, true) ))); + System.out.println("PKCS1:" + (pemX.ToPEM_PKCS1(false) .equals( pem.ToPEM_PKCS1(false) ))); + System.out.println("PKCS8:" + (pemX.ToPEM_PKCS8(false) .equals( pem.ToPEM_PKCS8(false) ))); System.out.println("仅公钥:"); System.out.println("XML:" + (pemX.ToXML(true) .equals( pem.ToXML(true) ))); - System.out.println("PKCS1:" + (pemX.ToPEM(true, false) .equals( pem.ToPEM(true, false) ))); - System.out.println("PKCS8:" + (pemX.ToPEM(true, true) .equals( pem.ToPEM(true, true) ))); + System.out.println("PKCS1:" + (pemX.ToPEM_PKCS1(true) .equals( pem.ToPEM_PKCS1(true) ))); + System.out.println("PKCS8:" + (pemX.ToPEM_PKCS8(true) .equals( pem.ToPEM_PKCS8(true) ))); //使用n、e、d构造pem对象 RSA_PEM pem4 = new RSA_PEM(pem.Key_Modulus, pem.Key_Exponent, pem.Key_D); @@ -107,6 +107,16 @@ static void RSATest() throws Exception{ dec4.init(Cipher.DECRYPT_MODE, pem4.getRSAPrivateKey()); System.out.println("【用n、e、d构造解密】"); System.out.println(new String(dec4.doFinal(en),"utf-8")); + + + + System.out.println(); + System.out.println(); + System.out.println("【" + pem.keySize() + "私钥(PKCS#8)】:"); + System.out.println(pem.ToPEM_PKCS8(false)); + System.out.println(); + System.out.println("【" + pem.keySize() + "公钥(PKCS#1)】:不常见的公钥格式"); + System.out.println(pem.ToPEM_PKCS1(true)); }