Generating RSA keys in PKCS#1 format in Java
Asked Answered
R

6

27

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();
byte[] 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

Roundy answered 30/9, 2011 at 13:51 Comment(2)
Can you give a snippet of the code you are currently using to generate the keys?Rickierickman
Ok, I added a snippet. Thanks.Roundy
W
38

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:

Those posts contained useful, but incomplete and sometimes outdated info (i.e. for older versions of BouncyCastle), that helped me to construct this post.

Winepress answered 27/4, 2015 at 9:53 Comment(2)
Nice answer, saves my dayMarillin
The "type" argument in the constructor of the PemObject can be PEMParser.TYPE_RSA_PUBLIC_KEY instead of "RSA PRIVATE KEY"Irita
W
7

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.
Whispering answered 3/1, 2012 at 14:12 Comment(0)
R
0

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;
}
Rompers answered 29/1, 2013 at 3:35 Comment(1)
This doesn't work since OpenSSL 1.0.0, which was actually released in 2010 but initially spread slowly; you must instead extract RSA from EVP_PKEY (or just read with PEM_read[_bio]_RSAPrivateKey -- PEM read actually goes through shared logic and then converts) and use PEM_write[_bio]_RSAPrivateKey. Or much easier just use commandline: openssl rsa <pkcs8pem >pkcs1pem. And since 1.0.0 commandline can also do PKCS1-form public key with -RSAPublicKey_out (and if applicable -pubin).Sympetalous
S
-1

The BouncyCastle framework has a PKCS1 Encoder to solve this: http://www.bouncycastle.org/docs/docs1.6/index.html

Stemware answered 30/9, 2011 at 14:15 Comment(1)
Thank you for your response klaustopher. My understanding is that the PKCS1Encoding class in the BC library is for encrypting/decrypting using PKCS1 padding. I'm actually trying to change the format of the keys themselves to be PKCS#1. PKCS#1 defines the format of the keys, as well as the padding schemes for encryption. From wikipedia: Defines the mathematical properties and format of RSA public and private keys (ASN.1-encoded in clear-text), and the basic algorithms and encoding/padding schemes for performing RSA encryption, decryption, and producing and verifying signatures._Roundy
T
-1

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
Tengdin answered 3/1, 2012 at 11:39 Comment(1)
THIS IS A BAD AND DANGEROUS IDEA. The X.509 AlgorithmIdentifier type is used in many places and in some it is required NOT to have parameters. Fortunately Bouncy changed in 1.53 so this doesn't compile, lessening the risk of someone using it. The correct way is to call the 2-arg ctor to set the parameters to null, as shown in org.bouncycastle.crypto.util.SubjectPublicKeyInfoFactory org.bouncycastle.jce.provider.JCERSA{Public,Private,PrivateCrt}Key org.bouncycastle.jcajce.provider.asymmetric.rsa.BCRSA{Public,Private,PrivateCrt}KeySympetalous
C
-2

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

Chaudoin answered 17/12, 2014 at 16:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.