When I generate an RSA key pair using the Java API, the public key is encoded in the X.509 format and the private key is encoded in the PKCS#8 format. I'm looking to encode both as PKCS#1. Is this possible? I've spent a considerable amount of time going through the Java docs but haven't found a solution. The result is the same when I use the Java and the Bouncy Castle providers.
Here is a snippet of the code:
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();
b开发者_StackOverflow中文版yte[] pubBytes = pub.getEncoded();
The two resulting byte arrays are formatted as X.509 (public) and PKCS#8 (private).
Any help would be much appreciated. There are some similar posts but none really answer my question.
Thank You
You will need 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;
The code snippets below have been checked and found working with Bouncy Castle 1.52.
Private key
Convert private key from PKCS8 to 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();
Convert private key in PKCS1 to 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();
Check with command line OpenSSL that the key format is as expected:
openssl rsa -in rsa_private_key.pem -noout -text
Public key
Convert public key from X.509 SubjectPublicKeyInfo to PKCS1:
PublicKey pub = pair.getPublic();
byte[] pubBytes = pub.getEncoded();
SubjectPublicKeyInfo spkInfo = SubjectPublicKeyInfo.getInstance(pubBytes);
ASN1Primitive primitive = spkInfo.parsePublicKey();
byte[] publicKeyPKCS1 = primitive.getEncoded();
Convert public key in PKCS1 to 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();
Check with command line OpenSSL that the key format is as expected:
openssl rsa -in rsa_public_key.pem -RSAPublicKey_in -noout -text
Thanks
Many thanks to the authors of the following posts:
- https://stackoverflow.com/a/8713518/1016580
- https://stackoverflow.com/a/14052651/1016580
- https://stackoverflow.com/a/14068057/1016580
Those posts contained useful, but incomplete and sometimes outdated info (i.e. for older versions of BouncyCastle), that helped me to construct this post.
From RFC5208, the PKCS#8 unencrypted format consists of a PrivateKeyInfo
structure:
PrivateKeyInfo ::= SEQUENCE { version Version, privateKeyAlgorithm PrivateKeyAlgorithmIdentifier, privateKey PrivateKey, attributes [0] IMPLICIT Attributes OPTIONAL }
where privateKey
is:
"...an octet string whose contents are the value of the private key. The interpretation of the contents is defined in the registration of the private-key algorithm. For an RSA private key, for example, the contents are a BER encoding of a value of type RSAPrivateKey."
This RSAPrivateKey
structure is just the PKCS#1 encoding of the key, which we can extract using 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.
I wrote a C programme to convert pkcs8 private key to pkcs1. It works!
/*****************************************
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;
}
The BouncyCastle framework has a PKCS1 Encoder to solve this: http://www.bouncycastle.org/docs/docs1.6/index.html
I was trying to generate OpenSSL-friendly RSA public keys in DER format using BountyCastle J2ME library ported to BlackBerry, my code:
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();
}
Key was still incorrect:
$ 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
I changed org.bouncycastle.asn1.x509.AlgorithmIdentifier
public AlgorithmIdentifier(
String objectId)
{
this.objectId = new DERObjectIdentifier(objectId);
// This line has been added
this.parametersDefined = true;
}
And now have nice key:
$ 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
Which can be used to encrypt:
$ echo "123" | openssl rsautl -pubin -inkey test.der -encrypt -keyform DER -out y
$ wc -c y
64 y
I know this is old post. but I spent two days to solve this problem and finally find BouncyCastle can do that
ASN1Encodable
http://www.bouncycastle.org/docs/docs1.5on/org/bouncycastle/asn1/ASN1Encodable.html
精彩评论