Skip to content

Commit

Permalink
公钥也区分PKCS#1和PKCS#8
Browse files Browse the repository at this point in the history
  • Loading branch information
xiangyuecn committed Jul 8, 2020
1 parent dc21ff1 commit b4d939f
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 36 deletions.
11 changes: 8 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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格式公钥(用的比较少)。


### 构造方法

Expand Down Expand Up @@ -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不受影响。

Expand Down Expand Up @@ -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文件使用。

Expand Down
66 changes: 47 additions & 19 deletions RSA_PEM.java
Original file line number Diff line number Diff line change
Expand Up @@ -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长度
Expand Down Expand Up @@ -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
Expand All @@ -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);
Expand All @@ -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 {
/****生成私钥****/

Expand All @@ -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);

Expand Down Expand Up @@ -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 + "-----";
Expand Down
38 changes: 24 additions & 14 deletions Test.java
Original file line number Diff line number Diff line change
Expand Up @@ -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();


Expand Down Expand Up @@ -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验证---------
Expand All @@ -94,19 +94,29 @@ 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);
Cipher dec4 = Cipher.getInstance("RSA");
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));
}


Expand Down

0 comments on commit b4d939f

Please sign in to comment.