在 Java 中生成 PKCS#1 格式的 RSA 密钥
Posted
技术标签:
【中文标题】在 Java 中生成 PKCS#1 格式的 RSA 密钥【英文标题】:Generating RSA keys in PKCS#1 format in Java 【发布时间】:2011-11-28 12:14:21 【问题描述】:当我使用 Java API 生成 RSA 密钥对时,公钥以 X.509 格式编码,私钥以 PKCS#8 格式编码。我希望将两者都编码为 PKCS#1。这可能吗?我花了相当多的时间浏览 Java 文档,但还没有找到解决方案。当我使用 Java 和 Bouncy Castle 提供程序时,结果是相同的。
这是一个sn-p的代码:
KeyPairGenerator keygen = KeyPairGenerator.getInstance("RSA","BC");
keygen.initialize(1024);
KeyPair pair = keygen.generateKeyPair();
PrivateKey priv = pair.getPrivate();
PublicKey pub = pair.getPublic();
byte[] privBytes = priv.getEncoded();
byte[] pubBytes = pub.getEncoded();
生成的两个字节数组被格式化为 X.509(公共)和 PKCS#8(私有)。
任何帮助将不胜感激。有一些类似的帖子,但没有一个真正回答我的问题。
谢谢
【问题讨论】:
你能给出你当前用来生成密钥的代码的sn-p吗? 好的,我添加了一个sn-p。谢谢。 【参考方案1】:您将需要 BouncyCastle:
import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemWriter;
下面的代码 sn-ps 已经过检查,发现可以与 Bouncy Castle 1.52 一起使用。
私钥
将私钥从 PKCS8 转换为 PKCS1:
PrivateKey priv = pair.getPrivate();
byte[] privBytes = priv.getEncoded();
PrivateKeyInfo pkInfo = PrivateKeyInfo.getInstance(privBytes);
ASN1Encodable encodable = pkInfo.parsePrivateKey();
ASN1Primitive primitive = encodable.toASN1Primitive();
byte[] privateKeyPKCS1 = primitive.getEncoded();
将PKCS1中的私钥转换为PEM:
PemObject pemObject = new PemObject("RSA PRIVATE KEY", privateKeyPKCS1);
StringWriter stringWriter = new StringWriter();
PemWriter pemWriter = new PemWriter(stringWriter);
pemWriter.writeObject(pemObject);
pemWriter.close();
String pemString = stringWriter.toString();
使用命令行 OpenSSL 检查密钥格式是否符合预期:
openssl rsa -in rsa_private_key.pem -noout -text
公钥
将公钥从 X.509 SubjectPublicKeyInfo 转换为 PKCS1:
PublicKey pub = pair.getPublic();
byte[] pubBytes = pub.getEncoded();
SubjectPublicKeyInfo spkInfo = SubjectPublicKeyInfo.getInstance(pubBytes);
ASN1Primitive primitive = spkInfo.parsePublicKey();
byte[] publicKeyPKCS1 = primitive.getEncoded();
将 PKCS1 中的公钥转换为 PEM:
PemObject pemObject = new PemObject("RSA PUBLIC KEY", publicKeyPKCS1);
StringWriter stringWriter = new StringWriter();
PemWriter pemWriter = new PemWriter(stringWriter);
pemWriter.writeObject(pemObject);
pemWriter.close();
String pemString = stringWriter.toString();
使用命令行 OpenSSL 检查密钥格式是否符合预期:
openssl rsa -in rsa_public_key.pem -RSAPublicKey_in -noout -text
谢谢
非常感谢以下帖子的作者:
https://***.com/a/8713518/1016580 https://***.com/a/14052651/1016580 https://***.com/a/14068057/1016580这些帖子包含有用但不完整且有时已过时的信息(即针对旧版本的 BouncyCastle),这有助于我构建此帖子。
【讨论】:
不错的答案,节省了我的一天 PemObject的构造函数中的“type”参数可以是PEMParser.TYPE_RSA_PUBLIC_KEY
而不是"RSA PRIVATE KEY"
【参考方案2】:
我写了一个 C 程序来将 pkcs8 私钥转换为 pkcs1。有效!
/*****************************************
convert pkcs8 private key file to pkcs1
2013-1-25 Larry Wu created
****************************************/
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <openssl/rsa.h>
#include <openssl/bio.h>
#include <openssl/err.h>
#include <openssl/pem.h>
#include <openssl/engine.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <assert.h>
#include <stdarg.h>
#include <fstream>
#include <sstream>
#include <iostream>
#include <map>
#include <set>
#include <list>
#include <vector>
using namespace std;
#define MY_TRACE_ERROR printf
/*
gcc -Wall -o pkcs_8to1 pkcs_8to1.cpp -g -lstdc++ -lcrypto -lssl
*/
int main(int argc, char **argv)
EVP_PKEY * pkey = NULL;
string kin_fname;
FILE *kin_file = NULL;
string kout_fname;
FILE *kout_file = NULL;
// param
if(argc != 3)
printf("Usage: %s <pkcs8_key_file> <pkcs1_key_file>\n", argv[0]);
return 1;
kin_fname = argv[1];
kout_fname = argv[2];
// init
OpenSSL_add_all_digests();
ERR_load_crypto_strings();
// read key
if((kin_file = fopen(kin_fname.c_str(), "r")) == NULL)
MY_TRACE_ERROR("kin_fname open fail:%s\n", kin_fname.c_str());
return 1;
if ((pkey = PEM_read_PrivateKey(kin_file, NULL, NULL, NULL)) == NULL)
ERR_print_errors_fp(stderr);
MY_TRACE_ERROR("PEM_read_PrivateKey fail\n");
fclose(kin_file);
return 2;
// write key
if((kout_file = fopen(kout_fname.c_str(), "w")) == NULL)
MY_TRACE_ERROR("kout_fname open fail:%s\n", kout_fname.c_str());
return 1;
if (!PEM_write_PrivateKey(kout_file, pkey, NULL, NULL, 0, NULL, NULL))
ERR_print_errors_fp(stderr);
MY_TRACE_ERROR("PEM_read_PrivateKey fail\n");
fclose(kout_file);
return 2;
// clean
fclose(kin_file);
fclose(kout_file);
EVP_PKEY_free(pkey);
return 0;
【讨论】:
这在 OpenSSL 1.0.0 之后就不起作用了,OpenSSL 1.0.0 实际上是在 2010 年发布但最初传播缓慢;您必须改为从EVP_PKEY
中提取RSA
(或仅使用PEM_read[_bio]_RSAPrivateKey
读取——PEM 读取实际上经过共享逻辑然后转换)并使用PEM_write[_bio]_RSAPrivateKey
。或者更容易使用命令行:openssl rsa <pkcs8pem >pkcs1pem
。而且由于 1.0.0 命令行也可以使用-RSAPublicKey_out
(如果适用,-pubin
)执行 PKCS1-form public 密钥。【参考方案3】:
我知道这是旧帖子。但是我花了两天时间解决了这个问题,终于发现BouncyCastle可以做到这一点
ASN1 可编码
http://www.bouncycastle.org/docs/docs1.5on/org/bouncycastle/asn1/ASN1Encodable.html
【讨论】:
【参考方案4】:从RFC5208 开始,PKCS#8 未加密格式由PrivateKeyInfo
结构组成:
privateKey
在哪里:
"...一个八位组字符串,其内容是私钥的值。内容的解释在私钥算法的注册中定义。例如,对于RSA私钥,内容是RSAPrivateKey 类型值的 BER 编码。"
RSAPrivateKey
结构只是密钥的 PKCS#1 编码,我们可以使用 BouncyCastle 提取:
// pkcs8Bytes contains PKCS#8 DER-encoded key as a byte[]
PrivateKeyInfo pki = PrivateKeyInfo.getInstance(pkcs8Bytes);
RSAPrivateKeyStructure pkcs1Key = RSAPrivateKeyStructure.getInstance(
pki.getPrivateKey());
byte[] pkcs1Bytes = pkcs1Key.getEncoded(); // etc.
【讨论】:
【参考方案5】:我试图使用移植到 BlackBerry 的 BountyCastle J2ME 库生成 DER 格式的 OpenSSL 友好 RSA 公钥,我的代码:
public void testMe() throws Exception
RSAKeyPairGenerator generator = new RSAKeyPairGenerator();
generator.init(new RSAKeyGenerationParameters(BigInteger.valueOf(0x10001),
new SecureRandom(), 512, 80));
AsymmetricCipherKeyPair keyPair = generator.generateKeyPair();
RSAKeyParameters params = (RSAKeyParameters) keyPair.getPublic();
RSAPublicKeyStructure struct = new RSAPublicKeyStructure(params.getModulus(),
params.getExponent());
SubjectPublicKeyInfo info =
new SubjectPublicKeyInfo(new AlgorithmIdentifier("1.2.840.113549.1.1.1"),
struct);
byte[] bytes = info.getDEREncoded();
FileOutputStream out = new FileOutputStream("/tmp/test.der");
out.write(bytes);
out.flush();
out.close();
密钥仍然不正确:
$ openssl asn1parse -in test.der -inform DER -i
0:d=0 hl=2 l= 90 cons: SEQUENCE
2:d=1 hl=2 l= 11 cons: SEQUENCE
4:d=2 hl=2 l= 9 prim: OBJECT :rsaEncryption
15:d=1 hl=2 l= 75 prim: BIT STRING
我更改了 org.bouncycastle.asn1.x509.AlgorithmIdentifier
public AlgorithmIdentifier(
String objectId)
this.objectId = new DERObjectIdentifier(objectId);
// This line has been added
this.parametersDefined = true;
现在有了漂亮的钥匙:
$ openssl asn1parse -in test.der -inform DER -i
0:d=0 hl=2 l= 92 cons: SEQUENCE
2:d=1 hl=2 l= 13 cons: SEQUENCE
4:d=2 hl=2 l= 9 prim: OBJECT :rsaEncryption
15:d=2 hl=2 l= 0 prim: NULL
17:d=1 hl=2 l= 75 prim: BIT STRING
可以用来加密的:
$ echo "123" | openssl rsautl -pubin -inkey test.der -encrypt -keyform DER -out y
$ wc -c y
64 y
【讨论】:
这是一个糟糕而危险的想法。 X.509AlgorithmIdentifier
类型在很多地方都有使用,并且在某些情况下要求它没有参数。幸运的是,Bouncy 在 1.53 中进行了更改,因此无法编译,从而降低了有人使用它的风险。 正确的方式是调用2-arg ctor设置参数为null,如org.bouncycastle.crypto.util.SubjectPublicKeyInfoFactory org.bouncycastle.jce.provider.JCERSAPublic,Private,PrivateCrtKey org.bouncycastle.jcajce.provider.asymmetric.rsa.BCRSAPublic,Private,PrivateCrtKey
【参考方案6】:
BouncyCastle 框架有一个 PKCS1 编码器来解决这个问题:http://www.bouncycastle.org/docs/docs1.6/index.html
【讨论】:
感谢您的回复 klaustopher。我的理解是 BC 库中的 PKCS1Encoding 类用于使用 PKCS1 填充进行加密/解密。我实际上是在尝试将密钥本身的格式更改为 PKCS#1。 PKCS#1 定义了密钥的格式,以及加密的填充方案。来自***:定义了 RSA 公钥和私钥(以明文形式编码的 ASN.1)的数学属性和格式,以及用于执行 RSA 加密、解密以及生成和验证的基本算法和编码/填充方案签名。_以上是关于在 Java 中生成 PKCS#1 格式的 RSA 密钥的主要内容,如果未能解决你的问题,请参考以下文章