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 andView on GitHub (pinned to 0e5b888e1c)
Solutions
- Replace dh.generate_key! with dh = OpenSSL::PKey.generate_key(dh_params), using the returned new key.
- If you first copied parameters (dh.public_key or a params-only PEM), feed that parameter object straight into OpenSSL::PKey.generate_key.
- Upgrade gems that call generate_key! internally (older net-ssh and similar).
- 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
- Treat PKey objects as immutable under OpenSSL 3.0 and derive new keys functionally.
- Grep the codebase and Gemfile for generate_key! and public_key when upgrading OpenSSL.
- Assert the expected OpenSSL::OPENSSL_VERSION_NUMBER in CI.
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.