diff --git a/ext/openssl/ossl_pkey.c b/ext/openssl/ossl_pkey.c
index 013412c27..e7c255c02 100644
--- a/ext/openssl/ossl_pkey.c
+++ b/ext/openssl/ossl_pkey.c
@@ -446,6 +446,183 @@ pkey_generate(int argc, VALUE *argv, VALUE self, int genparam)
     return ossl_pkey_new(gen_arg.pkey);
 }
 
+#if OSSL_OPENSSL_PREREQ(3, 0, 0)
+#include <openssl/param_build.h>
+#include <openssl/core_names.h>
+
+struct pkey_from_data_alias {
+    char alias[10];
+    char param_name[20];
+};
+
+static const struct pkey_from_data_alias rsa_aliases[] = {
+    { "p",    OSSL_PKEY_PARAM_RSA_FACTOR1 },
+    { "q",    OSSL_PKEY_PARAM_RSA_FACTOR2 },
+    { "dmp1", OSSL_PKEY_PARAM_RSA_EXPONENT1 },
+    { "dmq1", OSSL_PKEY_PARAM_RSA_EXPONENT2 },
+    { "iqmp", OSSL_PKEY_PARAM_RSA_COEFFICIENT1 },
+    { "", "" }
+};
+
+static const struct pkey_from_data_alias fcc_aliases[] = {
+    { "pub_key",  OSSL_PKEY_PARAM_PUB_KEY },
+    { "priv_key", OSSL_PKEY_PARAM_PRIV_KEY },
+    { "", "" }
+};
+
+struct pkey_from_data_arg {
+    VALUE options;
+    OSSL_PARAM_BLD *param_bld;
+    const OSSL_PARAM *settable_params;
+    const struct pkey_from_data_alias *aliases;
+};
+
+static int
+add_data_to_builder(VALUE key, VALUE value, VALUE arg) {
+    if(NIL_P(value))
+        return ST_CONTINUE;
+
+    if (SYMBOL_P(key))
+        key = rb_sym2str(key);
+
+    const char *key_ptr = StringValueCStr(key);
+    const struct pkey_from_data_arg *params = (const struct pkey_from_data_arg *) arg;
+
+    for(int i = 0; strlen(params->aliases[i].alias) > 0; i++) {
+        if(strcmp(params->aliases[i].alias, key_ptr) == 0) {
+            key_ptr = params->aliases[i].param_name;
+            break;
+        }
+    }
+
+    for (const OSSL_PARAM *settable_params = params->settable_params; settable_params->key != NULL; settable_params++) {
+        if(strcmp(settable_params->key, key_ptr) == 0) {
+            switch (settable_params->data_type) {
+            case OSSL_PARAM_INTEGER:
+            case OSSL_PARAM_UNSIGNED_INTEGER:
+                if(!OSSL_PARAM_BLD_push_BN(params->param_bld, key_ptr, GetBNPtr(value))) {
+                    ossl_raise(ePKeyError, "OSSL_PARAM_BLD_push_BN");
+                }
+                break;
+            case OSSL_PARAM_UTF8_STRING:
+                StringValue(value);
+                if(!OSSL_PARAM_BLD_push_utf8_string(params->param_bld, key_ptr, RSTRING_PTR(value), RSTRING_LENINT(value))) {
+                    ossl_raise(ePKeyError, "OSSL_PARAM_BLD_push_utf8_string");
+                }
+                break;
+
+            case OSSL_PARAM_OCTET_STRING:
+                StringValue(value);
+                if(!OSSL_PARAM_BLD_push_octet_string(params->param_bld, key_ptr, RSTRING_PTR(value), RSTRING_LENINT(value))) {
+                    ossl_raise(ePKeyError, "OSSL_PARAM_BLD_push_octet_string");
+                }
+                break;
+            case OSSL_PARAM_UTF8_PTR:
+            case OSSL_PARAM_OCTET_PTR:
+                ossl_raise(ePKeyError, "Unsupported parameter \"%s\", handling of OSSL_PARAM_UTF8_PTR and OSSL_PARAM_OCTET_PTR not implemented", key_ptr);
+                break;
+            }
+
+            return ST_CONTINUE;
+        }
+    }
+
+    VALUE supported_parameters = rb_ary_new();
+
+    for (const OSSL_PARAM *settable_params = params->settable_params; settable_params->key != NULL; settable_params++) {
+        rb_ary_push(supported_parameters, rb_str_new_cstr(settable_params->key));
+    }
+
+    for(int i = 0; strlen(params->aliases[i].alias) > 0; i++) {
+        rb_ary_push(supported_parameters, rb_str_new_cstr(params->aliases[i].alias));
+    }
+
+    ossl_raise(ePKeyError, "Invalid parameter \"%s\". Supported parameters: %"PRIsVALUE, key_ptr, rb_ary_join(supported_parameters, rb_str_new2(", ")));
+}
+
+static VALUE
+iterate_from_data_options_cb(VALUE value)
+{
+    struct pkey_from_data_arg *args = (void *)value;
+
+    rb_hash_foreach(args->options, &add_data_to_builder, (VALUE) args);
+
+    return Qnil;
+}
+
+static VALUE
+pkey_from_data(int argc, VALUE *argv, VALUE self)
+{
+    VALUE alg, options;
+    rb_scan_args(argc, argv, "11", &alg, &options);
+
+    const char* algorithm = StringValueCStr(alg);
+
+    EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name(NULL, algorithm, NULL);
+
+    if (ctx == NULL)
+        ossl_raise(ePKeyError, "EVP_PKEY_CTX_new_from_name");
+
+    struct pkey_from_data_arg from_data_args = { 0 };
+
+    from_data_args.param_bld = OSSL_PARAM_BLD_new();
+    from_data_args.options = options;
+
+    if (from_data_args.param_bld == NULL) {
+        EVP_PKEY_CTX_free(ctx);
+        ossl_raise(ePKeyError, "OSSL_PARAM_BLD_new");
+    }
+
+    from_data_args.settable_params = EVP_PKEY_fromdata_settable(ctx, EVP_PKEY_KEYPAIR);
+
+    if (from_data_args.settable_params == NULL) {
+        EVP_PKEY_CTX_free(ctx);
+        OSSL_PARAM_BLD_free(from_data_args.param_bld);
+        ossl_raise(ePKeyError, "EVP_PKEY_fromdata_settable");
+    }
+
+    if (strcmp("RSA", algorithm) == 0)
+        from_data_args.aliases = rsa_aliases;
+    else
+        from_data_args.aliases = fcc_aliases;
+
+    int state;
+    rb_protect(iterate_from_data_options_cb, (VALUE) &from_data_args, &state);
+
+    if(state) {
+        EVP_PKEY_CTX_free(ctx);
+        OSSL_PARAM_BLD_free(from_data_args.param_bld);
+        rb_jump_tag(state);
+    }
+
+    OSSL_PARAM *params = OSSL_PARAM_BLD_to_param(from_data_args.param_bld);
+    OSSL_PARAM_BLD_free(from_data_args.param_bld);
+
+    if (params == NULL) {
+        EVP_PKEY_CTX_free(ctx);
+        ossl_raise(ePKeyError, "OSSL_PARAM_BLD_to_param");
+    }
+
+    EVP_PKEY *pkey = NULL;
+
+    if (EVP_PKEY_fromdata_init(ctx) <= 0) {
+        EVP_PKEY_CTX_free(ctx);
+        ossl_raise(ePKeyError, "EVP_PKEY_fromdata_init");
+    }
+
+    if (EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_KEYPAIR, params) <= 0) {
+        EVP_PKEY_CTX_free(ctx);
+        EVP_PKEY_free(pkey);
+        ossl_raise(ePKeyError, "EVP_PKEY_fromdata");
+    }
+
+    EVP_PKEY_CTX_free(ctx);
+
+    return ossl_pkey_new(pkey);
+}
+
+#endif
+
 /*
  * call-seq:
  *    OpenSSL::PKey.generate_parameters(algo_name [, options]) -> pkey
@@ -498,6 +675,33 @@ ossl_pkey_s_generate_key(int argc, VALUE *argv, VALUE self)
     return pkey_generate(argc, argv, self, 0);
 }
 
+/*
+ * call-seq:
+ *    OpenSSL::PKey.from_data(algo_name, parameters) -> pkey
+ *
+ * Generates a new key based on given key parameters.
+ * NOTE: Requires OpenSSL 3.0 or later.
+ *
+ * The first parameter is the type of the key to create, given as a String, for example RSA, DSA, EC etc.
+ * Second parameter is the parameters to be used for the key.
+ *
+ * For details algorithms and parameters see https://www.openssl.org/docs/man3.0/man3/EVP_PKEY_fromdata.html
+ *
+ * == Example
+ *   pkey = OpenSSL::PKey.from_data("RSA", n: 3161751493, e: 65537, d: 2064855961)
+ *   pkey.private? #=> true
+ *   pkey.public_key #=> #<OpenSSL::PKey::RSA...
+ */
+static VALUE
+ossl_pkey_s_from_data(int argc, VALUE *argv, VALUE self)
+{
+#if OSSL_OPENSSL_PREREQ(3, 0, 0)
+    return pkey_from_data(argc, argv, self);
+#else
+    rb_raise(ePKeyError, "OpenSSL::PKey.from_data requires OpenSSL 3.0 or later");
+#endif
+}
+
 /*
  * TODO: There is no convenient way to check the presence of public key
  * components on OpenSSL 3.0. But since keys are immutable on 3.0, pkeys without
@@ -1751,6 +1955,8 @@ Init_ossl_pkey(void)
     rb_define_module_function(mPKey, "read", ossl_pkey_new_from_data, -1);
     rb_define_module_function(mPKey, "generate_parameters", ossl_pkey_s_generate_parameters, -1);
     rb_define_module_function(mPKey, "generate_key", ossl_pkey_s_generate_key, -1);
+    rb_define_module_function(mPKey, "from_data", ossl_pkey_s_from_data, -1);
+
 #ifdef HAVE_EVP_PKEY_NEW_RAW_PRIVATE_KEY
     rb_define_module_function(mPKey, "new_raw_private_key", ossl_pkey_new_raw_private_key, 2);
     rb_define_module_function(mPKey, "new_raw_public_key", ossl_pkey_new_raw_public_key, 2);
diff --git a/test/openssl/fixtures/pkey/ec-prime256v1.pem b/test/openssl/fixtures/pkey/ec-prime256v1.pem
new file mode 100644
index 000000000..331204e18
--- /dev/null
+++ b/test/openssl/fixtures/pkey/ec-prime256v1.pem
@@ -0,0 +1,5 @@
+-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEIF82EiQBDw+pE6VW1bVeIj1YhBE2PnQvBwTWbtGVDikZoAoGCCqGSM49
+AwEHoUQDQgAE4A8HPRtiQXvN6O2vRqd11flrUppec9Q16vXkHZeW/otceOW8KsvL
+D4Lf77woXqaPBrgLg98MfnG/fkWApqRttA==
+-----END EC PRIVATE KEY-----
diff --git a/test/openssl/fixtures/pkey/ec-secp384r1.pem b/test/openssl/fixtures/pkey/ec-secp384r1.pem
new file mode 100644
index 000000000..9ce8f12de
--- /dev/null
+++ b/test/openssl/fixtures/pkey/ec-secp384r1.pem
@@ -0,0 +1,6 @@
+-----BEGIN EC PRIVATE KEY-----
+MIGkAgEBBDBpl+EijPemKAT8htq593YwZkdWQZXQHsZO1sGPtdqxdAg4zuExTw0V
+4CpES/2PlkmgBwYFK4EEACKhZANiAAQaHsTYFFUAoM3aDfdgGKu6joaYfopoiMBF
+vfreMsl8rQYRx8D6ZR9039W/OyTwa6kgFSUGdJQgrDArJWk+mc3f9AK03L1aeRVm
+PqI4vldF5qeFmxRkMbvsjOw7Ww7ZYS8=
+-----END EC PRIVATE KEY-----
diff --git a/test/openssl/fixtures/pkey/ec-secp521r1.pem b/test/openssl/fixtures/pkey/ec-secp521r1.pem
new file mode 100644
index 000000000..5902f5aed
--- /dev/null
+++ b/test/openssl/fixtures/pkey/ec-secp521r1.pem
@@ -0,0 +1,7 @@
+-----BEGIN EC PRIVATE KEY-----
+MIHcAgEBBEIBaJnA94w7oe13yZbz3AAklIooPMz30CowSYWiiN8MvSLda+WB+qNk
+yjDTELtPSf/DpE3sVpvRKpOpwP6gc05G1DGgBwYFK4EEACOhgYkDgYYABAGGDZbR
+j3ewwvEEL7tXFuckJ5ScFa666sPwfthAJmIWZimK/uYX+d/BfAio6/2FxhlvNO7N
+R0YW0YogrxhyYDaQXQCMHTa2OLlQ3+FUY7a/zV0+RnK57DvC5hyTElQnN7doA0zW
+v8YJM3Ph5fzq4RQ9OiNEVRarcz4CKtPE/4P8qFbCiw==
+-----END EC PRIVATE KEY-----
diff --git a/test/openssl/test_pkey.rb b/test/openssl/test_pkey.rb
index aee0546f6..18b54e09f 100644
--- a/test/openssl/test_pkey.rb
+++ b/test/openssl/test_pkey.rb
@@ -244,4 +244,213 @@ def test_to_text
     rsa = Fixtures.pkey("rsa1024")
     assert_include rsa.to_text, "publicExponent"
   end
+
+  if openssl?(3, 0, 0)
+    def test_from_data_with_invalid_alg
+      assert_raise_with_message(OpenSSL::PKey::PKeyError, /^EVP_PKEY_CTX_new_from_name: unsupported/) do
+        OpenSSL::PKey.from_data("ASR", {})
+      end
+    end
+
+    def test_s_from_data_rsa_with_n_e_and_d_given_as_integers
+      new_key = OpenSSL::PKey.from_data("RSA", "n" => 3161751493,
+                                               "e" => 65537,
+                                               "d" => 2064855961)
+
+      assert_instance_of OpenSSL::PKey::RSA, new_key
+      assert_equal true, new_key.private?
+      assert_equal OpenSSL::BN.new(3161751493), new_key.n
+      assert_equal OpenSSL::BN.new(65537), new_key.e
+      assert_equal OpenSSL::BN.new(2064855961), new_key.d
+    end
+
+    def test_s_from_data_rsa_with_n_e_and_d_given_as_symbols
+      new_key = OpenSSL::PKey.from_data("RSA", n: OpenSSL::BN.new(3161751493),
+                                               e: OpenSSL::BN.new(65537),
+                                               d: OpenSSL::BN.new(2064855961))
+
+      assert_instance_of OpenSSL::PKey::RSA, new_key
+      assert_equal true, new_key.private?
+      assert_equal OpenSSL::BN.new(3161751493), new_key.n
+      assert_equal OpenSSL::BN.new(65537), new_key.e
+      assert_equal OpenSSL::BN.new(2064855961), new_key.d
+    end
+
+    def test_s_from_data_rsa_with_n_and_e_given
+      new_key = OpenSSL::PKey.from_data("RSA", "n" => OpenSSL::BN.new(3161751493),
+                                               "e" => OpenSSL::BN.new(65537))
+
+      assert_instance_of OpenSSL::PKey::RSA, new_key
+      assert_equal false, new_key.private?
+      assert_equal OpenSSL::BN.new(3161751493), new_key.n
+      assert_equal OpenSSL::BN.new(65537), new_key.e
+      assert_equal nil, new_key.d
+    end
+
+    def test_s_from_data_rsa_with_openssl_internal_names
+      source  = Fixtures.pkey("rsa2048")
+      new_key = OpenSSL::PKey.from_data("RSA", "n" => source.n,
+                                               "e" => source.e,
+                                               "d" => source.d,
+                                               "rsa-factor1" => source.p,
+                                               "rsa-factor2" => source.q,
+                                               "rsa-exponent1" => source.dmp1,
+                                               "rsa-exponent2" => source.dmq1,
+                                               "rsa-coefficient1" => source.iqmp)
+
+      assert_equal source.n, new_key.n
+      assert_equal source.e, new_key.e
+      assert_equal source.d, new_key.d
+      assert_equal source.p, new_key.p
+      assert_equal source.q, new_key.q
+      assert_equal source.dmp1, new_key.dmp1
+      assert_equal source.dmq1, new_key.dmq1
+      assert_equal source.iqmp, new_key.iqmp
+      assert_equal source.to_pem, new_key.to_pem
+    end
+
+    def test_s_from_data_rsa_with_simple_names
+      source  = Fixtures.pkey("rsa2048")
+      new_key = OpenSSL::PKey.from_data("RSA", "n" => source.n,
+                                               "e" => source.e,
+                                               "d" => source.d,
+                                               "p" => source.p,
+                                               "q" => source.q,
+                                               "dmp1" => source.dmp1,
+                                               "dmq1" => source.dmq1,
+                                               "iqmp" => source.iqmp)
+
+      assert_equal source.n, new_key.n
+      assert_equal source.e, new_key.e
+      assert_equal source.d, new_key.d
+      assert_equal source.p, new_key.p
+      assert_equal source.q, new_key.q
+      assert_equal source.dmp1, new_key.dmp1
+      assert_equal source.dmq1, new_key.dmq1
+      assert_equal source.iqmp, new_key.iqmp
+      assert_equal source.to_pem, new_key.to_pem
+    end
+
+    def test_s_from_data_rsa_with_invalid_parameter
+      assert_raise_with_message(OpenSSL::PKey::PKeyError, /Invalid parameter "invalid"/) do
+        OpenSSL::PKey.from_data("RSA", "invalid" => 1234)
+      end
+    end
+
+    def test_s_from_data_ec_pub_given_as_string
+      source  = Fixtures.pkey("ec-prime256v1")
+      new_key = OpenSSL::PKey.from_data("EC", "group" => source.group.curve_name,
+                                              "pub" => source.public_key.to_bn.to_s(2))
+      assert_instance_of OpenSSL::PKey::EC, new_key
+      assert_equal source.group.curve_name, new_key.group.curve_name
+      assert_equal source.public_key, new_key.public_key
+      assert_equal nil, new_key.private_key
+    end
+
+    def test_s_from_data_ec_priv_given_as_bn
+      source  = Fixtures.pkey("ec-prime256v1")
+      new_key = OpenSSL::PKey.from_data("EC", "group" => source.group.curve_name,
+                                              "priv" => source.private_key.to_bn)
+      assert_instance_of OpenSSL::PKey::EC, new_key
+      assert_equal source.group.curve_name, new_key.group.curve_name
+      assert_equal source.private_key, new_key.private_key
+      assert_equal nil, new_key.public_key
+    end
+
+    def test_s_from_data_ec_priv_given_as_integer
+      source  = Fixtures.pkey("ec-prime256v1")
+      new_key = OpenSSL::PKey.from_data("EC", "group" => source.group.curve_name,
+                                              "priv" => source.private_key.to_i)
+      assert_instance_of OpenSSL::PKey::EC, new_key
+      assert_equal source.group.curve_name, new_key.group.curve_name
+      assert_equal source.private_key, new_key.private_key
+      assert_equal nil, new_key.public_key
+    end
+
+    def test_s_from_data_ec_priv_and_pub_given_for_different_curves
+      [Fixtures.pkey("ec-prime256v1"),
+       Fixtures.pkey("ec-secp384r1"),
+       Fixtures.pkey("ec-secp521r1")].each do |source|
+        new_key = OpenSSL::PKey.from_data("EC", "group" => source.group.curve_name,
+                                                "pub" => source.public_key.to_bn.to_s(2),
+                                                "priv" => source.private_key.to_i)
+        assert_instance_of OpenSSL::PKey::EC, new_key
+        assert_equal source.group.curve_name, new_key.group.curve_name
+        assert_equal source.private_key, new_key.private_key
+        assert_equal source.public_key, new_key.public_key
+      end
+    end
+
+    def test_s_from_data_ec_pub_given_as_integer
+      assert_raise_with_message(TypeError, "no implicit conversion of Integer into String") do
+        OpenSSL::PKey.from_data("EC", { "group" => "prime256v1", "pub" => 12345 })
+      end
+    end
+
+    def test_s_from_data_ec_with_invalid_parameter
+      assert_raise_with_message(OpenSSL::PKey::PKeyError, /Invalid parameter "invalid"/) do
+        OpenSSL::PKey.from_data("EC", "invalid" => 1234)
+      end
+    end
+
+    def test_s_from_data_dsa_with_all_supported_parameters
+      source  = Fixtures.pkey("dsa1024")
+      new_key = OpenSSL::PKey.from_data("DSA", "pub" => source.params["pub_key"],
+                                               "priv" => source.params["priv_key"],
+                                               "p" => source.params["p"],
+                                               "q" => source.params["q"],
+                                               "g" => source.params["g"])
+
+      assert_instance_of OpenSSL::PKey::DSA, new_key
+      assert_equal source.params, new_key.params
+    end
+
+    def test_s_from_data_dsa_with_gem_specific_keys
+      source  = Fixtures.pkey("dsa2048")
+      new_key = OpenSSL::PKey.from_data("DSA", source.params)
+
+      assert_equal source.params, new_key.params
+    end
+
+    def test_s_from_data_dsa_with_invalid_parameter
+      assert_raise_with_message(OpenSSL::PKey::PKeyError, /Invalid parameter "invalid". Supported parameters: p, q, g, j/) do
+        OpenSSL::PKey.from_data("DSA", "invalid" => 1234)
+      end
+    end
+
+    def test_s_from_data_dh_with_all_supported_parameters
+      source  = Fixtures.pkey("dh2048_ffdhe2048")
+      new_key = OpenSSL::PKey.from_data("DH", source.params)
+
+      assert_instance_of OpenSSL::PKey::DH, new_key
+      assert_equal source.params, new_key.params
+    end
+
+    def test_s_from_data_dh_with_invalid_parameter
+      assert_raise_with_message(OpenSSL::PKey::PKeyError, /Invalid parameter "invalid"/) do
+        OpenSSL::PKey.from_data("DH", "invalid" => 1234)
+      end
+    end
+
+    def test_s_from_data_ed25519
+      # Ed25519 is not FIPS-approved.
+      omit_on_fips
+
+      pub_pem = <<~EOF
+    -----BEGIN PUBLIC KEY-----
+    MCowBQYDK2VwAyEA0I6olrZGYml7JGusuKJW9G7D0DZ9UormSady9kR7V4Q=
+    -----END PUBLIC KEY-----
+    EOF
+
+      key = OpenSSL::PKey.from_data("ED25519", "pub" => "\xD0\x8E\xA8\x96\xB6Fbi{$k\xAC\xB8\xA2V\xF4n\xC3\xD06}R\x8A\xE6I\xA7r\xF6D{W\x84")
+      assert_instance_of OpenSSL::PKey::PKey, key
+      assert_equal pub_pem, key.public_to_pem
+    end
+  else
+    def test_from_parameter_raises_on_pre_3_openssl
+      assert_raise_with_message(OpenSSL::PKey::PKeyError, "OpenSSL::PKey.from_data requires OpenSSL 3.0 or later") do
+        OpenSSL::PKey.from_data("RSA", {})
+      end
+    end
+  end
 end