Request For CertBot To Support The Signing of Ed25519 Certificates

OpenSSL clearly already supports the generate of Ed25519 private keys and derived certificates. But the Certbot robot does not support the signing of such certificates by widely respected Certificate Authorities.

(Can/should ISRG submit a proposal to support Ed25519/Ed448 certificates to CA/B Forum? - #9 by schoen)

Unlike the ECDSA algorithms, Ed25519 cannot reveal the public key even if the attacker has access to a message and its corresponding digital signature:

Reference: EdDSA and Ed25519 - Practical Cryptography for Developers

Moreover, unlike ECDSA, Ed25519 will not reveal the private key even if the user accidentially reveals the nonce used to sign a message. This vulnerability exists for secp256k1, P-256, and P-512:


Finally, Ed25519 is simpler in design than ECDSA. May Certbot please start supporting the signing of Ed25519 certificates by trustworthy Certificate Authority


What's the stance of CA/B forum on such certificates?


Do you know of any CA already issuing Ed25519 certificates? It's not like that if certbot would support it, suddenly CAs will issue certs with Ed25519.

Also, I'm not sure if this Community is the right place to ask for this feature request for certbot. The "About" thread in this Feature Requests section does actually state it's the right place to ask for feature requests for the "Let's Encrypt client", but that was back in 2015! Since then the development of certbot has been taken over by the EFF. So maybe the certbot Github page would be the most likely candidate to request this feature request: Issues · certbot/certbot · GitHub Although I think chances are pretty slim that they'll implement it if CAs are not allowed to issue Ed25519 certs.


That's exactly it; see this thread: Support Ed25519 and Ed448


I don't think anyone can realistically expect LetsEncrypt/Certbot to support the CA/B forum adopting this standard out of the blue.

Perhaps someone would be interested in coding a Certbot/Pebble POC? I'm certainly not :wink: – but I do think there is probably someone passionate about this, and if successful that experiment could motivate the LetsEncrypt staff to advocate for it.

It's possible to do a POC in Certbot, as Python's Cryptography toolkit supports it: Search · ed25519 · GitHub

I haven't looked into support with go.

IMHO it would make sense for someone to make a POC to explore the feasibility of this, and use that to lobby LetsEncrypt/Certbot, who could then consider whether or not it should be advocated to the CA/B forum.


Pebble doesn't care about the contents of the private key, I checked. No code checking RSA/ECDSA/EdDSA what so ever :smiley:

For certbot on the other hand: different story. I tried a few hours yesterday, but it's not as simple as I thought. Cerbot uses multiple libraries for the RSA/ECDSA key processing (cryptography and OpenSSLs crypt) and for some reason it didn't play well together.. I was stuck on getting crypt to actually sign the CSR without a hash. Usually, a CSR is signed with the RSA/ECDSA key in combination with a hash function, but with EdDSA it seems you don't need the hash function? Tell crypt that! I couldn't...


looks like ed25519 defined to use SHA-512 as hash function, try send it as parameter anyway?


Assuming you have your own ACME enabled Certificate Authority you could perhaps use your own CSR with --csr in certonly mode. As far as I know there are no web browsers(?) which would be able to consume an Ed25519 certificate, if that's relevant to your use case.


That didn't work, but the following did work:

diff --git a/certbot/certbot/_internal/cli/ b/certbot/certbot/_internal/cli/
index 4212c353b..38f9674a4 100644
--- a/certbot/certbot/_internal/cli/
+++ b/certbot/certbot/_internal/cli/
@@ -279,7 +279,7 @@ def prepare_and_parse_args(plugins, args, detect_defaults=False):
         "security", "--rsa-key-size", type=int, metavar="N",
         default=flag_default("rsa_key_size"), help=config_help("rsa_key_size"))
-        "security", "--key-type", choices=['rsa', 'ecdsa'], type=str,
+        "security", "--key-type", choices=['rsa', 'ecdsa', 'ed25519'], type=str,
         default=flag_default("key_type"), help=config_help("key_type"))
         "security", "--elliptic-curve", type=str, choices=[
diff --git a/certbot/certbot/ b/certbot/certbot/
index e620d43e0..be903e866 100644
--- a/certbot/certbot/
+++ b/certbot/certbot/
@@ -19,6 +19,7 @@ from cryptography.hazmat.backends import default_backend
 from cryptography.hazmat.primitives.asymmetric import ec
 from import ECDSA
 from import EllipticCurvePublicKey
+from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
 from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15
 from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey
 from cryptography.hazmat.primitives.serialization import Encoding
@@ -289,6 +290,15 @@ def make_key(bits=1024, key_type="rsa", elliptic_curve=None):
         key = crypto.load_privatekey(crypto.FILETYPE_PEM, _key_pem)
+    elif key_type == 'ed25519':
+        _key = Ed25519PrivateKey.generate()
+        _key_pem = _key.private_bytes(
+            encoding=Encoding.PEM,
+            format=PrivateFormat.PKCS8,
+            encryption_algorithm=NoEncryption()
+        )
+        key = crypto.load_privatekey(crypto.FILETYPE_PEM, _key_pem)
         raise errors.Error("Invalid key_type specified: {}.  Use [rsa|ecdsa]".format(key_type))
     return crypto.dump_privatekey(crypto.FILETYPE_PEM, key)


diff --git a/acme/acme/ b/acme/acme/
index 749478bf5..52435970e 100644
--- a/acme/acme/
+++ b/acme/acme/
@@ -214,7 +214,7 @@ def make_csr(private_key_pem, domains, must_staple=False):
-    csr.sign(private_key, 'sha256')
+    csr.sign(private_key, 'NULL')
     return crypto.dump_certificate_request(
         crypto.FILETYPE_PEM, csr)

AND modify OpenSSLs crypt!:

diff -Naur a/lib64/python3.9/site-packages/OpenSSL/ b/lib64/python3.9/site-packages/OpenSSL/
--- a/lib64/python3.9/site-packages/OpenSSL/	2021-08-13 20:35:44.284389392 +0200
+++ b/lib64/python3.9/site-packages/OpenSSL/	2021-08-13 20:36:24.206705683 +0200
@@ -1052,8 +1052,8 @@
             raise ValueError("Key is uninitialized")
         digest_obj = _lib.EVP_get_digestbyname(_byte_string(digest))
-        if digest_obj == _ffi.NULL:
-            raise ValueError("No such digest method")
+        #if digest_obj == _ffi.NULL:
+        #    raise ValueError("No such digest method")
         sign_result = _lib.X509_REQ_sign(self._req, pkey._pkey, digest_obj)
         _openssl_assert(sign_result > 0)

Without that latter modification, OpenSSLs crypt doesn't accept the NULL parameter..

This is of course just a proof of concept modification and shouldn't be used. :slight_smile:

Also note that the following actually works too, for Ed25519:

diff --git a/acme/acme/ b/acme/acme/
index 749478bf5..56c16f7d9 100644
--- a/acme/acme/
+++ b/acme/acme/
@@ -214,7 +214,7 @@ def make_csr(private_key_pem, domains, must_staple=False):
-    csr.sign(private_key, 'sha256')
+    csr.sign(private_key, 'FOOBAR')
     return crypto.dump_certificate_request(
         crypto.FILETYPE_PEM, csr)

... as long as you also modify the OpenSSL crypt part to ignore any non-existing digest :stuck_out_tongue: But an existing digest does not work :slight_smile:



This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.

For anyone interested. I've incorporated part of above code and augmented it so it doesn't require a patched OpenSSL (Python) library!

You can find a certbot version supporting Ed25518 as wel as Ed448 here:

Unfortunately Go does not support Ed448 (see proposal: x/crypto: add implementation of Diffie-Hellman x448 · Issue #29390 · golang/go · GitHub for their arguments), so I believe SmallStep does not support Ed448 (but it does support EdDSA, probably Ed25519 only) and I could not test the above code fully with aid of Pebble (although it functions almost completely, it just doesn't get a certificate back from Pebble..)


Very cool! Apache and OpenSSL (and curl, at least when compiled against OpenSSL) support EdDSA:

osiris@erazer ~ $ openssl s_client -CAfile /tmp/root.pem -connect
depth=2 CN = Pebble Root CA 7520ca
verify return:1
depth=1 CN = Pebble Intermediate CA 529b41
verify return:1
depth=0 CN =
verify return:1
Certificate chain
 0 s:CN =
   i:CN = Pebble Intermediate CA 529b41
 1 s:CN = Pebble Intermediate CA 529b41
   i:CN = Pebble Root CA 7520ca
Server certificate
subject=CN =

issuer=CN = Pebble Intermediate CA 529b41

No client certificate CA names sent
Peer signature type: Ed25519
Server Temp Key: X25519, 253 bits

See above how (almost) the entire handshake is done using Curve25519! Ed25519 for the signature and X25519 for the ECDHE key exchange!

Unfortunately Pebble uses RSA intermediates and roots.. :cry: So a large part, if not most, of the data in the certificate are due to the RSA signature :stuck_out_tongue:


I can confirm that SmallStep returns an "algorithm unimplemented" error when trying to get a Ed448 CSR signed unfortunately.. I guess another reason NOT to use Go (developers making strange decisions..)