Generate CloudFlare R2 Authorization Header to upload image via API by curl php

Thanks everyone for your support.

I have a php project that creates images from AI API, then uploads images to R2 to store and get image links from my domain. I’m having problems creating AH for curl to upload images to R2, here is my function:

function createS3AuthorizationHeader($accessKey, $secretKey, $bucket, $objectKey, $region, $contentType) {
        $service = 's3';
        $httpMethod = 'PUT';
        
        date_default_timezone_set('UTC'); // Ensure we use UTC time zone

        $now = time();
        $amzDate = gmdate('Ymd\THis\Z', $now);

        // Extract date components from amzDate
        $dateStamp = substr($amzDate, 0, 8);

        Log::debug('AMZDate: '. $amzDate);
        Log::debug('DateStamp: '. $dateStamp);

        // Canonical URI
        $canonicalUri = "/{$bucket}/{$objectKey}";

        // Canonical Query String (empty for Cloudflare)
        $canonicalQueryString = '';

        // Canonical Headers
        $canonicalHeaders = "content-type:$contentType\nhost:{$bucket}.{$region}.r2.cloudflarestorage.com\nx-amz-content-sha256:UNSIGNED-PAYLOAD\nx-amz-date:$amzDate\n";

        // Signed headers
        $signedHeaders = 'content-type;host;x-amz-content-sha256;x-amz-date';

        // Create canonical request
        $canonicalRequest = "$httpMethod\n$canonicalUri\n$canonicalQueryString\n$canonicalHeaders\n$signedHeaders\nUNSIGNED-PAYLOAD";

        // Hash canonical request with SHA256
        $canonicalRequestHash = hash('sha256', $canonicalRequest);

        // Create string to sign
        $stringToSign = "AWS4-HMAC-SHA256\n$amzDate\n$dateStamp/{$region}/r2/cloudflarestorage/aws4_request\n$canonicalRequestHash";
        Log::debug($stringToSign);

        // Create signing key
        $kDate = hash_hmac('sha256', $dateStamp, 'AWS4' . $secretKey, true);
        $kRegion = hash_hmac('sha256', $region, $kDate, true);
        $kService = hash_hmac('sha256', 'r2', $kRegion, true);
        $kSigning = hash_hmac('sha256', 'aws4_request', $kService, true);

        // Calculate signature
        $signature = hash_hmac('sha256', $stringToSign, $kSigning);

        // Create Authorization header
        $authorizationHeader = "AWS4-HMAC-SHA256 Credential={$accessKey}/{$dateStamp}/{$region}/r2/cloudflarestorage/aws4_request, SignedHeaders={$signedHeaders}, Signature={$signature}";

        // Return headers
        return array(
            'x-amz-content-sha256: UNSIGNED-PAYLOAD',
            'Content-Type: '. $contentType,
            'x-amz-date: '. $amzDate,
            'Authorization: '. $authorizationHeader
        );
    }

And here is the sample data that I logged:

AMZDate: 20240501T014516Z
DateStamp: 20240501  
String to sign:
AWS4-HMAC-SHA256
20240501T014516Z
20240501/auto/r2/cloudflarestorage/aws4_request
b77289cc08992a8d685b4d6805d95888f9247e7c942d494159e6a479c1108a1f

Even though the data matches, I still get the error message:

“Invalid Argument: Credential signed date auto does not match 20240501 from x-amz-date header”