1
0
mirror of https://github.com/php/php-src.git synced 2026-03-24 00:02:20 +01:00

Add digest algo param to public encrypt and private decrypt

Specifically, it is added to openssl_public_encrypt() and
openssl_private_decrypt() functions. The purpose is to specify digest
algorithm for OEAP padding. It currently defaults to SHA1 for some
OpenSSL versions which is not preferred for modern setup and causes
problems in compatibility with web crypto.

Closes GH-19223
This commit is contained in:
Jakub Zelenka
2025-07-23 16:56:37 +02:00
parent 93b9808004
commit b1fce8a98c
6 changed files with 107 additions and 11 deletions

4
NEWS
View File

@@ -11,6 +11,10 @@ PHP NEWS
. Add support for CURLINFO_CONN_ID in curl_getinfo() (thecaliskan)
. Add support for CURLINFO_QUEUE_TIME_T in curl_getinfo() (thecaliskan)
- OpenSSL:
. Add $digest_algo parameter to openssl_public_encrypt() and
openssl_private_decrypt() functions. (Jakub Zelenka)
- Reflection:
. Fixed bug GH-19187 (ReflectionNamedType::getName() prints nullable type when
retrieved from ReflectionProperty::getSettableType()). (ilutov)

View File

@@ -327,6 +327,10 @@ PHP 8.5 UPGRADE NOTES
- libxml:
. libxml_set_external_entity_loader() now has a formal return type of true.
- OpenSSL:
. openssl_public_encrypt() and openssl_private_decrypt() have new parameter
$digest_algo that allows specifying hash digest algorith for OEAP padding.
- PCNTL:
. pcntl_exec() now has a formal return type of false.
. pcntl_waitid() takes an additional resource_usage argument to

View File

@@ -3725,6 +3725,29 @@ clean_exit:
/* }}} */
/* Helper to set RSA padding and digest for OAEP */
static int php_openssl_set_rsa_padding_and_digest(EVP_PKEY_CTX *ctx, zend_long padding, const char *digest_algo, const EVP_MD **pmd)
{
if (EVP_PKEY_CTX_set_rsa_padding(ctx, padding) <= 0) {
return 0;
}
if (digest_algo != NULL) {
const EVP_MD *md = php_openssl_get_evp_md_by_name(digest_algo);
if (md == NULL) {
php_error_docref(NULL, E_WARNING, "Unknown digest algorithm: %s", digest_algo);
return 0;
}
*pmd = md;
if (padding == RSA_PKCS1_OAEP_PADDING) {
if (EVP_PKEY_CTX_set_rsa_oaep_md(ctx, md) <= 0) {
return 0;
}
}
}
return 1;
}
/* {{{ Encrypts data with private key */
PHP_FUNCTION(openssl_private_encrypt)
@@ -3780,10 +3803,12 @@ PHP_FUNCTION(openssl_private_decrypt)
{
zval *key, *crypted;
zend_long padding = RSA_PKCS1_PADDING;
char * data;
size_t data_len;
char *data;
char *digest_algo = NULL;
size_t data_len, digest_algo_len = 0;
const EVP_MD *md = NULL;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "szz|l", &data, &data_len, &crypted, &key, &padding) == FAILURE) {
if (zend_parse_parameters(ZEND_NUM_ARGS(), "szz|lp!", &data, &data_len, &crypted, &key, &padding, &digest_algo, &digest_algo_len) == FAILURE) {
RETURN_THROWS();
}
@@ -3798,7 +3823,7 @@ PHP_FUNCTION(openssl_private_decrypt)
size_t out_len = 0;
EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(pkey, NULL);
if (!ctx || EVP_PKEY_decrypt_init(ctx) <= 0 ||
EVP_PKEY_CTX_set_rsa_padding(ctx, padding) <= 0 ||
!php_openssl_set_rsa_padding_and_digest(ctx, padding, digest_algo, &md) ||
EVP_PKEY_decrypt(ctx, NULL, &out_len, (unsigned char *) data, data_len) <= 0) {
php_openssl_store_errors();
RETVAL_FALSE;
@@ -3820,6 +3845,7 @@ PHP_FUNCTION(openssl_private_decrypt)
RETVAL_TRUE;
cleanup:
php_openssl_release_evp_md(md);
EVP_PKEY_CTX_free(ctx);
EVP_PKEY_free(pkey);
}
@@ -3831,9 +3857,11 @@ PHP_FUNCTION(openssl_public_encrypt)
zval *key, *crypted;
zend_long padding = RSA_PKCS1_PADDING;
char * data;
size_t data_len;
char *digest_algo = NULL;
size_t data_len, digest_algo_len = 0;
const EVP_MD *md = NULL;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "szz|l", &data, &data_len, &crypted, &key, &padding) == FAILURE) {
if (zend_parse_parameters(ZEND_NUM_ARGS(), "szz|lp!", &data, &data_len, &crypted, &key, &padding, &digest_algo, &digest_algo_len) == FAILURE) {
RETURN_THROWS();
}
@@ -3848,7 +3876,7 @@ PHP_FUNCTION(openssl_public_encrypt)
size_t out_len = 0;
EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(pkey, NULL);
if (!ctx || EVP_PKEY_encrypt_init(ctx) <= 0 ||
EVP_PKEY_CTX_set_rsa_padding(ctx, padding) <= 0 ||
!php_openssl_set_rsa_padding_and_digest(ctx, padding, digest_algo, &md) ||
EVP_PKEY_encrypt(ctx, NULL, &out_len, (unsigned char *) data, data_len) <= 0) {
php_openssl_store_errors();
RETVAL_FALSE;
@@ -3869,6 +3897,7 @@ PHP_FUNCTION(openssl_public_encrypt)
RETVAL_TRUE;
cleanup:
php_openssl_release_evp_md(md);
EVP_PKEY_CTX_free(ctx);
EVP_PKEY_free(pkey);
}
@@ -3879,7 +3908,7 @@ PHP_FUNCTION(openssl_public_decrypt)
{
zval *key, *crypted;
zend_long padding = RSA_PKCS1_PADDING;
char * data;
char *data;
size_t data_len;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "szz|l", &data, &data_len, &crypted, &key, &padding) == FAILURE) {

View File

@@ -575,13 +575,13 @@ function openssl_private_encrypt(#[\SensitiveParameter] string $data, &$encrypte
* @param string $decrypted_data
* @param OpenSSLAsymmetricKey|OpenSSLCertificate|array|string $private_key
*/
function openssl_private_decrypt(string $data, #[\SensitiveParameter] &$decrypted_data, #[\SensitiveParameter] $private_key, int $padding = OPENSSL_PKCS1_PADDING): bool {}
function openssl_private_decrypt(string $data, #[\SensitiveParameter] &$decrypted_data, #[\SensitiveParameter] $private_key, int $padding = OPENSSL_PKCS1_PADDING, ?string $digest_algo = null): bool {}
/**
* @param string $encrypted_data
* @param OpenSSLAsymmetricKey|OpenSSLCertificate|array|string $public_key
*/
function openssl_public_encrypt(#[\SensitiveParameter] string $data, &$encrypted_data, $public_key, int $padding = OPENSSL_PKCS1_PADDING): bool {}
function openssl_public_encrypt(#[\SensitiveParameter] string $data, &$encrypted_data, $public_key, int $padding = OPENSSL_PKCS1_PADDING, ?string $digest_algo = null): bool {}
/**
* @param string $decrypted_data

View File

@@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead.
* Stub hash: a42bd7dec0a5e011983ce08b5e31cd8718247501 */
* Stub hash: 4186e01a05ec179f1f2196a2afd1860671f793d5 */
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_openssl_x509_export_to_file, 0, 2, _IS_BOOL, 0)
ZEND_ARG_OBJ_TYPE_MASK(0, certificate, OpenSSLCertificate, MAY_BE_STRING, NULL)
@@ -258,6 +258,7 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_openssl_private_decrypt, 0, 3, _
ZEND_ARG_INFO(1, decrypted_data)
ZEND_ARG_INFO(0, private_key)
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, padding, IS_LONG, 0, "OPENSSL_PKCS1_PADDING")
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, digest_algo, IS_STRING, 1, "null")
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_openssl_public_encrypt, 0, 3, _IS_BOOL, 0)
@@ -265,6 +266,7 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_openssl_public_encrypt, 0, 3, _I
ZEND_ARG_INFO(1, encrypted_data)
ZEND_ARG_INFO(0, public_key)
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, padding, IS_LONG, 0, "OPENSSL_PKCS1_PADDING")
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, digest_algo, IS_STRING, 1, "null")
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_openssl_public_decrypt, 0, 3, _IS_BOOL, 0)

View File

@@ -0,0 +1,57 @@
--TEST--
openssl_private_decrypt() with digest algorithm tests
--EXTENSIONS--
openssl
--FILE--
<?php
$data = "Testing openssl_private_decrypt() with digest algorithms";
$privkey = "file://" . __DIR__ . "/private_rsa_1024.key";
$pubkey = "file://" . __DIR__ . "/public.key";
openssl_public_encrypt($data, $encrypted_sha256, $pubkey, OPENSSL_PKCS1_OAEP_PADDING, "sha256");
var_dump(openssl_private_decrypt($encrypted_sha256, $output_sha256, $privkey, OPENSSL_PKCS1_OAEP_PADDING, "sha256"));
var_dump($output_sha256);
openssl_public_encrypt($data, $encrypted_default, $pubkey, OPENSSL_PKCS1_OAEP_PADDING);
var_dump(openssl_private_decrypt($encrypted_default, $output_default, $privkey, OPENSSL_PKCS1_OAEP_PADDING));
var_dump($output_default);
// Some distros (RHEL) explicitly disable SHA1 so this runs only if available
if (in_array('sha1', openssl_get_md_methods())) {
openssl_public_encrypt($data, $encrypted_sha256, $pubkey, OPENSSL_PKCS1_OAEP_PADDING, "sha256");
var_dump(openssl_private_decrypt($encrypted_sha256, $output_mismatch, $privkey, OPENSSL_PKCS1_OAEP_PADDING, "sha1"));
var_dump($output_mismatch);
} else {
var_dump(false);
var_dump(NULL);
}
var_dump(openssl_private_decrypt($encrypted_sha256, $output_invalid, $privkey, OPENSSL_PKCS1_OAEP_PADDING, "invalid_hash"));
var_dump($output_invalid);
// Test that "p" is used instead of "s" (it is not a ZPP test but flag selection test so do not remove)
try {
var_dump(openssl_private_decrypt($encrypted_sha256, $output_invalid, $privkey, OPENSSL_PKCS1_OAEP_PADDING, "sha256\0extra"));
var_dump($output_invalid);
} catch (\ValueError $e) {
var_dump($e->getMessage());
}
openssl_public_encrypt($data, $encrypted_pkcs1, $pubkey, OPENSSL_PKCS1_PADDING);
var_dump(openssl_private_decrypt($encrypted_pkcs1, $output_pkcs1, $privkey, OPENSSL_PKCS1_PADDING, "sha256"));
var_dump($output_pkcs1);
?>
--EXPECTF--
bool(true)
string(56) "Testing openssl_private_decrypt() with digest algorithms"
bool(true)
string(56) "Testing openssl_private_decrypt() with digest algorithms"
bool(false)
NULL
Warning: openssl_private_decrypt(): Unknown digest algorithm: invalid_hash in %s on line %d
bool(false)
NULL
string(85) "openssl_private_decrypt(): Argument #5 ($digest_algo) must not contain any null bytes"
bool(true)
string(56) "Testing openssl_private_decrypt() with digest algorithms"