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

如何解决JavaScript签名数据后PHP端验证失败问题?

JS RSA签名与PHP服务端验证问题解决

我需要在JavaScript中对文本签名,然后在PHP服务端验证。测试时用JS生成RSA密钥对,JS内验证签名有效,但PHP验证始终失败,报错error:02000077:rsa routines::wrong signature length和error:0480006C:PEM routines::no start line。


1. JavaScript生成RSA密钥对

// 生成RSA密钥对
async function generateKeyPair() {
    const keyPair = await window.crypto.subtle.generateKey(
        {
            name: "RSASSA-PKCS1-v1_5",
            modulusLength: 2048,
            publicExponent: new Uint8Array([0x01, 0x00, 0x01]), // 65537
            hash: { name: "SHA-256" },
        },
        true,
        ["sign", "verify"]
    );

    const publicKeyBuffer = await window.crypto.subtle.exportKey("spki", keyPair.publicKey);
    const privateKeyBuffer = await window.crypto.subtle.exportKey("pkcs8", keyPair.privateKey);

    // 转Base64字符串返回
    return {
        privateKey: btoa(String.fromCharCode(...new Uint8Array(privateKeyBuffer))),
        publicKey: btoa(String.fromCharCode(...new Uint8Array(publicKeyBuffer))),
    };
}

生成的密钥示例:

privateKey: "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDw4Ees4C+vsTUQodgWKIsj3Ni67RG3ny9xY1KjCaatu2o/ev5hS4yrxxWLZAFU9mt/rfNmzzby3mqlWPWm8Df91Mue6wNTsN2yMnHw+XvcvovCngSTH4H2zY+uAhEiG+u+vzGqbzxm0JB3ybX5kYEMK2iKoALq1ASJ781gyy7AsCf/Ck+OvIE4in1kNm4a5NUgbyuflWerMIB7FUQ7h/+XlLn3F2bvC1SWWxKsmQ/dF5fYpZaAV2KvVw2LMnkWdU536an9vxj5LZIJyzfNQv/foNGcUh8iT1tLe8jV4eYrAcqiLnG+iZFFc3X5F33WUILmRvCg11bec6ic1NwuY8eDAgMBAAECggEAQq7kSNCbgvj85sjXSHMa6ee2vDDrKblQ6gQEGYyPbyMmK8LB72951wg7R6Z80+eQJP2kF38gCCZYwcOZ5gg0h/nEEQ+gkSeyiCV886gtiRPbHxqdy5j6YrfPoe2Cjr3KCrllZ3h58UCl7fOShC+q2RKfU1ku1ZGyW/leEwDMxZy1PISHFHtmd43LdrkWgyNk4TIpNRzizx+gxNeyQUEZDfkUu4mFP/weWM26lLyaE+RkPqvFnLjXckvgno1bY8Hq2yywVkyvfBo0tQvVBtNP5WTEyOvNGylc46pnVBODrSUn5q4ZNdi56fd7WFPBFySAVQiA0uLgaWYOuBUGMyc5CQKBgQD+wcyYL2IgA5O2c6Jp9cJV9xQVvWEQ/sVUDJgRVhOxAqw9r2LmUe7tRnWYEI4Sz9g/ejFq8fkL+h2lQbGB+ZKlu5EVHrmfuYf3zo80QKMWC1XKbYnw0HKkMlOqxiMYyu6PqFX59icmcZ58k1m9h2br5f7GGGWAFY8yFgRUIUR6FwKBgQDyDSSbH7WoqUUhYNvY9wKUUYM8uZSAC1TPfuR/ZvAec3cZxMJyOnY88MPOh63vUMzTUt6AAyps2EFPa0UGuysevMaXSL+MAQQzDfnEC2KfeRqkVOKYPrjjjxIl5mQJCacpB7rdzLszmtJJ9G99lTqeGuVa3mhlJupqckYbbdO9dQKBgQD9zf4TMEHGO0oSX6nTfvCZzIrKDd6CnA/j6JgnzWXY2BzZZ75UUBSFd8j4MqYYv9FljEtnjKLd99VJKuW54/bh/rhQHkg4hRKdI8EwAaV49NoHzpG6xTExvKH2ZWfZ73M01DSzzzS57EBFRFgHpro3EvB8UxnsPY5oC99MIcijCQKBgBn16OguVXh6dyymS84QaBlqSK4ZpWC6VmVO0ckMTFKnxa1g2g4QUSAmHoonKTOSsfU0XSLTtBgqdY7EDYo0RuKsEoylQ84LSd0D8bbiFbjO71mStR7pE0Fs1eB0vmPtwhz3dEZXr/hP8Z/29II+oCPW9KRzWDUJIHk8OmK0u9IFAoGAEOWhm/zaXMNJ+oBcvBbCKTZ0XInzvV4SqhC6Bj9aC8wqCe5QKyKl9HglG9J+o3D+hIEcMXGvIv1KB3xDStCQQKcDOrD/8tGZtstSONaNzeGg0hUY9SKd7R2wMPEWufzccFE+zVG5hHUg+eQrnzXdXkG8hW1QxQxgoDkC3DNVsnE="
publicKey: "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA8OBHrOAvr7E1EKHYFiiLI9zYuu0Rt58vcWNSowmmrbtqP3r+YUuMq8cVi2QBVPZrf63zZs828t5qpVj1pvA3/dTLnusDU7DdsjJx8Pl73L6Lwp4Ekx+B9s2PrgIRIhvrvr8xqm88ZtCQd8m1+ZGBDCtoiqAC6tQEie/NYMsuwLAn/wpPjryBOIp9ZDZuGuTVIG8rn5VnqzCAexVEO4f/l5S59xdm7wtUllsSrJkP3ReX2KWWgFdir1cNizJ5FnVOd+mp/b8Y+S2SCcs3zUL/36DRnFIfIk9bS3vI1eHmKwHKoi5xvomRRXN1+Rd91lCC5kbwoNdW3nOonNTcLmPHgwIDAQAB"

2. JavaScript签名代码(修正后)

// 对数据签名
async function signRequestData(data, privateKeyBase64) {
    const encoder = new TextEncoder();
    const dataBuffer = encoder.encode(data);
    
    // 导入PKCS8格式的私钥
    const privateKey = await window.crypto.subtle.importKey(
        "pkcs8",
        Uint8Array.from(atob(privateKeyBase64), c => c.charCodeAt(0)),
        { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" },
        false,
        ["sign"]
    );

    // 直接传入原始数据,sign方法会自动执行SHA-256哈希
    const signatureBuffer = await window.crypto.subtle.sign(
        { name: "RSASSA-PKCS1-v1_5" },
        privateKey,
        dataBuffer
    );

    const signatureArray = new Uint8Array(signatureBuffer);
    const signatureBase64 = btoa(String.fromCharCode.apply(null, signatureArray));

    return signatureBase64;
}

3. JavaScript验证代码(验证通过)

// 验证签名
async function verifySignature(data, signatureBase64, publicKeyBase64) {
    const encoder = new TextEncoder();
    const dataBuffer = encoder.encode(data);
    const signatureArray = new Uint8Array(atob(signatureBase64).split("").map((c) => c.charCodeAt(0)));
    
    // 导入SPKI格式的公钥
    const publicKey = await window.crypto.subtle.importKey(
        "spki",
        Uint8Array.from(atob(publicKeyBase64), c => c.charCodeAt(0)),
        { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" },
        false,
        ["verify"]
    );
    
    // 直接传入原始数据,verify方法自动做哈希验证
    const isSignatureValid = await window.crypto.subtle.verify(
        { name: "RSASSA-PKCS1-v1_5" },
        publicKey,
        signatureArray,
        dataBuffer
    );

    return isSignatureValid;
}

4. 修正后的PHP验证代码

// 验证签名
public function verifySignature($data, $signatureBase64, $publicKeyBase64) {      
    // 格式化公钥:添加换行符,符合OpenSSL的PEM格式要求
    $formattedPublicKey = "-----BEGIN PUBLIC KEY-----\n" . 
        wordwrap($publicKeyBase64, 64, "\n", true) . 
        "\n-----END PUBLIC KEY-----";

    // 导入公钥
    $publicKeyResource = openssl_pkey_get_public($formattedPublicKey);
    if ($publicKeyResource === false) {
        // 打印详细错误信息
        while ($error = openssl_error_string()) {
            echo $error . "\n";
        }
        die("导入公钥失败");
    }

    // 将Base64签名解码为二进制数据
    $signature = base64_decode($signatureBase64);

    // 验证签名:OPENSSL_ALGO_SHA256指定签名时使用的哈希算法
    $isSignatureValid = openssl_verify($data, $signature, $publicKeyResource, OPENSSL_ALGO_SHA256);

    // 释放资源
    openssl_free_key($publicKeyResource);

    return ($isSignatureValid === 1);
}   

错误原因分析

  1. 公钥格式问题:OpenSSL要求PEM格式的公钥每行最多64个字符,原代码直接拼接BEGIN/END后,公钥是连续的Base64字符串,导致no start line错误。
  2. 签名逻辑不匹配:原JS代码手动对数据做SHA-256哈希后再签名,而PHP的openssl_verify默认会对传入的$data重新做哈希验证,相当于重复哈希,导致wrong signature length错误。

可选方案:PHP生成密钥对

如果想避免跨语言格式问题,可以直接在PHP中生成密钥对,再传给JS使用:

// 生成RSA密钥对配置
$config = array(
    "digest_alg" => "sha256",
    "private_key_bits" => 2048,
    "private_key_type" => OPENSSL_KEYTYPE_RSA,
);

// 生成密钥对
$res = openssl_pkey_new($config);

// 导出PKCS8格式私钥(Base64)
openssl_pkey_export($res, $privateKeyPem);
$privateKeyBase64 = str_replace(array("-----BEGIN PRIVATE KEY-----", "-----END PRIVATE KEY-----", "\n"), "", $privateKeyPem);

// 导出SPKI格式公钥(Base64)
$publicKeyDetails = openssl_pkey_get_details($res);
$publicKeyPem = $publicKeyDetails['key'];
$publicKeyBase64 = str_replace(array("-----BEGIN PUBLIC KEY-----", "-----END PUBLIC KEY-----", "\n"), "", $publicKeyPem);

// 输出密钥供JS使用
echo "私钥(Base64): " . $privateKeyBase64 . "\n";
echo "公钥(Base64): " . $publicKeyBase64 . "\n";

内容的提问来源于stack exchange,提问作者Jason 'Slingshot' Miller

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 17:19:54