If you find yourself in the situation that you have to reuse your Letsencrypt credentials/account generated by Dehydrated (a bash Letsencrypt interface) with the official Certbot client, like me, you'll want to convert your config files.

In my case, I wanted to change my e-mail address, and the Dehydrated client offered no such command. With Certbot you can do this:

$ certbot register --update-registration --account f65c...

But you'll need your credentials in a format that Certbot groks.

With a bit of trial and error, you can came a long way converting the files:

$ ls /.../dehydrated/accounts/ACCOUNT
account_key.pem  registration_info.json

$ mkdir -p /etc/letsencrypt/accounts/acme-v01.api.letsencrypt.org/directory/ACCOUNT
$ cd /etc/letsencrypt/accounts/acme-v01.api.letsencrypt.org/directory/ACCOUNT
$ cat >meta.json <<EOF
{"creation_host": "my.example.com", "creation_dt": "2016-12-20T14:12:31Z"}
EOF

If you have a sample Certbot regr.json, you'll figure out what to place there based on the contents of the Dehydrated registration_info.json.

registration_info.json:

{
  "id": ACCOUNT_NUMBER,
  "key": {
    "kty": "RSA",
    "n": "MODULUS_IN_BASE64",
    "e": "EXPONENT_IN_BASE64"
  },
  "contact": [],
  "agreement": "https://letsencrypt.org/documents/LE-SA-v1.1.1-August-1-2016.pdf",
  "initialIp": "IP_ADDRESS",
  "createdAt": "2016-12-20T14:12:31.054249908Z",
  "Status": "valid"
}

regr.json:

{
  "body": {
    "contact": [],
    "agreement": "https://letsencrypt.org/documents/LE-SA-v1.1.1-August-1-2016.pdf",
    "key": {
      "kty": "RSA",
      "n": "MODULUS_IN_BASE64",
      "e": "EXPONENT_IN_BASE64"
    }
  },
  "uri": "https://acme-v01.api.letsencrypt.org/acme/reg/ACCOUNT_NUMBER",
  "new_authzr_uri": "https://acme-v01.api.letsencrypt.org/acme/new-authz",
  "terms_of_service": "https://letsencrypt.org/documents/LE-SA-v1.1.1-August-1-2016.pdf"
}

Lastly, you'll need the Certbot private_key.json. It can be converted from Dehydrated account_key.pem, with the following rsapem2json.py python snippet:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#!/usr/bin/env python
# Usage: openssl rsa -in account_key.pem -text -noout | python rsapem2json.py
# Will convert the RSA PEM private key to the Letsencrypt/Certbot
# private_key.json file.
#
# Public Domain, Walter Doekes, OSSO B.V., 2016
#
# From:
#     -----BEGIN RSA PRIVATE KEY-----
#     MIIJJwsdAyjCseEAtNsljpkjhk9143w//jVdsfWsdf9sffLgdsf+sefdfsgE54km
#     ...
#
# To:
#     {"e": "AQAB",
#      "n": "2YIitsUxJlYn_rVn_8Sges...",
#     ...
#
from base64 import b64encode
from sys import stdin

maps = {
    'modulus': 'n', 'privateExponent': 'd', 'prime1': 'p', 'prime2': 'q',
    'coefficient': 'qi', 'exponent1': 'dp', 'exponent2': 'dq'}
extra = {'kty': 'RSA', 'e': '<publicExponent>'}

def block2b64(lines, key):
    found = False
    chars = []
    for line in lines:
        if line.startswith(key + ':'):
            found = True
        elif found and line.startswith(' '):
            for i in line.split(':'):
                i = i.strip()
                if i:
                    chars.append(chr(int(i, 16)))
        elif found:
            break
    assert chars, 'nothing found for {0}'.format(key)
    return b64encode(''.join(chars))

data = stdin.read().split('\n')
conv = dict((v, block2b64(data, k)) for k, v in maps.items())
conv.update(extra)

# Add exponent
e = [i for i in data if i.startswith('publicExponent:')][0]
e = e.split('(', 1)[-1].split(')', 1)[0]
assert e.startswith('0x'), e
e = ('', '0')[len(e) % 2 == 1] + e[2:]
e = b64encode(''.join(chr(int(e[i:i+2], 16)) for i in range(0, len(e), 2)))
conv['e'] = e

# JSON-safe output.
print(repr(conv).replace("'", '"'))

Don't forget to chmod 400 the private_key.json.

python encryption letsencrypt