ruby/ruby · error · OpenSSL::PKey::PKeyError

OpenSSL::PKey::DH is immutable on OpenSSL 3.0; use OpenSSL::

Error message

OpenSSL::PKey::DH is immutable on OpenSSL 3.0; use OpenSSL::PKey.generate_key instead

What it means

OpenSSL 3.0 made key objects immutable, so the in-place OpenSSL::PKey::DH#generate_key! (which wrote pub_key/priv_key into the same object) can no longer be implemented. Ruby/OpenSSL raises PKeyError whenever generate_key! is called while linked against OpenSSL 3.0 or newer, pointing at OpenSSL::PKey.generate_key, the functional-style replacement. The version check runs before any key material is inspected, so even a fully parameterized DH object raises.

Source

Thrown at ext/openssl/lib/openssl/pkey.rb:108

    # OpenSSL 3.0.0 or later.
    #
    # See also OpenSSL::PKey.generate_key.
    #
    # Example:
    #   # DEPRECATED USAGE: This will not work on OpenSSL 3.0 or later
    #   dh0 = OpenSSL::PKey::DH.new(2048)
    #   dh = dh0.public_key # #public_key only copies the DH parameters (contrary to the name)
    #   dh.generate_key!
    #   puts dh.private? # => true
    #   puts dh0.pub_key == dh.pub_key #=> false
    #
    #   # With OpenSSL::PKey.generate_key
    #   dh0 = OpenSSL::PKey::DH.new(2048)
    #   dh = OpenSSL::PKey.generate_key(dh0)
    #   puts dh0.pub_key == dh.pub_key #=> false
    def generate_key!
      if OpenSSL::OPENSSL_VERSION_NUMBER >= 0x30000000
        raise PKeyError, "OpenSSL::PKey::DH is immutable on OpenSSL 3.0; " \
        "use OpenSSL::PKey.generate_key instead"
      end

      unless priv_key
        tmp = OpenSSL::PKey.generate_key(self)
        set_key(tmp.pub_key, tmp.priv_key)
      end
      self
    end

    class << self
      # :call-seq:
      #    DH.generate(size, generator = 2) -> dh
      #
      # Creates a new DH instance from scratch by generating random parameters
      # and a key pair.
      #
      # See also OpenSSL::PKey.generate_parameters and

View on GitHub (pinned to 0e5b888e1c)

Solutions

  1. Replace dh.generate_key! with dh = OpenSSL::PKey.generate_key(dh_params), using the returned new key.
  2. If you first copied parameters (dh.public_key or a params-only PEM), feed that parameter object straight into OpenSSL::PKey.generate_key.
  3. Upgrade gems that call generate_key! internally (older net-ssh and similar).
  4. Running against OpenSSL 1.1.1 avoids the raise but is unsupported; treat it only as a temporary stopgap.

Example fix

# before (OpenSSL 1.1.1 era)
dh = OpenSSL::PKey::DH.new(2048)
dh.generate_key! # raises PKeyError on OpenSSL 3.0

# after
dh_params = OpenSSL::PKey::DH.new(2048)
dh = OpenSSL::PKey.generate_key(dh_params)
Defensive patterns

Strategy: fallback

Validate before calling

dh = if OpenSSL::OPENSSL_VERSION_NUMBER >= 0x30000000
  OpenSSL::PKey.generate_key(dh_params) # OpenSSL 3.0+
else
  dh_params.generate_key!             # OpenSSL 1.1.x
end

Try / catch

begin
  dh.generate_key!
rescue OpenSSL::PKey::PKeyError
  dh = OpenSSL::PKey.generate_key(dh)
end

Prevention

When it happens

Trigger: Any call to dh.generate_key! on OpenSSL::PKey::DH when OpenSSL::OPENSSL_VERSION_NUMBER >= 0x30000000, regardless of whether the receiver has p/g parameters set.

Common situations: Upgrading the OS or Ruby build from OpenSSL 1.1.1 to 3.x (Ubuntu 22.04, Debian 12, RHEL 9); legacy scripts that copy DH parameters via public_key and then generate a key in place; older versions of gems such as net-ssh calling generate_key! internally.

Related errors


AI-assisted analysis of ruby/ruby@0e5b888e1c (2026-08-21). Data as JSON: /api/errors/1937b1a0f381f52f. Report an issue: GitHub.