本文主要描述了RSA私钥的PKCS#1与PKCS#8编码,RSA公钥的PKCS#1与X.509编码,以及RSA公私钥在Openssl和Java之间的转换。
PKCS(The Public-Key Cryptography Standards)是一系列公钥密码学的相关标准,其中 《PKCS#1: RSA Cryptography Standard》 是RSA密码学标准的基础部分,而 《PKCS#8: Private-Key Information Syntax Standard》 是私钥信息语法格式。
在PKCS#1中,详细地定义了RSA公钥和私钥的语法格式,通常将符合这种语法的公私钥称为PKCS#1格式的公私钥。
在PKCS#8中,描述了一种私钥信息的语法,同时还提供一种加密密钥的语法。比如RSA私钥证书中的加密私钥就是用此描述的。相应地,符合此标准的私钥也称为PKCS#8格式的私钥。
通常,在java中的RSA私钥是使用PKCS#8格式,在Openssl中是PKCS#1格式。
X.509是一系列的证书标准。在这里提及是因为Java中的RSA公钥的编码就是符合X.509证书公钥的。通常,Openssl中的公钥采用PKCS#1编码,而Java中则通常是X.509编码。
ASN.1描述了一种对数据进行表示、编码、传输和解码的数据格式。 一般都采用ASN.1来定义一个通用的、抽象的数据结构。PKCS系列标准中也是如此,在PKCS#1标准的附录A可以看到RSA公私钥的ASN.1的详细描述;在PKCS#8中可以看到私钥(加密私钥)信息语法的ASN.1的详细描述。
ASN.1中常见的数据类型定义如下:
类型码 | ASN.1类型 | 作用 |
---|---|---|
1 | 布尔型 | 储存布尔值 |
2 | 整数 | 储存大整数 |
3 | 位串 | 存储位数组 |
4 | 八位位串 | 存储字节数组 |
5 | 空 | 预留位 |
6 | 对象标识符 | 标识算法及协议 |
证书和密钥的常见编码有DER和PEM两种。
DER编码的数据是用二进制表示的,直接查看只能看到一些乱码。而PEM是Base64编码的,所以可以直接查看。
在证书和密钥等结构的ASN.1描述中, 通常需要用OID来标识一个对象。比如标识一个私钥是RSA私钥或标识一个哈希算法是SHA1算法等待。在ASN.1中定义了这种数据类型,它的标识代码是06。
在分析之前,我们先来看看用Openssl命令怎么来生成RSA密钥。
openssl genrsa -out prikey.pem 1024
上面这条命令可以生成一个PKCS#1格式的,PEM编码的,1024位的RSA私钥。用NotePad++打开查看:
openssl rsa –in prikey.pem –RSAPublicKey_out –out pubkey.pkcs1.pem
这条命令可以从RSA私钥中PKCS#1格式的、PEM编码的RSA公钥。用NotePad++打开查看:
PKCS#1格式的RSA公钥的ASN.1描述如下:
RSAPublicKey ::= SEQUENCE {
modulus INTEGER, -- n
publicExponent INTEGER -- e
}
其中:
我们用Asn1View来查看一下前面生成的pubkey.pkcs1.pem这个RSA公钥:
可以看到,它是符合PKCS标准的。其中包括了n和e这两个RSA公钥元素。其中e为65537,即0x10001,这是一个最常用的公钥幂指数。
RSA算法中的公钥指数目前在用的有两个,其一名为F0,等于3;其二名为F4,等于65537。
PKCS#1格式的RSA私钥的ASN.1描述如下:
RSAPrivateKey ::= SEQUENCE {
version Version,
modulus INTEGER, -- n
publicExponent INTEGER, -- e
privateExponent INTEGER, -- d
prime1 INTEGER, -- p
prime2 INTEGER, -- q
exponent1 INTEGER, -- d mod (p-1)
exponent2 INTEGER, -- d mod (q-1)
coefficient INTEGER, -- (inverse of q) mod p
otherPrimeInfos OtherPrimeInfos OPTIONAL
}
其中各字段含义如下:
Version ::= INTEGER { two-prime(0), multi(1) }
(CONSTRAINED BY {-- version must be multi if otherPrimeInfos present --})
OtherPrimeInfos ::= SEQUENCE SIZE(1..MAX) OF OtherPrimeInfo
OtherPrimeInfo的ASN.1结构如下:
OtherPrimeInfo ::= SEQUENCE {
prime INTEGER, -- ri
exponent INTEGER, -- di
coefficient INTEGER -- ti
}
OtherPrimeInfo中各字段含义如下:
我们用Asn1View来查看一下前面生成的prikey.pem这个RSA私钥:
从上面的PKCS#1的RSA私钥结构中我们可以知道,RSA公钥被包含于其中,所以在前面我们才可以用Openssl命令从RSA私钥中导出公钥。
在PKCS#8标准中给出的私钥信息(未加密)的语法格式如下:
PrivateKeyInfo ::= SEQUENCE {
verion Version
privateKeyAlgorithm PrivateKeyAlgorithmIdentifier
privateKey PrivateKey
attributes [0] IMPLICIT Attributes OPTIONAL
}
Version ::= INTEGER
PrivateKeyAlgorithmIdentifier ::= AlgorithmIdentifier
PrivateKey ::= OCTET STRING
Attributes ::= SET OF Attribute
AlgorithmIdentifier ::= SEQUENCE {
algorithm OBJECT IDENTIFIER,
parameters ANY DEFINED BY algorithm OPTIONAL
}
其中个字段含义为:
我们使用下面的Openssl命令将前面的PKCS#1编码的RSA私钥转为PKCS#8编码的RSA私钥:
openssl pkcs8 -topk8 -in prikey.pem -out prikey.pkcs8.pem -nocrypt
然后用Asn1View来查看一下:
在这个PKCS#8编码的RSA私钥中,私钥算法OID为1.2.840.113549.1.1.1,其中1.2.840.113549.1.1是PKCS1标准的OID,它下面有很多分支,第一个分支标识RSA私钥。其中的privateKey这个私钥数据就是一个PKCS#1编码的RSA私钥的BER编码。
在X.509公钥证书中定义的公钥信息如下:
SubjectPublicKeyInfo ::= SEQUENCE {
algorithm AlgorithmIdentifier,
subjectPublicKey BIT STRING
}
AlgorithmIdentifier ::= SEQUENCE {
algorithm OBJECT IDENTIFIER,
parameters ANY DEFINED BY algorithm OPTIONAL
}
其中:
我们使用下面的Openssl命令将前面的PKCS#1编码的RSA公钥钥转为X509中的RSA公钥:
openssl rsa –in prikey.pem -pubout -out pubkey.x509.pem
然后用Asn1View来查看一下:
在这个X.509的RSA公钥中,公钥算法OID为1.2.840.113549.1.1.1,表示是一个PKCS#1格式的公钥。其中的subjectPublicKey这个公钥数据就是一个PKCS#1编码的RSA公钥的BER编码。
PKCS#8公钥
我们可以从X.509的RSA公钥中看到,它的ASN.1结构描述和PKCS#8中的私钥信息语法基本相同,都是一个算法OID加上密钥数据。所以有时候我们可能也简便地将X.509公钥称为PKCS#8格式的公钥,这样可以将公私钥与PKCS#1和PKCS#8一同提及。但应该明白,PKCS#8标准中并没有定义公钥消息语法,它是定义在X.509公钥证书标准中的。
Java中的RSA公钥接口RSAPublicKey的结构图如下:
Java中的RSA私钥接口RSAPrivateKey的结构图如下:
可以从Java中RSA公私钥的接口方法中看到,公私钥只包括了必要的2个元素,即模数和幂指数,因为RSA算法的基本计算模型就是:
输入数据.modPow(幂指数, 模数)
其中输入数据、幂指数和模数都是BigInteger类型。
上面的RSAPublicKey和RSAPrivateKey只是方法接口,具体的底层实现类其实是在RSAPublicKeyImpl和RSAPrivateCrtKeyImpl中,它们的类结构图如下:
从RSAPrivateCrtKeyImpl的源码中可以看到,该结构包含了在PKCS#1中定义的RSA私钥的除otherPrimeInfos之外的所有字段;同时继承自PKCS8Key,它实现了RSA公钥的PKCS8编码。
从RSAPublicKeyImpl的源码中也可以看到,该结构包含了在PKCS#1中定义的RSA公钥结构的所有元素;同时继承自X509Key,它实现了RSA公钥的X509编码。
在Java和Openssl中互用RSA公私钥时,主要涉及到RSA私钥的PKCS#1与PKCS#8的相互转换、RSA公钥的PKCS#1和X509格式之间的转换、DER和PEM编码之间的转换,以及Java中通过RSA密钥字节数组构造RSAPublicKey和RSAPrivateKey结构的方法。
注意:这里涉及到的RSA私钥都是没有加密的。
这里的字节数组是指从RSAPublicKey和RSAPrivateKey的getEncoded方法获取的数组,这样才可以使用下面的方法将其还原。
public static RSAPrivateKey genPrivateKey(byte[] derPrikeyPkcs8Encoded) throws Exception
{
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(derPrikeyPkcs8Encoded);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return (RSAPrivateKey)keyFactory.generatePrivate(keySpec);
}
public static RSAPublicKey genPublicKey(byte[] derPubkeyX509Encoded) throws Exception
{
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(derPubkeyX509Encoded);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return (RSAPublicKey)keyFactory.generatePublic(keySpec);
}
对于Openssl和Java之间的RSA公私钥的转换,这里基于BouncyCastle库提供一种转换的实现方式,代码如下:
package com.yy.rsa;
import org.bouncycastle.asn1.ASN1Object;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemReader;
import org.bouncycastle.util.io.pem.PemWriter;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StringWriter;
import java.security.Key;
/**
* RSA密钥格式转换.
*
* 私钥的PKCS#1和PKCS#8之间的转换.
* 公钥的PKCS#1和X509之间的转换.
*
* PKCS1编码的密钥的PEM格式的前缀带有算法名称;非PKCS1的则没有.
*
* Created by YaoYuan on 2019/4/30.
*/
public class RsaKeyCodec {
public static final String TYPE_PREFIX_PKCS1_PRIVATE_KEY = "RSA PRIVATE KEY";
public static final String TYPE_PREFIX_PKCS1_PUBLIC_KEY = "RSA PUBLIC KEY";
public static final String TYPE_PREFIX_PKCS8_PRIVATE_KEY = "PRIVATE KEY";
public static final String TYPE_PREFIX_X509_PUBLIC_KEY = "PUBLIC KEY";
/**
* DER转PEM
*
* @param typePrefix 密钥类型前缀,如"PRIVATE KEY".
* 参考类型:
* {@link #TYPE_PREFIX_PKCS1_PRIVATE_KEY},
* {@link #TYPE_PREFIX_PKCS1_PUBLIC_KEY},
* {@link #TYPE_PREFIX_PKCS8_PRIVATE_KEY},
* {@link #TYPE_PREFIX_X509_PUBLIC_KEY}
* @param derKeyEncoded 编码后的密钥数据
* @return PKCS8编码的PEM
*/
public static String derToPem(String typePrefix, byte[] derKeyEncoded) throws Exception {
PemObject pemObject = new PemObject(typePrefix, derKeyEncoded);
StringWriter stringWriter = new StringWriter();
PemWriter pemWriter = new PemWriter(stringWriter);
pemWriter.writeObject(pemObject);
pemWriter.close();
return stringWriter.toString();
}
/**
* PEM转DER
*
* @param pemKey PEM的key
* @return DER字节数组
*/
public static byte[] pemToDer(String pemKey) throws IOException {
InputStreamReader isr = new InputStreamReader(new ByteArrayInputStream(pemKey.getBytes()));
PemReader pemReader = new PemReader(isr);
return pemReader.readPemObject().getContent();
}
/**
* PKCS8编码的私钥转为PKCS1,DER格式.
*
* @param derPrikeyPkcs8Encoded 编码后的PKCS8私钥数据,通过java的{@link Key#getEncoded()}获取
* @return PKCS1编码的DER私钥
*/
public static byte[] der_prikey_pkcs8ToPkcs1(byte[] derPrikeyPkcs8Encoded) throws Exception {
PrivateKeyInfo pki = PrivateKeyInfo.getInstance(derPrikeyPkcs8Encoded);
org.bouncycastle.asn1.pkcs.RSAPrivateKey rsaPrivateKey = org.bouncycastle.asn1.pkcs.RSAPrivateKey.getInstance(pki.parsePrivateKey());
return rsaPrivateKey.getEncoded();
}
/**
* X509编码的公钥转为PKCS1,DER格式.
*
* @param derPubkeyX509Encoded 编码后的X509公钥数据,通过java的{@link Key#getEncoded()}获取
* @return PKCS1编码的DER公钥
*/
public static byte[] der_pubkey_x509ToPkcs1(byte[] derPubkeyX509Encoded) throws Exception {
SubjectPublicKeyInfo spki = SubjectPublicKeyInfo.getInstance(derPubkeyX509Encoded);
org.bouncycastle.asn1.pkcs.RSAPublicKey rsaPublicKey = org.bouncycastle.asn1.pkcs.RSAPublicKey.getInstance(spki.parsePublicKey());
return rsaPublicKey.getEncoded();
}
/**
* PKCS1编码的私钥转为PKCS8,DER格式.
*
* @param derPrikeyPkcs1Encoded 编码后的PKCS1私钥数据
* @return PKCS8编码的DER私钥
*/
public static byte[] der_prikey_pkcs1ToPkcs8(byte[] derPrikeyPkcs1Encoded) throws Exception {
AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(PKCSObjectIdentifiers.pkcs8ShroudedKeyBag);
ASN1Object asn1Object = ASN1Primitive.fromByteArray(derPrikeyPkcs1Encoded);
PrivateKeyInfo privateKeyInfo = new PrivateKeyInfo(algorithmIdentifier, asn1Object);
return privateKeyInfo.getEncoded();
}
/**
* PKCS1编码的公钥转为X509,DER格式.
*
* @param derPubkeyPkcs1Encoded 编码后的PKCS1公钥数据
* @return X509编码的DER公钥
*/
public static byte[] der_pubkey_pkcs1ToX509(byte[] derPubkeyPkcs1Encoded) throws Exception {
AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(PKCSObjectIdentifiers.pkcs8ShroudedKeyBag);
ASN1Object asn1Object = ASN1Primitive.fromByteArray(derPubkeyPkcs1Encoded);
SubjectPublicKeyInfo spki = new SubjectPublicKeyInfo(algorithmIdentifier, asn1Object);
return spki.getEncoded();
}
}