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

PHP与CryptoJS的HMAC-SHA256签名结果不一致问题及适配方案

Fixing HMAC-SHA256 Signature Mismatches Between PHP Laravel and CryptoJS

Hey there, let's break down why you're seeing differences between your PHP Laravel signature implementation and the working CryptoJS one in Postman, plus give you a solid solution that aligns with the third-party API's requirements.

Core Reasons for the Discrepancy

The main gaps between CryptoJS and PHP typically boil down to encoding consistency and hash output handling:

  • UTF-8 Enforcement: CryptoJS treats all input strings as UTF-8 by default, but if your PHP code isn't explicitly ensuring UTF-8 encoding for stringToSign or partnerSecret, hidden encoding mismatches (like GBK or ISO-8859-1) will produce different raw byte inputs, leading to invalid signatures.
  • Hash Output Format: CryptoJS returns a WordArray object from HmacSHA256—when converted to hex, it uses a different byte ordering than PHP's hash_hmac output. Even though the underlying binary data is identical, the hex strings might look different. This is why you saw the final base64 signature match sometimes, but direct hex comparisons failed.
  • Key Handling: If your partnerSecret is a base64-encoded string from the third party, CryptoJS uses it as a UTF-8 string directly, but PHP might need you to decode it to raw bytes first (depending on the API's requirements).

Correct PHP Laravel Implementation

Here's a robust function that strictly follows the signature rule signature = base64(hmacSHA256(utf8(partnerSecret), utf8(stringToSign))):

<?php

function generateThirdPartySignature(string $stringToSign, string $partnerSecret): string
{
    // Force UTF-8 encoding to match CryptoJS's default behavior
    $utf8StringToSign = mb_convert_encoding($stringToSign, 'UTF-8', mb_detect_encoding($stringToSign));
    $utf8PartnerSecret = mb_convert_encoding($partnerSecret, 'UTF-8', mb_detect_encoding($partnerSecret));

    // Generate raw binary HMAC-SHA256 hash (critical for matching CryptoJS)
    $rawHmac = hash_hmac('sha256', $utf8StringToSign, $utf8PartnerSecret, true);

    // Encode the binary hash to base64 for the final signature
    return base64_encode($rawHmac);
}

// Example usage in Laravel:
$stringToSign = "your-constructed-string-to-sign";
$partnerSecret = env('THIRD_PARTY_PARTNER_SECRET');
$signature = generateThirdPartySignature($stringToSign, $partnerSecret);
?>

Key Details to Note

  1. UTF-8 Guarantee: Using mb_convert_encoding ensures even if your input strings come from non-UTF-8 sources (like legacy databases), they're converted properly before hashing.
  2. Raw Binary Hash: Setting the fourth parameter of hash_hmac to true returns the raw binary hash, just like CryptoJS's internal WordArray. Avoid using the hex output here—converting hex back to binary can introduce byte ordering issues.
  3. No Extra Conversions: Skip manual hex-to-binary steps; directly encode the raw hash to base64 to match CryptoJS's workflow.

How to Verify Consistency

To confirm your PHP code matches CryptoJS:

  1. In Postman's pre-request script, log both the hex and base64 outputs:
    const stringToSign = "your-test-string";
    const partnerSecret = "your-test-secret";
    const hmac = CryptoJS.HmacSHA256(stringToSign, partnerSecret);
    console.log("CryptoJS Hex:", hmac.toString(CryptoJS.enc.Hex));
    console.log("CryptoJS Base64:", hmac.toString(CryptoJS.enc.Base64));
    
  2. In PHP, log the corresponding values:
    $utf8String = mb_convert_encoding("your-test-string", 'UTF-8');
    $utf8Secret = mb_convert_encoding("your-test-secret", 'UTF-8');
    echo "PHP Hex: " . hash_hmac('sha256', $utf8String, $utf8Secret, false) . PHP_EOL;
    echo "PHP Base64: " . base64_encode(hash_hmac('sha256', $utf8String, $utf8Secret, true)) . PHP_EOL;
    
    The hex strings might differ (due to byte ordering in serialization), but the base64 outputs must be identical. If they are, your signature is correct.

Common Pitfalls to Avoid

  • Undecoded Partner Secret: If the third party provided a base64-encoded secret, use base64_decode($partnerSecret) before passing it to hash_hmac—CryptoJS might be handling this implicitly if you're passing the raw base64 string as UTF-8.
  • Mismatched StringToSign: Double-check that your PHP code constructs stringToSign exactly like Postman—even a single missing space, wrong parameter order, or extra newline will break the signature.
  • Laravel String Helpers: Avoid using Laravel's Str methods that modify the string (like trim or escape) unless you're sure they match the third party's requirements for stringToSign.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.09 10:17:34