How to get openssl rsa private key out of private_key.json

How to get a openssl genrsa compatible key out of the private_key.json which the official letsencrypt client creates (i.e., for using in e.g. https://github.com/diafygi/acme-tiny/)?

For Example with Java:
final Decoder dec = java.util.Base64.getUrlDecoder(); final Pattern pattern = Pattern.compile("\\{\"e\": \"(.*)\", \"d\": \"(.*)\", \"n\": \"(.*)\", \"q\": \"(.*)\", \"p\": \"(.*)\", \"kty\": \"(.*)\", \"qi\": \"(.*)\", \"dp\": \"(.*)\", \"dq\": \"(.*)\"}"); final String alias = "letsencryptUserAlias"; final String keyFile = argc[0]; final String storeFile = argc[1]; final File f = new File(keyFile); final byte[] b = new byte[(int)f.length()]; try(InputStream s = new FileInputStream(f)) { s.read(b, 0, b.length); } final String json = new String(b); final Matcher m6 = pattern.matcher(json); if(!m6.matches()) throw new IllegalArgumentException(); final BigInteger e = new BigInteger(1, dec.decode(m6.group(1))); final BigInteger d = new BigInteger(1, dec.decode(m6.group(2))); final BigInteger n = new BigInteger(1, dec.decode(m6.group(3))); final BigInteger q = new BigInteger(1, dec.decode(m6.group(4))); final BigInteger p = new BigInteger(1, dec.decode(m6.group(5))); final BigInteger qi = new BigInteger(1, dec.decode(m6.group(7))); final BigInteger dp = new BigInteger(1, dec.decode(m6.group(8))); final BigInteger dq = new BigInteger(1, dec.decode(m6.group(9))); final KeyFactory kf = KeyFactory.getInstance("RSA"); final RSAPrivateCrtKey s_key = (RSAPrivateCrtKey)kf.generatePrivate(new RSAPrivateCrtKeySpec(n, e, d, p, q, dp, dq, qi));

1 Like

@tlussnig Thanks!

Here my full adapted source:

public static void main(String[] args) throws FileNotFoundException, IOException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException {
    final Decoder dec = java.util.Base64.getUrlDecoder();
    final Pattern pattern = Pattern.compile("\\{\"e\": \"(.*)\", \"d\": \"(.*)\", \"n\": \"(.*)\", \"q\": \"(.*)\", \"p\": \"(.*)\", \"kty\": \"(.*)\", \"qi\": \"(.*)\", \"dp\": \"(.*)\", \"dq\": \"(.*)\"}");
    final String keyFile = args[0];
    final File f = new File(keyFile);
    final byte[] b = new byte[(int)f.length()];
    try(InputStream s = new FileInputStream(f)) { s.read(b, 0, b.length); }
    final String json = new String(b);
    final Matcher m6 = pattern.matcher(json);
    if(!m6.matches()) throw new IllegalArgumentException();
    final BigInteger e = new BigInteger(1, dec.decode(m6.group(1)));
    final BigInteger d = new BigInteger(1, dec.decode(m6.group(2)));
    final BigInteger n = new BigInteger(1, dec.decode(m6.group(3)));
    final BigInteger q = new BigInteger(1, dec.decode(m6.group(4)));
    final BigInteger p = new BigInteger(1, dec.decode(m6.group(5)));
    final BigInteger qi = new BigInteger(1, dec.decode(m6.group(7)));
    final BigInteger dp = new BigInteger(1, dec.decode(m6.group(8)));
    final BigInteger dq = new BigInteger(1, dec.decode(m6.group(9)));
    final KeyFactory kf = KeyFactory.getInstance("RSA");
    final RSAPrivateCrtKey s_key = (RSAPrivateCrtKey)kf.generatePrivate(new RSAPrivateCrtKeySpec(n, e, d, p, q, dp, dq, qi));

    FileWriter o = new FileWriter(new File(args[1]));
    final String encoded = Base64.getEncoder().encodeToString(s_key.getEncoded());
    o.write(encoded);
    o.close();
}
1 Like

Hate to completely out myself as a java noob, but how on earth does one actually use this code? javac won’t compile it. It doesn’t run directly via the ‘java’ command (saved in a file and passed as an argument). Can those of us that aren’t java devs please get some hints?

Clues are much appreciated.

Thanks,
-C

I’m not sure about the Java code, but if you have Go installed, you can try this program to do a similar thing.

$ go get -u gopkg.in/square/go-jose.v2/...
$ go run certbot-to-pem.go [path to private_key.json]
-----BEGIN RSA PRIVATE KEY-----
<snip>

Here is the program (certbot-to-pem.go):

package main

import (
	"crypto/rsa"
	"crypto/x509"
	"encoding/pem"
	"fmt"
	"io/ioutil"
	"os"
	"reflect"

	"gopkg.in/square/go-jose.v2"
)

func main() {
	if len(os.Args) != 2 {
		fmt.Println("Usage: certbot-to-pem path-to-certbot-private_key.json")
		os.Exit(1)
	}

	pkBuf, err := ioutil.ReadFile(os.Args[1])
	if err != nil {
		panic(err)
	}

	var k jose.JSONWebKey
	if err := k.UnmarshalJSON(pkBuf); err != nil {
		panic(err)
	}

	switch p := k.Key.(type) {
	case *rsa.PrivateKey:
		fmt.Println(string(pem.EncodeToMemory(&pem.Block{
			Type:  "RSA PRIVATE KEY",
			Bytes: x509.MarshalPKCS1PrivateKey(p),
		})))
	default:
		panic("Don't know how to deal with " + reflect.TypeOf(p).String())
	}
}

Awesome - thank you for the go code. I was able to extract a valid RSA key out of the private_key.json file. Saved this off to a private_key.pem file and openssl reports “RSA key ok” when I run a ‘-check’ against it.

Now I’m trying to sort out why the key extracted is being reported by openssl as not matching the certificate I was issued by letsencrypt. Am I just not caffeinated enough and completely missing how this private key is used in letsencrypt?

End result I need is both the private key and certificate available so that I can use them in a slapd instance.

That’s your Let’s Encrypt account key. It isn’t used for certificates.

Your certificate private key is present as a symlink in /etc/letsencrypt/live/{domain}/privkey.pem .

Gotcha - thanks. That’s where I know they get stored on my other nodes. This system in particular though never set that dir structure up when the letsencrypt provisioning ran:

=====

get a new cert

letsencrypt certonly --standalone --csr “$WORKDIR/httpd-csr.der” --email “$EMAIL” --agree-tos

It’s a FreeIPA server and someone had contributed a routine to handle generation and installation of letsencrypt certs for the various IPA services:
https://github.com/freeipa/freeipa-letsencrypt.git

That’s why I was searching around to see if I could figure out some other place that the letsencrypt utility had perhaps put the private key and had for some reason thought it was being stored in the private_key.json file, but I now follow the difference. I’ll re-run things via the CLI and get the proper dir structure working.

Thanks again for the help and enlightenment. Been staring at this for too long so obviously was getting confused.

-Chris

When Certbot is run with the “--csr” option, it doesn’t set things up in the typical /etc/letsencrypt/live/ location.

That’s why the renew-le.sh program is copying the certificate from a “0000_cert.pem” file somewhere.

The CSR was generated by certutil. I’m not familiar with certutil, but I assume it either read a private key from somewhere, or generated one and saved it somewhere.

You should consult the configuration or documentation for FreeIPA or certutil to try to determine where the key is.

Edit: I guess it’s in /etc/httpd/alias/ – possibly not in a .pem file – but I’m really not sure.

In the acme-tiny documentation, there’s also a way to do the conversion with Python: https://github.com/diafygi/acme-tiny/#use-existing-lets-encrypt-key