From e4297493a6c351926fa4b0c5ccd8eabbbccbf674 Mon Sep 17 00:00:00 2001 From: James ZHNAG Date: Sat, 4 Sep 2021 10:32:48 +0800 Subject: [PATCH 1/8] optim comments/readme/testcases - simple case on `PemUtil::parseCertificateSerialNo` which is from the `file://` protocol certificate --- README.md | 2 +- src/Crypto/Rsa.php | 7 +++---- tests/README.md | 6 +++++- tests/Util/PemUtilTest.php | 6 ++++-- 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index c6be803..d153691 100644 --- a/README.md +++ b/README.md @@ -139,7 +139,7 @@ $instance = Builder::factory([ - `mchid` 为你的`商户号`,一般是10字节纯数字 - `serial` 为你的`商户证书序列号`,一般是40字节字符串 - `privateKey` 为你的`商户API私钥`,一般是通过官方证书生成工具生成的文件名是`apiclient_key.pem`文件,支持纯字符串或者文件`resource`格式 -- `certs[$serial_number => #resource]` 为通过下载工具下载的平台证书`key/value`键值对,键为`平台证书序列号`,值为`平台证书`pem格式的纯字符串或者文件`resource`格式 +- `certs[$serial_number => #resource]` 为通过下载工具下载的`平台证书序列号`及`平台公钥`键值对,键为`平台证书序列号`,值为`平台证书`内置的`平台公钥`,推荐由`Rsa::from`函数加载后的`对象`或`资源`对象 - `secret` 为APIv2版的`密钥`,商户平台上设置的32字节字符串 - `merchant[cert => $path]` 为你的`商户证书`,一般是文件名为`apiclient_cert.pem`文件路径,接受`[$path, $passphrase]` 格式,其中`$passphrase`为证书密码 - `merchant[key => $path]` 为你的`商户API私钥`,一般是通过官方证书生成工具生成的文件名是`apiclient_key.pem`文件路径,接受`[$path, $passphrase]` 格式,其中`$passphrase`为私钥密码 diff --git a/src/Crypto/Rsa.php b/src/Crypto/Rsa.php index 330190a..4ff9270 100644 --- a/src/Crypto/Rsa.php +++ b/src/Crypto/Rsa.php @@ -164,9 +164,9 @@ public static function fromSpki(string $thing) * Loading the privateKey/publicKey. * * The `\$thing` can be one of the following: - * - `file://` protocol privateKey/publicKey(x509 certificate) string. + * - `file://` protocol `PKCS#1/PKCS#8 privateKey`/`SPKI publicKey`/`x509 certificate(for publicKey)` string. * - `public.spki://`, `public.pkcs1://`, `private.pkcs1://`, `private.pkcs8://` protocols string. - * - full `PEM` format privateKey/publicKey(x509 certificate) string. + * - full `PEM` in `PKCS#1/PKCS#8` format `privateKey`/`publicKey`/`x509 certificate(for publicKey)` string. * - `\OpenSSLAsymmetricKey` (PHP8) or `resource#pkey` (PHP7). * - `\OpenSSLCertificate` (PHP8) or `resource#X509` (PHP7) for publicKey. * - `Array` of `[privateKeyString,passphrase]` for encrypted privateKey. @@ -201,7 +201,6 @@ public static function from($thing, string $type = self::KEY_TYPE_PRIVATE) * - `file:///my/path/to/private.pkcs1.key` * - `file:///my/path/to/private.pkcs8.key` * - `file:///my/path/to/public.spki.pem` - * - `file:///my/path/to/public.pkcs1.pem` * - `file:///my/path/to/x509.crt` (for publicKey) * * The `\$thing` can be the `public.spki://`, `public.pkcs1://`, `private.pkcs1://`, `private.pkcs8://` protocols string, eg: @@ -222,7 +221,7 @@ public static function from($thing, string $type = self::KEY_TYPE_PRIVATE) * - `\OpenSSLCertificate` (PHP8) or `resource#X509` (PHP7) for publicKey. * * The `\$thing` can be the Array{$privateKey,$passphrase} style for loading privateKey, eg: - * - [`file:///my/path/to/private.pkcs8.key`, 'your_pass_phrase'] + * - [`file:///my/path/to/encrypted.private.pkcs8.key`, 'your_pass_phrase'] * - [`-----BEGIN ENCRYPTED PRIVATE KEY-----...-----END ENCRYPTED PRIVATE KEY-----`, 'your_pass_phrase'] * * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|resource|array{string,string}|string|mixed $thing - The thing. diff --git a/tests/README.md b/tests/README.md index 4082bf8..73f52b6 100644 --- a/tests/README.md +++ b/tests/README.md @@ -106,13 +106,15 @@ OK (490 tests, 2177 assertions) rm -rf ./tests/fixtures/mock.* ``` -如果希望静态测试,或者无`make`环境,希望手动进行测试,则可以提供以下6个文件(文件名需相同),来代替测试用例准备工作。 +如果希望静态测试,或者无`make`环境,希望手动进行测试,则可以提供以下8个文件(文件名需相同),来代替测试用例准备工作。 ``` tests/fixtures +├── mock.encrypted.pkcs8.key ├── mock.pkcs1.key ├── mock.pkcs1.pem ├── mock.pkcs8.key +├── mock.pwd.txt ├── mock.serial.txt ├── mock.sha256.crt ├── mock.spki.pem @@ -122,9 +124,11 @@ tests/fixtures |文件名|含义| |---|---| +|mock.encrypted.pkcs8.key|RSA私钥`PKCS#8`加密格式| |mock.pkcs1.key|RSA私钥`PKCS#1`格式| |mock.pkcs1.pem|RSA公钥`PKCS#1`格式| |mock.pkcs8.key|RSA私钥`PKCS#8`格式| +|mock.pwd.txt|RSA私钥`PKCS#8`加密格式的密码| |mock.serial.txt|X509`证书序列号`,16进制格式| |mock.sha256.crt|`X509证书`,sha256签名格式| |mock.spki.pem|RSA公钥`SPKI`格式| diff --git a/tests/Util/PemUtilTest.php b/tests/Util/PemUtilTest.php index 57219a0..2606a3a 100644 --- a/tests/Util/PemUtilTest.php +++ b/tests/Util/PemUtilTest.php @@ -40,7 +40,7 @@ public static function setUpBeforeClass(): void $certString = (string)file_get_contents($certFile); $privString = (string)file_get_contents($privFile); - self::$environment = [$serial, $certFile, $certString, $privFile, $privString]; + self::$environment = [$serial, $certFile, $certString, $privFile, $privString, 'file://' . $certFile]; } public static function tearDownAfterClass(): void @@ -104,12 +104,14 @@ public function testLoadPrivateKeyFromString(): void public function testParseCertificateSerialNo(): void { - [$serialNo, $certFile, $certString] = self::$environment; + [$serialNo, $certFile, $certString, , , $certFileProtocolString] = self::$environment; $serialNoFromPemUtilFile = PemUtil::parseCertificateSerialNo(PemUtil::loadCertificate($certFile)); $serialNoFromPemUtilString = PemUtil::parseCertificateSerialNo(PemUtil::loadCertificateFromString($certString)); $serialNoFromCertString = PemUtil::parseCertificateSerialNo($certString); + $serialNoFromCertFileProtocolString = PemUtil::parseCertificateSerialNo($certFileProtocolString); self::assertEquals($serialNo, $serialNoFromPemUtilFile); self::assertEquals($serialNo, $serialNoFromPemUtilString); self::assertEquals($serialNo, $serialNoFromCertString); + self::assertEquals($serialNo, $serialNoFromCertFileProtocolString); } } From 40ae53b183fea388c0a31550935ee5ab28767c6f Mon Sep 17 00:00:00 2001 From: James ZHNAG Date: Sat, 4 Sep 2021 12:16:36 +0800 Subject: [PATCH 2/8] `facepay` sample --- README.md | 43 +++++++++++++++++-- tests/TransformerTest.php | 6 +++ .../fixtures/getpublickey.response.sample.xml | 15 +++++++ 3 files changed, 60 insertions(+), 4 deletions(-) create mode 100644 tests/fixtures/getpublickey.response.sample.xml diff --git a/README.md b/README.md index d153691..84dc1b9 100644 --- a/README.md +++ b/README.md @@ -462,7 +462,7 @@ $res = $instance 'desc' => '理赔', 'spbill_create_ip' => '192.168.0.1', ], - 'security' => true, + 'security' => true, //请求需要双向证书 'debug' => true //开启调试模式 ]) ->then(static function($response) { @@ -495,6 +495,7 @@ $res = $instance 'mch_id' => '1900000109', 'sign_type' => 'MD5', ], + 'security' => true, //请求需要双向证书 // 特殊接入点,仅对本次请求有效 'base_uri' => 'https://fraud.mch.weixin.qq.com/', ]) @@ -521,7 +522,7 @@ $encryptor = static function(string $msg) use ($rsaPublicKeyInstance): string { return Rsa::encrypt($msg, $rsaPublicKeyInstance); }; $res = $instance -->mmpaysptrans->pay_bank +->v2->mmpaysptrans->pay_bank ->postAsync([ 'xml' => [ 'mch_id' => '1900000109', @@ -532,7 +533,41 @@ $res = $instance 'amount' => '100000', 'desc' => '理财', ], - 'security' => true, + 'security' => true, //请求需要双向证书 +]) +->otherwise(static function($e) { + if ($e instanceof \GuzzleHttp\Promise\RejectionException) { + return Transformer::toArray((string)$e->getReason()->getBody()); + } + return []; +}) +->wait(); +print_r($res); +``` + +### 刷脸支付-人脸识别-获取调用凭证 + +[官方开发文档地址](https://pay.weixin.qq.com/wiki/doc/wxfacepay/develop/android/faceuser.html) + +```php +use WeChatPay\Formatter; + +$res = $instance +->v2->face->get_wxpayface_authinfo +->postAsync([ + 'xml' => [ + 'store_id' => '1234567', + 'store_name' => '云店(广州白云机场店)', + 'device_id' => 'abcdef', + 'rawdata' => '从客户端`getWxpayfaceRawdata`方法取得的数据', + 'appid' => 'wx8888888888888888', + 'mch_id' => '1900000109', + 'now' => (string)Formatter::timestamp(), + 'version' => '1', + 'sign_type' => 'HMAC-SHA256', + ], + // 特殊接入点,仅对本次请求有效 + 'base_uri' => 'https://payapp.weixin.qq.com/', ]) ->otherwise(static function($e) { if ($e instanceof \GuzzleHttp\Promise\RejectionException) { @@ -860,7 +895,7 @@ $stack->before('http_errors', static function (callable $handler) use ($remoteVe }, 'verifier'); // 链式/同步/异步请求APIv3即可,例如: -$instance->V3->Certificates->getAsync()->then(static function($res) { return $res->getBody(); })->wait(); +$instance->v3->certificates->getAsync()->then(static function($res) { return $res->getBody(); })->wait(); ``` ## 常见问题 diff --git a/tests/TransformerTest.php b/tests/TransformerTest.php index abac25c..1dafcec 100644 --- a/tests/TransformerTest.php +++ b/tests/TransformerTest.php @@ -53,6 +53,12 @@ public function xmlToArrayDataProvider(): array 'settlement_refund_fee','settlement_total_fee', 'success_time', 'total_fee', 'transaction_id', ], ], + 'getpublickey.response.sample.xml' => [ + file_get_contents($baseDir . 'getpublickey.response.sample.xml') ?: '', + [ + 'return_code', 'return_msg', 'result_code', 'mch_id', 'pub_key', + ], + ], ]; } diff --git a/tests/fixtures/getpublickey.response.sample.xml b/tests/fixtures/getpublickey.response.sample.xml new file mode 100644 index 0000000..9d9a208 --- /dev/null +++ b/tests/fixtures/getpublickey.response.sample.xml @@ -0,0 +1,15 @@ + + + + + + + From d65adad1a6aa2b0302059d43d4fa7bec837b2d0e Mon Sep 17 00:00:00 2001 From: James ZHNAG Date: Sat, 4 Sep 2021 13:29:14 +0800 Subject: [PATCH 3/8] refined samples --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 84dc1b9..baf58a6 100644 --- a/README.md +++ b/README.md @@ -488,6 +488,7 @@ print_r($res); [官方开发文档地址](https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay_yhk.php?chapter=24_7&index=4) ```php +use WeChatPay\Transformer; $res = $instance ->v2->risk->getpublickey ->postAsync([ @@ -515,6 +516,7 @@ print_r($res); [官方开发文档地址](https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay_yhk.php?chapter=24_2) ```php +use WeChatPay\Transformer; use WeChatPay\Crypto\Rsa; // 做一个匿名方法,供后续方便使用,$rsaPubKeyString 是`risk/getpublickey` 的返回值'pub_key'字符串 $rsaPublicKeyInstance = Rsa::from($rsaPubKeyString, Rsa::KEY_TYPE_PUBLIC); @@ -535,6 +537,9 @@ $res = $instance ], 'security' => true, //请求需要双向证书 ]) +->then(static function($response) { + return Transformer::toArray((string)$response->getBody()); +}) ->otherwise(static function($e) { if ($e instanceof \GuzzleHttp\Promise\RejectionException) { return Transformer::toArray((string)$e->getReason()->getBody()); @@ -551,6 +556,7 @@ print_r($res); ```php use WeChatPay\Formatter; +use WeChatPay\Transformer; $res = $instance ->v2->face->get_wxpayface_authinfo @@ -569,6 +575,9 @@ $res = $instance // 特殊接入点,仅对本次请求有效 'base_uri' => 'https://payapp.weixin.qq.com/', ]) +->then(static function($response) { + return Transformer::toArray((string)$response->getBody()); +}) ->otherwise(static function($e) { if ($e instanceof \GuzzleHttp\Promise\RejectionException) { return Transformer::toArray((string)$e->getReason()->getBody()); @@ -584,6 +593,7 @@ print_r($res); [官方开发文档地址](https://pay.weixin.qq.com/wiki/doc/api/tools/sp_coupon.php?chapter=23_1&index=2) ```php +use WeChatPay\Transformer; $res = $instance ->v2->sandboxnew->pay->getsignkey ->postAsync([ From 0459d5aa8968ca6251938bd70425d59fd392f7e0 Mon Sep 17 00:00:00 2001 From: James ZHANG Date: Sat, 4 Sep 2021 20:37:31 +0800 Subject: [PATCH 4/8] refactor `withDefaults` asof variable-length arguments style --- src/ClientDecorator.php | 4 ++-- src/ClientJsonTrait.php | 3 ++- src/ClientXmlTrait.php | 6 ++---- tests/ClientDecoratorTest.php | 37 +++++++++++++++++++++++++++++++++++ 4 files changed, 43 insertions(+), 7 deletions(-) diff --git a/src/ClientDecorator.php b/src/ClientDecorator.php index e63e853..12909e7 100644 --- a/src/ClientDecorator.php +++ b/src/ClientDecorator.php @@ -47,9 +47,9 @@ final class ClientDecorator implements ClientDecoratorInterface * * @return array - With the built-in configuration. */ - protected static function withDefaults(array $config = []): array + protected static function withDefaults(array ...$config): array { - return array_replace_recursive(static::$defaults, ['headers' => static::userAgent()], $config); + return array_replace_recursive(static::$defaults, ['headers' => static::userAgent()], ...$config); } /** diff --git a/src/ClientJsonTrait.php b/src/ClientJsonTrait.php index 5c6c0db..ef25a91 100644 --- a/src/ClientJsonTrait.php +++ b/src/ClientJsonTrait.php @@ -9,6 +9,7 @@ use function is_resource; use function is_object; use function is_array; +use function implode; use function count; use function sprintf; use function array_key_exists; @@ -51,7 +52,7 @@ trait ClientJsonTrait abstract protected static function body(MessageInterface $message): string; - abstract protected static function withDefaults(array $config = []): array; + abstract protected static function withDefaults(array ...$config): array; /** * APIv3's signer middleware stack diff --git a/src/ClientXmlTrait.php b/src/ClientXmlTrait.php index 427b048..acf52b3 100644 --- a/src/ClientXmlTrait.php +++ b/src/ClientXmlTrait.php @@ -3,7 +3,6 @@ namespace WeChatPay; use function strlen; -use function array_replace_recursive; use function trigger_error; use function sprintf; @@ -33,7 +32,7 @@ trait ClientXmlTrait abstract protected static function body(MessageInterface $message): string; - abstract protected static function withDefaults(array $config = []): array; + abstract protected static function withDefaults(array ...$config): array; /** * APIv2's transformRequest, did the `datasign` and `array2xml` together @@ -124,10 +123,9 @@ public static function xmlBased(array $config = []): Client $stack->before('prepare_body', static::transformRequest($config['mchid'] ?? null, $config['secret'] ?? '', $config['merchant'] ?? []), 'transform_request'); $stack->before('http_errors', static::transformResponse($config['secret'] ?? ''), 'transform_response'); $config['handler'] = $stack; - $config['headers'] = array_replace_recursive(static::$headers, $config['headers'] ?? []); unset($config['mchid'], $config['serial'], $config['privateKey'], $config['certs'], $config['secret'], $config['merchant']); - return new Client(static::withDefaults($config)); + return new Client(static::withDefaults(['headers' => static::$headers], $config)); } } diff --git a/tests/ClientDecoratorTest.php b/tests/ClientDecoratorTest.php index 5d1a954..0b25dcf 100644 --- a/tests/ClientDecoratorTest.php +++ b/tests/ClientDecoratorTest.php @@ -190,6 +190,25 @@ public function testSelect(array $config): void ['handler' => $stack] = $settings; self::assertInstanceOf(HandlerStack::class, $stack); + self::assertArrayHasKey('base_uri', $settings); + /** @var \GuzzleHttp\Psr7\Uri $baseUri */ + ['base_uri' => $baseUri] = $settings; + self::assertEquals($config['base_uri'] ?? 'https://api.mch.weixin.qq.com/', (string)$baseUri); + + self::assertArrayHasKey('headers', $settings); + /** @var array $headers */ + ['headers' => $headers] = $settings; + self::assertIsArray($headers); + self::assertArrayHasKey('Content-Type', $headers); + self::assertArrayHasKey('Accept', $headers); + /** + * @var string $accept + * @var string $contentType + */ + ['Accept' => $accept, 'Content-Type' => $contentType] = $headers; + self::assertEquals('application/json, text/plain, application/x-gzip', $accept); + self::assertEquals('application/json; charset=utf-8', $contentType); + $stackDebugInfo = strval($stack); self::assertStringContainsString('verifier', $stackDebugInfo); self::assertStringContainsString('signer', $stackDebugInfo); @@ -208,6 +227,24 @@ public function testSelect(array $config): void ['handler' => $stack] = $settings; self::assertInstanceOf(HandlerStack::class, $stack); + /** @var \GuzzleHttp\Psr7\Uri $baseUri */ + ['base_uri' => $baseUri] = $settings; + self::assertEquals($config['base_uri'] ?? 'https://api.mch.weixin.qq.com/', (string)$baseUri); + + self::assertArrayHasKey('headers', $settings); + /** @var array $headers */ + ['headers' => $headers] = $settings; + self::assertIsArray($headers); + self::assertArrayHasKey('Content-Type', $headers); + self::assertArrayHasKey('Accept', $headers); + /** + * @var string $accept + * @var string $contentType + */ + ['Accept' => $accept, 'Content-Type' => $contentType] = $headers; + self::assertEquals('text/xml, text/plain, application/x-gzip', $accept); + self::assertEquals('text/xml; charset=utf-8', $contentType); + $stackDebugInfo = strval($stack); self::assertStringNotContainsString('verifier', $stackDebugInfo); self::assertStringNotContainsString('signer', $stackDebugInfo); From d6bfb86a617b31395c139761ee78b1a4c7cb50c2 Mon Sep 17 00:00:00 2001 From: James ZHNAG Date: Sun, 5 Sep 2021 13:38:27 +0800 Subject: [PATCH 5/8] exposed the `$padding` parameter for the Rsa::encrypt/decrypt functions --- UPGRADING.md | 2 ++ src/Crypto/Rsa.php | 30 ++++++++++++++-------- tests/Crypto/RsaTest.php | 54 ++++++++++++++++++++++++++++++++++++++++ tests/README.md | 24 +++++++++--------- 4 files changed, 88 insertions(+), 22 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index 79950f5..09e35f7 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -52,6 +52,7 @@ v1.2 对 `RSA公/私钥`加载做了加强,释放出 `Rsa::from` 统一加载 APIv3相关「RSA数据签名」,变化如下: ```diff +-use WeChatPay\Util\PemUtil; -$merchantPrivateKeyFilePath = '/path/to/merchant/apiclient_key.pem'; -$merchantPrivateKeyInstance = PemUtil::loadPrivateKey($merchantPrivateKeyFilePath); +$merchantPrivateKeyFilePath = 'file:///path/to/merchant/apiclient_key.pem'; @@ -61,6 +62,7 @@ APIv3相关「RSA数据签名」,变化如下: APIv3回调通知「验签」,变化如下: ```diff +-use WeChatPay\Util\PemUtil; // 根据通知的平台证书序列号,查询本地平台证书文件, // 假定为 `/path/to/wechatpay/inWechatpaySerial.pem` -$certInstance = PemUtil::loadCertificate('/path/to/wechatpay/inWechatpaySerial.pem'); diff --git a/src/Crypto/Rsa.php b/src/Crypto/Rsa.php index 4ff9270..ca50a42 100644 --- a/src/Crypto/Rsa.php +++ b/src/Crypto/Rsa.php @@ -265,17 +265,22 @@ private static function parse($thing, string $type = self::KEY_TYPE_PRIVATE) } /** - * Encrypts text with `OPENSSL_PKCS1_OAEP_PADDING`. + * Encrypts text by the given `$publicKey` in the `$padding`(default is `OPENSSL_PKCS1_OAEP_PADDING`) mode. + * + * Some of APIv2 were required the `$padding` mode as of `RSAES-PKCS1-v1_5` which is equal to the `OPENSSL_PKCS1_PADDING` constant, exposed it for this case. + * + * **Warning:** While the `$padding` is `OPENSSL_NO_PADDING` value, the `$plaintext` must be padded by `caller-self`, be careful about this. * * @param string $plaintext - Cleartext to encode. - * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|object|resource|string|mixed $publicKey - A PEM encoded public key. + * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|object|resource|string|mixed $publicKey - The public key. + * @param int $padding - One of OPENSSL_PKCS1_PADDING, OPENSSL_SSLV23_PADDING, OPENSSL_PKCS1_OAEP_PADDING, OPENSSL_NO_PADDING, default is `OPENSSL_PKCS1_OAEP_PADDING`. * * @return string - The base64-encoded ciphertext. * @throws UnexpectedValueException */ - public static function encrypt(string $plaintext, $publicKey): string + public static function encrypt(string $plaintext, $publicKey, int $padding = OPENSSL_PKCS1_OAEP_PADDING): string { - if (!openssl_public_encrypt($plaintext, $encrypted, $publicKey, OPENSSL_PKCS1_OAEP_PADDING)) { + if (!openssl_public_encrypt($plaintext, $encrypted, $publicKey, $padding)) { throw new UnexpectedValueException('Encrypting the input $plaintext failed, please checking your $publicKey whether or nor correct.'); } @@ -287,7 +292,7 @@ public static function encrypt(string $plaintext, $publicKey): string * * @param string $message - Content will be `openssl_verify`. * @param string $signature - The base64-encoded ciphertext. - * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|object|resource|string|mixed $publicKey - A PEM encoded public key. + * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|object|resource|string|mixed $publicKey - The public key. * * @return boolean - True is passed, false is failed. * @throws UnexpectedValueException @@ -305,7 +310,7 @@ public static function verify(string $message, string $signature, $publicKey): b * Creates and returns a `base64_encode` string that uses `OPENSSL_ALGO_SHA256`. * * @param string $message - Content will be `openssl_sign`. - * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|object|resource|string|mixed $privateKey - A PEM encoded private key. + * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|object|resource|string|mixed $privateKey - The private key. * * @return string - The base64-encoded signature. * @throws UnexpectedValueException @@ -320,17 +325,22 @@ public static function sign(string $message, $privateKey): string } /** - * Decrypts base64 encoded string with `privateKey` with `OPENSSL_PKCS1_OAEP_PADDING`. + * Decrypts base64 encoded string with `$privateKey` in the `$padding`(default is `OPENSSL_PKCS1_OAEP_PADDING`) mode. + * + * Some of APIv2 were required the `$padding` mode as of `RSAES-PKCS1-v1_5` which is equal to the `OPENSSL_PKCS1_PADDING` constant, exposed it for this case. + * + * **Warning:** While the `$padding` is `OPENSSL_NO_PADDING` value, the `$decrypted` value must be unpadded by `caller-self`, be careful about this. * * @param string $ciphertext - Was previously encrypted string using the corresponding public key. - * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|resource|string|mixed $privateKey - A PEM encoded private key. + * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|resource|string|array{string,string}|mixed $privateKey - The private key. + * @param int $padding - One of OPENSSL_PKCS1_PADDING, OPENSSL_SSLV23_PADDING, OPENSSL_PKCS1_OAEP_PADDING, OPENSSL_NO_PADDING, default is `OPENSSL_PKCS1_OAEP_PADDING`. * * @return string - The utf-8 plaintext. * @throws UnexpectedValueException */ - public static function decrypt(string $ciphertext, $privateKey): string + public static function decrypt(string $ciphertext, $privateKey, int $padding = OPENSSL_PKCS1_OAEP_PADDING): string { - if (!openssl_private_decrypt(base64_decode($ciphertext), $decrypted, $privateKey, OPENSSL_PKCS1_OAEP_PADDING)) { + if (!openssl_private_decrypt(base64_decode($ciphertext), $decrypted, $privateKey, $padding)) { throw new UnexpectedValueException('Decrypting the input $ciphertext failed, please checking your $privateKey whether or nor correct.'); } diff --git a/tests/Crypto/RsaTest.php b/tests/Crypto/RsaTest.php index 6d50c92..4c624ee 100644 --- a/tests/Crypto/RsaTest.php +++ b/tests/Crypto/RsaTest.php @@ -3,6 +3,9 @@ namespace WeChatPay\Tests\Crypto; use const PHP_MAJOR_VERSION; +use const OPENSSL_PKCS1_OAEP_PADDING; +use const OPENSSL_PKCS1_PADDING; +use const OPENSSL_SSLV23_PADDING; use function file_get_contents; use function method_exists; @@ -16,6 +19,8 @@ use function substr; use function rtrim; +use UnexpectedValueException; + use WeChatPay\Crypto\Rsa; use PHPUnit\Framework\TestCase; @@ -261,6 +266,55 @@ public function testDecrypt(string $plaintext, $publicKey, $privateKey): void self::assertEquals($plaintext, $mytext); } + /** + * @return array}> + */ + public function crossPaddingPhrasesProvider(): array + { + [, , , , , , , [$privateKey], , , , , , [$publicKey]] = array_values($this->keyPhrasesDataProvider()); + return [ + 'encrypted as OPENSSL_PKCS1_OAEP_PADDING, and decrpted as OPENSSL_PKCS1_PADDING' => [ + random_bytes(32), [$publicKey, OPENSSL_PKCS1_OAEP_PADDING], [$privateKey, OPENSSL_PKCS1_PADDING], UnexpectedValueException::class + ], + 'encrypted as OPENSSL_PKCS1_OAEP_PADDING, and decrpted as OPENSSL_SSLV23_PADDING' => [ + random_bytes(32), [$publicKey, OPENSSL_PKCS1_OAEP_PADDING], [$privateKey, OPENSSL_SSLV23_PADDING], UnexpectedValueException::class + ], + 'encrypted as OPENSSL_PKCS1_PADDING, and decrpted as OPENSSL_PKCS1_OAEP_PADDING' => [ + random_bytes(32), [$publicKey, OPENSSL_PKCS1_PADDING], [$privateKey, OPENSSL_PKCS1_OAEP_PADDING], UnexpectedValueException::class + ], + 'encrypted as OPENSSL_PKCS1_PADDING, and decrpted as OPENSSL_SSLV23_PADDING' => [ + random_bytes(32), [$publicKey, OPENSSL_PKCS1_PADDING], [$privateKey, OPENSSL_SSLV23_PADDING], null + ], + 'encrypted as OPENSSL_SSLV23_PADDING, and decrpted as OPENSSL_PKCS1_PADDING' => [ + random_bytes(32), [$publicKey, OPENSSL_SSLV23_PADDING], [$privateKey, OPENSSL_PKCS1_PADDING], null + ], + 'encrypted as OPENSSL_SSLV23_PADDING, and decrpted as OPENSSL_PKCS1_OAEP_PADDING' => [ + random_bytes(32), [$publicKey, OPENSSL_SSLV23_PADDING], [$privateKey, OPENSSL_PKCS1_OAEP_PADDING], UnexpectedValueException::class + ], + ]; + } + + /** + * @dataProvider crossPaddingPhrasesProvider + * @param string $plaintext + * @param array{\OpenSSLAsymmetricKey|resource|mixed,int} $publicKeyAndPaddingMode + * @param array{\OpenSSLAsymmetricKey|resource|mixed,int} $privateKeyAndPaddingMode + * @param ?class-string<\UnexpectedValueException> $exception + */ + public function testCrossEncryptDecryptWithDifferentPadding( + string $plaintext, array $publicKeyAndPaddingMode, array $privateKeyAndPaddingMode, ?string $exception = null + ): void + { + $ciphertext = Rsa::encrypt($plaintext, ...$publicKeyAndPaddingMode); + if ($exception) { + $this->expectException($exception); + } + $decrypted = Rsa::decrypt($ciphertext, ...$privateKeyAndPaddingMode); + if ($exception === null) { + self::assertEquals($plaintext, $decrypted); + } + } + /** * @dataProvider keysProvider * @param string $plaintext diff --git a/tests/README.md b/tests/README.md index 73f52b6..6d15f4d 100644 --- a/tests/README.md +++ b/tests/README.md @@ -91,18 +91,18 @@ Certificate: vendor/bin/phpunit PHPUnit 9.5.9 by Sebastian Bergmann and contributors. -............................................................... 63 / 490 ( 12%) -............................................................... 126 / 490 ( 25%) -............................................................... 189 / 490 ( 38%) -............................................................... 252 / 490 ( 51%) -............................................................... 315 / 490 ( 64%) -............................................................... 378 / 490 ( 77%) -............................................................... 441 / 490 ( 90%) -................................................. 490 / 490 (100%) - -Time: 00:00.615, Memory: 12.00 MB - -OK (490 tests, 2177 assertions) +............................................................... 63 / 497 ( 12%) +............................................................... 126 / 497 ( 25%) +............................................................... 189 / 497 ( 38%) +............................................................... 252 / 497 ( 50%) +............................................................... 315 / 497 ( 63%) +............................................................... 378 / 497 ( 76%) +............................................................... 441 / 497 ( 88%) +........................................................ 497 / 497 (100%) + +Time: 00:00.634, Memory: 12.00 MB + +OK (497 tests, 2251 assertions) rm -rf ./tests/fixtures/mock.* ``` From 8add1f34c4c5f9d6fd3998a6adf6e5600c22c3d7 Mon Sep 17 00:00:00 2001 From: James ZHNAG Date: Sun, 5 Sep 2021 14:36:16 +0800 Subject: [PATCH 6/8] strictly support by `Rsa::paddingModeLimitedCheck` only --- src/Crypto/Rsa.php | 33 +++++++++++++++++++++++++++++++-- tests/Crypto/RsaTest.php | 17 ++++++++++++++--- tests/README.md | 24 ++++++++++++------------ 3 files changed, 57 insertions(+), 17 deletions(-) diff --git a/src/Crypto/Rsa.php b/src/Crypto/Rsa.php index ca50a42..dfd9e17 100644 --- a/src/Crypto/Rsa.php +++ b/src/Crypto/Rsa.php @@ -4,6 +4,7 @@ use const OPENSSL_ALGO_SHA256; use const OPENSSL_PKCS1_OAEP_PADDING; +use const OPENSSL_PKCS1_PADDING; use const PHP_URL_SCHEME; use function array_column; @@ -264,6 +265,30 @@ private static function parse($thing, string $type = self::KEY_TYPE_PRIVATE) return $src; } + /** + * Check the RSA padding mode either `OPENSSL_PKCS1_PADDING` or `OPENSSL_PKCS1_OAEP_PADDING`. + * + * **Warning:** + * + * Decryption failures in the `RSA_PKCS1_PADDING` mode leak information which can potentially be used to mount a Bleichenbacher padding oracle attack. + * This is an inherent weakness in the PKCS #1 v1.5 padding design. Prefer `RSA_PKCS1_OAEP_PADDING`. + * + * **`RSA_SSLV23_PADDING`** - PKCS #1 v1.5 padding with an SSL-specific modification that denotes that the server is SSL3 capable. + * **`RSA_NO_PADDING`** - Raw RSA encryption. *Insecure* + * + * @link https://www.openssl.org/docs/man1.1.1/man3/RSA_public_encrypt.html + * + * @param int $padding - The padding mode, only support `OPENSSL_PKCS1_PADDING` or `OPENSSL_PKCS1_OAEP_PADDING`, otherwise thrown `\UnexpectedValueException`. + * + * @throws UnexpectedValueException + */ + private static function paddingModeLimitedCheck(int $padding): void + { + if (!($padding === OPENSSL_PKCS1_OAEP_PADDING || $padding === OPENSSL_PKCS1_PADDING)) { + throw new UnexpectedValueException(sprintf("Doesn't supported padding mode(%d), here only support OPENSSL_PKCS1_OAEP_PADDING or OPENSSL_PKCS1_PADDING.", $padding)); + } + } + /** * Encrypts text by the given `$publicKey` in the `$padding`(default is `OPENSSL_PKCS1_OAEP_PADDING`) mode. * @@ -273,13 +298,15 @@ private static function parse($thing, string $type = self::KEY_TYPE_PRIVATE) * * @param string $plaintext - Cleartext to encode. * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|object|resource|string|mixed $publicKey - The public key. - * @param int $padding - One of OPENSSL_PKCS1_PADDING, OPENSSL_SSLV23_PADDING, OPENSSL_PKCS1_OAEP_PADDING, OPENSSL_NO_PADDING, default is `OPENSSL_PKCS1_OAEP_PADDING`. + * @param int $padding - One of OPENSSL_PKCS1_PADDING, OPENSSL_PKCS1_OAEP_PADDING, default is `OPENSSL_PKCS1_OAEP_PADDING`. * * @return string - The base64-encoded ciphertext. * @throws UnexpectedValueException */ public static function encrypt(string $plaintext, $publicKey, int $padding = OPENSSL_PKCS1_OAEP_PADDING): string { + static::paddingModeLimitedCheck($padding); + if (!openssl_public_encrypt($plaintext, $encrypted, $publicKey, $padding)) { throw new UnexpectedValueException('Encrypting the input $plaintext failed, please checking your $publicKey whether or nor correct.'); } @@ -333,13 +360,15 @@ public static function sign(string $message, $privateKey): string * * @param string $ciphertext - Was previously encrypted string using the corresponding public key. * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|resource|string|array{string,string}|mixed $privateKey - The private key. - * @param int $padding - One of OPENSSL_PKCS1_PADDING, OPENSSL_SSLV23_PADDING, OPENSSL_PKCS1_OAEP_PADDING, OPENSSL_NO_PADDING, default is `OPENSSL_PKCS1_OAEP_PADDING`. + * @param int $padding - One of OPENSSL_PKCS1_PADDING, OPENSSL_PKCS1_OAEP_PADDING, default is `OPENSSL_PKCS1_OAEP_PADDING`. * * @return string - The utf-8 plaintext. * @throws UnexpectedValueException */ public static function decrypt(string $ciphertext, $privateKey, int $padding = OPENSSL_PKCS1_OAEP_PADDING): string { + static::paddingModeLimitedCheck($padding); + if (!openssl_private_decrypt(base64_decode($ciphertext), $decrypted, $privateKey, $padding)) { throw new UnexpectedValueException('Decrypting the input $ciphertext failed, please checking your $privateKey whether or nor correct.'); } diff --git a/tests/Crypto/RsaTest.php b/tests/Crypto/RsaTest.php index 4c624ee..e976c2d 100644 --- a/tests/Crypto/RsaTest.php +++ b/tests/Crypto/RsaTest.php @@ -283,14 +283,23 @@ public function crossPaddingPhrasesProvider(): array random_bytes(32), [$publicKey, OPENSSL_PKCS1_PADDING], [$privateKey, OPENSSL_PKCS1_OAEP_PADDING], UnexpectedValueException::class ], 'encrypted as OPENSSL_PKCS1_PADDING, and decrpted as OPENSSL_SSLV23_PADDING' => [ - random_bytes(32), [$publicKey, OPENSSL_PKCS1_PADDING], [$privateKey, OPENSSL_SSLV23_PADDING], null + random_bytes(32), [$publicKey, OPENSSL_PKCS1_PADDING], [$privateKey, OPENSSL_SSLV23_PADDING], UnexpectedValueException::class ], 'encrypted as OPENSSL_SSLV23_PADDING, and decrpted as OPENSSL_PKCS1_PADDING' => [ - random_bytes(32), [$publicKey, OPENSSL_SSLV23_PADDING], [$privateKey, OPENSSL_PKCS1_PADDING], null + random_bytes(32), [$publicKey, OPENSSL_SSLV23_PADDING], [$privateKey, OPENSSL_PKCS1_PADDING], UnexpectedValueException::class ], 'encrypted as OPENSSL_SSLV23_PADDING, and decrpted as OPENSSL_PKCS1_OAEP_PADDING' => [ random_bytes(32), [$publicKey, OPENSSL_SSLV23_PADDING], [$privateKey, OPENSSL_PKCS1_OAEP_PADDING], UnexpectedValueException::class ], + 'encrypted as OPENSSL_SSLV23_PADDING, and decrpted as OPENSSL_SSLV23_PADDING' => [ + random_bytes(32), [$publicKey, OPENSSL_SSLV23_PADDING], [$privateKey, OPENSSL_SSLV23_PADDING], UnexpectedValueException::class + ], + 'encrypted as OPENSSL_PKCS1_OAEP_PADDING, and decrpted as OPENSSL_PKCS1_OAEP_PADDING' => [ + random_bytes(32), [$publicKey, OPENSSL_PKCS1_OAEP_PADDING], [$privateKey, OPENSSL_PKCS1_OAEP_PADDING], null + ], + 'encrypted as OPENSSL_PKCS1_PADDING, and decrpted as OPENSSL_PKCS1_PADDING' => [ + random_bytes(32), [$publicKey, OPENSSL_PKCS1_PADDING], [$privateKey, OPENSSL_PKCS1_PADDING], null + ], ]; } @@ -305,12 +314,14 @@ public function testCrossEncryptDecryptWithDifferentPadding( string $plaintext, array $publicKeyAndPaddingMode, array $privateKeyAndPaddingMode, ?string $exception = null ): void { - $ciphertext = Rsa::encrypt($plaintext, ...$publicKeyAndPaddingMode); if ($exception) { $this->expectException($exception); } + $ciphertext = Rsa::encrypt($plaintext, ...$publicKeyAndPaddingMode); $decrypted = Rsa::decrypt($ciphertext, ...$privateKeyAndPaddingMode); if ($exception === null) { + self::assertNotEmpty($ciphertext); + self::assertNotEmpty($decrypted); self::assertEquals($plaintext, $decrypted); } } diff --git a/tests/README.md b/tests/README.md index 6d15f4d..aa0e794 100644 --- a/tests/README.md +++ b/tests/README.md @@ -91,18 +91,18 @@ Certificate: vendor/bin/phpunit PHPUnit 9.5.9 by Sebastian Bergmann and contributors. -............................................................... 63 / 497 ( 12%) -............................................................... 126 / 497 ( 25%) -............................................................... 189 / 497 ( 38%) -............................................................... 252 / 497 ( 50%) -............................................................... 315 / 497 ( 63%) -............................................................... 378 / 497 ( 76%) -............................................................... 441 / 497 ( 88%) -........................................................ 497 / 497 (100%) - -Time: 00:00.634, Memory: 12.00 MB - -OK (497 tests, 2251 assertions) +............................................................... 63 / 500 ( 12%) +............................................................... 126 / 500 ( 25%) +............................................................... 189 / 500 ( 37%) +............................................................... 252 / 500 ( 50%) +............................................................... 315 / 500 ( 63%) +............................................................... 378 / 500 ( 75%) +............................................................... 441 / 500 ( 88%) +........................................................... 500 / 500 (100%) + +Time: 00:00.641, Memory: 12.00 MB + +OK (500 tests, 2258 assertions) rm -rf ./tests/fixtures/mock.* ``` From ca38979100b0adf3b8aa4eff251a0ddfb0e5ff41 Mon Sep 17 00:00:00 2001 From: James ZHNAG Date: Sun, 5 Sep 2021 16:59:57 +0800 Subject: [PATCH 7/8] bump to v1.2.1 --- CHANGELOG.md | 9 +++++++++ README.md | 4 ++-- composer.json | 2 +- src/ClientDecoratorInterface.php | 2 +- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index feef98e..f8a834d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # 变更历史 +## 1.2.1 - 2021-09-06 + +[变更细节](../../compare/v1.2.0...v1.2.1) + +- 增加`加密RSA私钥`的测试用例覆盖; +- 优化文档样例及升级指南,修正错别字; +- 优化内部`withDefaults`函数,使用变长参数合并初始化参数; +- 优化`Rsa::encrypt`及`Rsa::decrpt`方法,增加第三可选参数,以支持`OPENSSL_PKCS1_PADDING`填充模式的加解密; + ## 1.2.0 - 2021-09-02 [变更细节](../../compare/v1.1.4...v1.2.0) diff --git a/README.md b/README.md index baf58a6..b544c50 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ APIv3已内置 `请求签名` 和 `应答验签` 两个middleware中间件,创 ## 项目状态 -当前版本为`1.2.0`测试版本。 +当前版本为`1.2.1`测试版本。 请商户的专业技术人员在使用时注意系统和软件的正确性和兼容性,以及带来的风险。 **版本说明:** `开发版`指: `类库API`随时会变;`测试版`指: 少量`类库API`可能会变;`稳定版`指: `类库API`稳定持续;版本遵循[语义化版本号](https://semver.org/lang/zh-CN/)规则。 @@ -60,7 +60,7 @@ composer require wechatpay/wechatpay ```json "require": { - "wechatpay/wechatpay": "^1.2.0" + "wechatpay/wechatpay": "^1.2.1" } ``` diff --git a/composer.json b/composer.json index c9da45e..e3436d2 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "name": "wechatpay/wechatpay", - "version": "1.2.0", + "version": "1.2.1", "description": "[A]Sync Chainable WeChatPay v2&v3's OpenAPI SDK for PHP", "type": "library", "keywords": [ diff --git a/src/ClientDecoratorInterface.php b/src/ClientDecoratorInterface.php index f5c9cad..db3e71f 100644 --- a/src/ClientDecoratorInterface.php +++ b/src/ClientDecoratorInterface.php @@ -14,7 +14,7 @@ interface ClientDecoratorInterface /** * @var string - This library version */ - public const VERSION = '1.2.0'; + public const VERSION = '1.2.1'; /** * @var string - The HTTP transfer `xml` based protocol From 6ea22794a1ca80d8fd08315dbb2b8f516ff8a0d7 Mon Sep 17 00:00:00 2001 From: James ZHANG Date: Sun, 5 Sep 2021 20:11:09 +0800 Subject: [PATCH 8/8] optim Rsa's code constants&coments --- src/Crypto/Rsa.php | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/Crypto/Rsa.php b/src/Crypto/Rsa.php index dfd9e17..632e8d0 100644 --- a/src/Crypto/Rsa.php +++ b/src/Crypto/Rsa.php @@ -38,7 +38,7 @@ use UnexpectedValueException; /** - * Provides some methods for the RSA `OPENSSL_ALGO_SHA256` with `OPENSSL_PKCS1_OAEP_PADDING`. + * RSA `PKEY` loader and encrypt/decrypt/sign/verify methods. */ class Rsa { @@ -48,18 +48,18 @@ class Rsa public const KEY_TYPE_PRIVATE = 'private'; private const LOCAL_FILE_PROTOCOL = 'file://'; - private const PKEY_NEEDLE = ' KEY-'; - private const PKEY_FORMAT = "-----BEGIN %1\$s KEY-----\n%2\$s\n-----END %1\$s KEY-----"; + private const PKEY_PEM_NEEDLE = ' KEY-'; + private const PKEY_PEM_FORMAT = "-----BEGIN %1\$s KEY-----\n%2\$s\n-----END %1\$s KEY-----"; private const PKEY_PEM_FORMAT_PATTERN = '#-{5}BEGIN ((?:RSA )?(?:PUBLIC|PRIVATE)) KEY-{5}\r?\n([^-]+)\r?\n-{5}END \1 KEY-{5}#'; private const CHR_CR = "\r"; private const CHR_LF = "\n"; /** @var array - Supported loading rules */ private const RULES = [ - 'private.pkcs1' => [self::PKEY_FORMAT, 'RSA PRIVATE', 16], - 'private.pkcs8' => [self::PKEY_FORMAT, 'PRIVATE', 16], - 'public.pkcs1' => [self::PKEY_FORMAT, 'RSA PUBLIC', 15], - 'public.spki' => [self::PKEY_FORMAT, 'PUBLIC', 14], + 'private.pkcs1' => [self::PKEY_PEM_FORMAT, 'RSA PRIVATE', 16], + 'private.pkcs8' => [self::PKEY_PEM_FORMAT, 'PRIVATE', 16], + 'public.pkcs1' => [self::PKEY_PEM_FORMAT, 'RSA PUBLIC', 15], + 'public.spki' => [self::PKEY_PEM_FORMAT, 'PUBLIC', 14], ]; /** @@ -250,7 +250,7 @@ private static function parse($thing, string $type = self::KEY_TYPE_PRIVATE) } } - if (is_int(strpos($src, self::PKEY_NEEDLE))) { + if (is_int(strpos($src, self::PKEY_PEM_NEEDLE))) { if ($type === self::KEY_TYPE_PUBLIC && preg_match(self::PKEY_PEM_FORMAT_PATTERN, $src, $matches)) { [, $kind, $base64] = $matches; $mapRules = (array)array_combine(array_column(self::RULES, 1/*column*/), array_keys(self::RULES)); @@ -294,8 +294,6 @@ private static function paddingModeLimitedCheck(int $padding): void * * Some of APIv2 were required the `$padding` mode as of `RSAES-PKCS1-v1_5` which is equal to the `OPENSSL_PKCS1_PADDING` constant, exposed it for this case. * - * **Warning:** While the `$padding` is `OPENSSL_NO_PADDING` value, the `$plaintext` must be padded by `caller-self`, be careful about this. - * * @param string $plaintext - Cleartext to encode. * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|object|resource|string|mixed $publicKey - The public key. * @param int $padding - One of OPENSSL_PKCS1_PADDING, OPENSSL_PKCS1_OAEP_PADDING, default is `OPENSSL_PKCS1_OAEP_PADDING`. @@ -356,8 +354,6 @@ public static function sign(string $message, $privateKey): string * * Some of APIv2 were required the `$padding` mode as of `RSAES-PKCS1-v1_5` which is equal to the `OPENSSL_PKCS1_PADDING` constant, exposed it for this case. * - * **Warning:** While the `$padding` is `OPENSSL_NO_PADDING` value, the `$decrypted` value must be unpadded by `caller-self`, be careful about this. - * * @param string $ciphertext - Was previously encrypted string using the corresponding public key. * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|resource|string|array{string,string}|mixed $privateKey - The private key. * @param int $padding - One of OPENSSL_PKCS1_PADDING, OPENSSL_PKCS1_OAEP_PADDING, default is `OPENSSL_PKCS1_OAEP_PADDING`.