Search code examples
python-3.xssl

Python dh key too small, which side is faulty?


Using Python 3, I'm trying to connect using a SSL context to a remote SMTP host, but I get the following error:

[SSL: DH_KEY_TOO_SMALL] dh key too small (_ssl.c:1056)

Here's the code I use:

from smtplib import SMTP
import ssl, os, certifi

ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH, cafile=certifi.where())
ssl_context.options |= ssl.OP_NO_TLSv1
ssl_context.options |= ssl.OP_NO_TLSv1_1

ssl_context.load_cert_chain(os.path.join(certsdir, 'certificate.pem'), os.path.join(certsdir, 'id_rsa'))
ssl_context.load_dh_params(os.path.join(certsdir, 'dhparams.pem'))

ssl_context.check_hostname = False
ssl_context.verify_mode = ssl.CERT_NONE

smtp = SMTP(server_name)
smtp.connect((host, 25))
smtp.ehlo()
if smtp.has_extn('starttls'):
    smtp.starttls(None, None, ssl_context)
    smtp.ehlo()

smtp.mail(fromaddr)
smtp.rcpt(toaddr)
smtp.data(message)
smtp.quit()

Is the issue on my end, or on the destination's server end? Is there something I can do to avoid this issue?

I use certifi for the list of certificates, that is based on Mozilla recommendations and is up-to-date.


Solution

  • Question: Is the issue on my end, or on the destination's server end?

    The server is offering a weak DH key, the client (your script) wants a stronger key. The problem should usually be fixed at the server side. And note that your call of load_dh_params makes no sense since setting the DH key is only relevant for the server side.

    Is there something I can do to avoid this issue?

    Don't use DH ciphers in the first place. All modern clients support ECDHE ciphers which don't have this problem. DH is very slow anyway. Usually the client would also choose a ECDHE cipher if offered and this error will not happen. While it might be that the TLS stack at the client is too old and prefers DH, such an old stack would usually not complain about a weak DH. It is thus more likely that the servers SSL stack is too old so that it does not offer the more modern ciphers the clients wants by default.

    To make sure that no DH ciphers are offered by the client and thus ECDHE or RSA key exchange is used set the ciphers accordingly:

     ssl_context.set_ciphers('DEFAULT:!DH')
    

    Note though that RSA key exchange is considered obsolete too since it does not provide any forward secrecy. You might therefore try if the server can do without DH and without kRSA by using a cipher string of DEFAULT:!DH:!kRSA.