You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Laravel 9加密签名字符串提交外部URL遇400错误排查

Laravel 9中加密签名提交外部URL遇400 Bad Request排查

我的操作流程

  • 拼接字符串:
    $data = 'stringA' . 'stringB';
    
  • 公钥加密字符串:
    加密函数:
    // Encrypt String using Public Certificate
    function encrypt($plainText, $publicCertificatePath)
    {
        $cert = file_get_contents($publicCertificatePath);
        $cert = openssl_x509_read($cert);
        $publicKey = openssl_get_publickey($cert);
        // openssl_free_key($cert);
    
        $cipherText = '';
        openssl_public_encrypt($plainText, $cipherText, $publicKey, OPENSSL_PKCS1_PADDING);
    
        return base64_encode($cipherText);
    }
    
    调用加密:
    // public certificate was provided by the URL I am submitting data to
    $encryptedCredentials = encrypt($data, storage_path('app/certificates/public-cert.pem'));
    
  • 生成自签名证书:
    function generateCertificate($commonName, $countryName, $stateOrProvinceName, $localityName, $organizationName, $organizationalUnitName, $emailAddress, $outputPath)
    {
        // Generate private key
        $process = new Process([
            'openssl',
            'genpkey',
            '-algorithm',
            'RSA',
            '-out',
            $outputPath . '.key.pem',
            '-aes256',
            '-pass',
            'pass:password',
            '-outform',
            'PEM'
        ]);
        $process->run();
        if (!$process->isSuccessful()) {
            throw new ProcessFailedException($process);
        }
        // Generate CSR
        $process = new Process([
            'openssl',
            'req',
            '-new',
            '-key',
            $outputPath . '.key.pem',
            '-out',
            $outputPath . '.csr.pem',
            '-passin',
            'pass:password',
            '-subj',
            "/C=$countryName/ST=$stateOrProvinceName/L=$localityName/O=$organizationName/OU=$organizationalUnitName/CN=$commonName/emailAddress=$emailAddress"
        ]);
        $process->run();
        if (!$process->isSuccessful()) {
            throw new ProcessFailedException($process);
        }
        // Generate self-signed certificate
        $process = new Process([
            'openssl',
            'x509',
            '-req',
            '-days',
            '365',
            '-in',
            $outputPath . '.csr.pem',
            '-signkey',
            $outputPath . '.key.pem',
            '-out',
            $outputPath . '.cert.pem',
            '-passin',
            'pass:password'
        ]);
        $process->run();
        if (!$process->isSuccessful()) {
            throw new ProcessFailedException($process);
        }
    }
    
    generateCertificate('name', 'UG', 'state', 'Location', 'frd', 'Ecom', 'myemail@gmail.com', storage_path('app/certificates/'));
    
  • 私钥签名加密内容:
    签名函数:
    // Sign String using MDA Private Certificate
    function signString(string $text, string $privateKeyPath, string $passphrase): string
    {
        // Load private key
        $privateKey = openssl_get_privatekey(file_get_contents($privateKeyPath), $passphrase);
    
        // Sign the string
        openssl_sign($text, $signature, $privateKey, OPENSSL_ALGO_SHA256);
    
        // Free the key from memory
        openssl_free_key($privateKey);
    
        // Return the signature as a base64-encoded string
        return base64_encode($signature);
    }
    
    调用签名:
    // sign using MDA private certificate.
    $signedCredentials = signString($encryptedCredentials, storage_path('app/certificates/private.pem'), '');
    
  • 发送请求到外部URL:
    $url = "https://urlfromprovider";
    
    $data = [];
    
    $postData = http_build_query($data);
    
    // return $postData;
    
    $opts = array(
        'http' =>
        array(
            'method'  => 'POST',
            'header'  => "Content-Type: text/xml\r\n",
            'content' => $postData
        )
    );
    
    $context  = stream_context_create($opts);
    
    $response = file_get_contents($url, false, $context, 40000);
    
    return $response;
    

问题:严格按服务商要求操作,但始终返回400 Bad Request,可能的原因有哪些?


排查方向

1. 请求数据为空或格式不匹配

你定义的$data是空数组,http_build_query($data)生成空字符串,但请求头设置为Content-Type: text/xml,服务商大概率需要你提交包含加密数据和签名的XML结构内容,而非空值。需按服务商要求的XML格式填充$encryptedCredentials和$signedCredentials,示例:

$xml = <<<XML
<request>
  <encryptedData>$encryptedCredentials</encryptedData>
  <signature>$signedCredentials</signature>
</request>
XML;
$opts['http']['content'] = $xml;

2. 私钥路径与密码不匹配

生成自签名证书时,私钥文件路径是storage_path('app/certificates/.key.pem')($outputPath结尾带斜杠,拼接后文件名是.key.pem),但你签名时用的是storage_path('app/certificates/private.pem'),路径完全不符。另外生成私钥时设置了密码password,但签名时$passphrase传空字符串,导致无法正确加载私钥,签名失效。

3. 加密/签名算法不匹配

服务商可能要求特定的加密填充方式(比如OPENSSL_PKCS1_OAEP_PADDING而非当前的OPENSSL_PKCS1_PADDING)或签名哈希算法(比如OPENSSL_ALGO_SHA1),需核对服务商文档确认算法一致性。

4. 证书相关问题

  • 生成的私钥是AES256加密的,加载时必须提供正确密码;
  • 若服务商要求提前上传你的自签名证书公钥到他们系统,未上传会导致签名无法验证;
  • 检查证书文件权限,确保Laravel进程能正常读取证书。

5. 请求头不完整

服务商可能要求额外请求头(比如Accept、自定义签名校验头、Content-Length),需补充完整符合要求的请求头。

6. 字符串拼接规则错误

服务商可能要求拼接时添加分隔符(如stringA|stringB)、按特定顺序拼接、包含其他参数,需再次核对拼接规则是否完全符合要求。


内容的提问来源于stack exchange,提问作者jonahgeek

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 16:37:58