Request signatures (V4)

Updated at:

The V4 signature mechanism of the Simple Log Service API uses the HMAC-SHA256 algorithm to sign requests, which provides higher security than V1 signatures (HMAC-SHA1). All API requests must be authenticated by using an enabled AccessKey pair.

Input parameters

The V4 signing process requires the following input parameters:

Parameter

Type

Required

Description

method

String

Yes

The HTTP request method, such as GET, POST, PUT, or DELETE.

resource

String

Yes

The requested resource path, such as /logstores.

headers

Map

Yes

A collection of key-value pairs for the HTTP request headers.

body

Bytes

No

The content of the HTTP request body.

accessKeyId

String

Yes

The Alibaba Cloud AccessKey ID.

accessKeySecret

String

Yes

The Alibaba Cloud AccessKey secret.

region

String

Yes

The region ID, such as cn-hangzhou or cn-shanghai.

date

String

Yes

The UTC date in YYYYMMDD format, such as 20240901.

Signed request headers

The following HTTP request headers must be carried in an API request after V4 signing. The header keys are case-sensitive.

Header name

Description

x-log-signaturemethod

The fixed value hmac-sha256, which indicates that the HMAC-SHA256 signature algorithm is used.

x-log-apiversion

The API version number. The fixed value is 0.6.0.

Date

The HTTP request date, in RFC 1123 format and GMT time. For example: Mon, 02 Sep 2024 06:00:00 GMT

x-log-date

The Simple Log Service request timestamp, in ISO 8601 format, accurate to the second, and in UTC time. Format: yyyyMMdd'T'HHmmss'Z'. For example: 20240902T060000Z

Content-Type

The type of the request body. Required when the request has a body. For example: application/x-protobuf

Content-MD5

The MD5 value of the request body, as an uppercase hexadecimal string. Required when the request has a body.

Authorization

The signature authentication information. For the format, see Step 4 of the signing process below.

Signature algorithm workflow

V4 signing consists of four steps: build the canonical request, build the string to sign, calculate the signature, and build the Authorization header.

Canonical request construction

The following figure shows how the components of an HTTP request are processed and concatenated into the canonical request string (Canonical Request):

image.png

Signature calculation

The following figure shows the complete signature calculation process from the canonical request to the final Authorization header, including the four-layer HMAC derivation of the signing key:

image.png

Signing process

Step 1: Build the canonical request (Canonical Request)

Concatenate the key elements of the HTTP request into a single string in the canonical format. This string is called the canonical request (Canonical Request). The concatenation format is as follows:

CanonicalRequest =
  HTTPRequestMethod + '\n' +
  CanonicalURI + '\n' +
  CanonicalQueryString + '\n' +
  CanonicalHeaders + '\n' +
  SignedHeaders + '\n' +
  HashedPayload

The fields are described as follows:

Field

Description

HTTPRequestMethod

The HTTP request method, in uppercase. Such as GET, POST, PUT, or DELETE.

CanonicalURI

The URI-encoded resource path. It starts with /, such as /logstores/my-logstore. The path must be URI-encoded (/ is not encoded).

CanonicalQueryString

The query parameters, sorted by key in lexicographic order. Each parameter is in the format key=value, and multiple parameters are joined with &. Both keys and values must be URI-encoded.

CanonicalHeaders

The HTTP request headers that participate in signing. Convert the header names to lowercase, sort them in lexicographic order, and format each line as key:value\n. Leading and trailing spaces must be removed from the value.

SignedHeaders

The list of header names that participate in signing, all in lowercase, sorted in lexicographic order, and separated by ;. For example: content-type;host;x-log-apiversion;x-log-date

HashedPayload

The SHA-256 hash of the request body, in lowercase hexadecimal. If there is no body, use the SHA-256 value of an empty string: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

About SignedHeaders: The following headers must participate in signing: host, x-log-date, and content-type (when a body exists). Headers that start with x-log- or x-acs- should also be included in the signature.

Step 2: Build the string to sign (String to Sign)

Concatenate the signature algorithm identifier, the timestamp, the signature scope (Scope), and the hash of the canonical request into the string to sign:

StringToSign =
  "SLS4-HMAC-SHA256" + '\n' +
  TimeStamp + '\n' +
  Scope + '\n' +
  Hex(SHA256(CanonicalRequest))

The fields are described as follows:

Field

Description

SLS4-HMAC-SHA256

The fixed signature algorithm identifier string.

TimeStamp

The timestamp, consistent with the x-log-date request header, in the format yyyyMMdd'T'HHmmss'Z', such as 20240902T060000Z.

Scope

The signature scope, in the format YYYYMMDD/region/sls/aliyun_v4_request. For example, 20240902/cn-hangzhou/sls/aliyun_v4_request.

SHA256(CanonicalRequest)

The SHA-256 hash of the CanonicalRequest built in Step 1, in lowercase hexadecimal.

Step 3: Calculate the signature

Derive the signing key (Signing Key) step by step, and then perform HMAC-SHA256 on the string to sign by using this key to obtain the final signature.

Derive the signing key (Signing Key):

SigningKey = HMAC-SHA256(
  HMAC-SHA256(
    HMAC-SHA256(
      HMAC-SHA256("aliyun_v4" + AccessKeySecret, Date),
      Region
    ),
    "sls"
  ),
  "aliyun_v4_request"
)

The derivation is a four-layer nested HMAC:

  1. Use "aliyun_v4" + AccessKeySecret as the initial key and perform HMAC-SHA256 on the date (YYYYMMDD).

  2. Use the result of the previous step as the key and perform HMAC-SHA256 on the region (such as cn-hangzhou).

  3. Use the result of the previous step as the key and perform HMAC-SHA256 on the service name "sls".

  4. Use the result of the previous step as the key and perform HMAC-SHA256 on "aliyun_v4_request".

Calculate the final signature:

Signature = Hex(HMAC-SHA256(SigningKey, StringToSign))

Convert the result of the HMAC-SHA256 operation to a lowercase hexadecimal string to obtain the final signature.

Step 4: Build the Authorization header

Assemble the signature result into the Authorization request header in the following format:

Authorization: SLS4-HMAC-SHA256 Credential={AccessKeyId}/{Scope}, SignedHeaders={SignedHeaders}, Signature={Signature}

The fields are described as follows:

Field

Description

SLS4-HMAC-SHA256

The fixed signature algorithm identifier.

Credential

The format is AccessKeyId/Scope. Scope is the same as in Step 2, such as LTAI5tXxx/20240902/cn-hangzhou/sls/aliyun_v4_request.

SignedHeaders

The list of header names that participate in signing, the same as SignedHeaders in Step 1.

Signature

The signature string calculated in Step 3.

Signature examples

Example 1: GET request (list all Logstores)

HTTP request:

GET /logstores HTTP/1.1
Host: ali-test-project.cn-hangzhou.log.aliyuncs.com
x-log-apiversion: 0.6.0
x-log-signaturemethod: hmac-sha256
x-log-date: 20240902T060000Z
Date: Mon, 02 Sep 2024 06:00:00 GMT

Canonical Request:

GET
/logstores

host:ali-test-project.cn-hangzhou.log.aliyuncs.com
x-log-apiversion:0.6.0
x-log-date:20240902T060000Z
x-log-signaturemethod:hmac-sha256

host;x-log-apiversion;x-log-date;x-log-signaturemethod
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

String to Sign:

SLS4-HMAC-SHA256
20240902T060000Z
20240902/cn-hangzhou/sls/aliyun_v4_request
<Hex(SHA256(CanonicalRequest))>

Authorization header:

SLS4-HMAC-SHA256 Credential=LTAI5tXxx/20240902/cn-hangzhou/sls/aliyun_v4_request, SignedHeaders=host;x-log-apiversion;x-log-date;x-log-signaturemethod, Signature=<calculated signature>

Example 2: POST request (write log data)

HTTP request:

POST /logstores/my-logstore/shards/lb HTTP/1.1
Host: ali-test-project.cn-hangzhou.log.aliyuncs.com
Content-Type: application/x-protobuf
Content-MD5: 1DD45FA557BO4A6899BE4CC3D0B43B20
Content-Length: 208
x-log-apiversion: 0.6.0
x-log-signaturemethod: hmac-sha256
x-log-date: 20240902T060000Z
x-log-compresstype: lz4
x-log-bodyrawsize: 1024
Date: Mon, 02 Sep 2024 06:00:00 GMT

Canonical Request:

POST
/logstores/my-logstore/shards/lb

content-md5:1DD45FA557BO4A6899BE4CC3D0B43B20
content-type:application/x-protobuf
host:ali-test-project.cn-hangzhou.log.aliyuncs.com
x-log-apiversion:0.6.0
x-log-bodyrawsize:1024
x-log-compresstype:lz4
x-log-date:20240902T060000Z
x-log-signaturemethod:hmac-sha256

content-md5;content-type;host;x-log-apiversion;x-log-bodyrawsize;x-log-compresstype;x-log-date;x-log-signaturemethod
<Hex(SHA256(Body))>

Code examples

The following provides reference V4 signing implementations in eight languages: Java, Python, PHP, Go, C#, C++, TypeScript, and Rust. The core process is the same.

Java

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.text.SimpleDateFormat;
import java.util.*;

public class SlsV4Signer {

    private static byte[] hmacSha256(byte[] key, String msg) throws Exception {
        Mac mac = Mac.getInstance("HmacSHA256");
        mac.init(new SecretKeySpec(key, "HmacSHA256"));
        return mac.doFinal(msg.getBytes(StandardCharsets.UTF_8));
    }

    private static String sha256Hex(byte[] data) throws Exception {
        MessageDigest digest = MessageDigest.getInstance("SHA-256");
        byte[] hash = digest.digest(data);
        return bytesToHex(hash);
    }

    private static String bytesToHex(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) {
            sb.append(String.format("%02x", b));
        }
        return sb.toString();
    }

    private static byte[] getSigningKey(String secret, String date,
                                        String region) throws Exception {
        byte[] kDate = hmacSha256(
            ("aliyun_v4" + secret).getBytes(StandardCharsets.UTF_8), date);
        byte[] kRegion = hmacSha256(kDate, region);
        byte[] kService = hmacSha256(kRegion, "sls");
        return hmacSha256(kService, "aliyun_v4_request");
    }

    public static void signRequest(String method, String resource,
            Map<String, String> headers, byte[] body,
            String accessKeyId, String accessKeySecret,
            String region) throws Exception {

        // 1. Prepare time parameters
        SimpleDateFormat rfc1123 = new SimpleDateFormat(
            "EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US);
        rfc1123.setTimeZone(TimeZone.getTimeZone("GMT"));
        SimpleDateFormat iso8601 = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'");
        iso8601.setTimeZone(TimeZone.getTimeZone("UTC"));
        SimpleDateFormat dateOnly = new SimpleDateFormat("yyyyMMdd");
        dateOnly.setTimeZone(TimeZone.getTimeZone("UTC"));

        Date now = new Date();
        String dateStamp = dateOnly.format(now);
        String timestamp = iso8601.format(now);
        String scope = dateStamp + "/" + region + "/sls/aliyun_v4_request";

        // 2. Set required request headers
        headers.put("x-log-date", timestamp);
        headers.put("x-log-signaturemethod", "hmac-sha256");
        headers.put("x-log-apiversion", "0.6.0");
        headers.put("Date", rfc1123.format(now));

        // 3. Build the CanonicalRequest
        TreeMap<String, String> sortedHeaders = new TreeMap<>();
        for (Map.Entry<String, String> e : headers.entrySet()) {
            sortedHeaders.put(e.getKey().toLowerCase(), e.getValue().trim());
        }
        StringBuilder canonicalHeadersSb = new StringBuilder();
        StringBuilder signedHeadersSb = new StringBuilder();
        for (Map.Entry<String, String> e : sortedHeaders.entrySet()) {
            canonicalHeadersSb.append(e.getKey()).append(":")
                .append(e.getValue()).append("\n");
            if (signedHeadersSb.length() > 0) signedHeadersSb.append(";");
            signedHeadersSb.append(e.getKey());
        }
        String signedHeaders = signedHeadersSb.toString();
        String payloadHash = sha256Hex(body != null ? body : new byte[0]);

        String canonicalRequest = String.join("\n",
            method.toUpperCase(), resource, "",
            canonicalHeadersSb.toString(), signedHeaders, payloadHash);

        // 4. Build the StringToSign
        String stringToSign = String.join("\n",
            "SLS4-HMAC-SHA256", timestamp, scope,
            sha256Hex(canonicalRequest.getBytes(StandardCharsets.UTF_8)));

        // 5. Calculate the signature
        byte[] signingKey = getSigningKey(accessKeySecret, dateStamp, region);
        String signature = bytesToHex(hmacSha256(signingKey, stringToSign));

        // 6. Build the Authorization header
        String auth = "SLS4-HMAC-SHA256 Credential=" + accessKeyId + "/" + scope
            + ", SignedHeaders=" + signedHeaders
            + ", Signature=" + signature;
        headers.put("Authorization", auth);
    }
}

Python

import hashlib
import hmac
import datetime


def sha256_hex(data):
    if isinstance(data, str):
        data = data.encode("utf-8")
    return hashlib.sha256(data).hexdigest()


def hmac_sha256(key, msg):
    if isinstance(key, str):
        key = key.encode("utf-8")
    if isinstance(msg, str):
        msg = msg.encode("utf-8")
    return hmac.new(key, msg, hashlib.sha256).digest()


def get_signing_key(secret, date, region, service="sls"):
    k_date = hmac_sha256(("aliyun_v4" + secret), date)
    k_region = hmac_sha256(k_date, region)
    k_service = hmac_sha256(k_region, service)
    return hmac_sha256(k_service, "aliyun_v4_request")


def sign_v4(method, resource, headers, body,
            access_key_id, access_key_secret, region):
    # 1. Prepare time parameters
    now = datetime.datetime.utcnow()
    date_stamp = now.strftime("%Y%m%d")
    timestamp = now.strftime("%Y%m%dT%H%M%SZ")
    scope = f"{date_stamp}/{region}/sls/aliyun_v4_request"

    # 2. Set required request headers
    headers["x-log-date"] = timestamp
    headers["x-log-signaturemethod"] = "hmac-sha256"
    headers["x-log-apiversion"] = "0.6.0"
    headers["Date"] = now.strftime("%a, %d %b %Y %H:%M:%S GMT")

    # 3. Build the CanonicalRequest
    signed_header_keys = sorted(k.lower() for k in headers)
    canonical_headers = "".join(
        f"{k}:{headers[k].strip()}\n" for k in signed_header_keys
    )
    signed_headers = ";".join(signed_header_keys)
    payload_hash = sha256_hex(body if body else b"")

    canonical_request = "\n".join([
        method.upper(), resource, "",
        canonical_headers, signed_headers, payload_hash,
    ])

    # 4. Build the StringToSign
    string_to_sign = "\n".join([
        "SLS4-HMAC-SHA256", timestamp, scope,
        sha256_hex(canonical_request),
    ])

    # 5. Calculate the signature
    signing_key = get_signing_key(access_key_secret, date_stamp, region)
    signature = hmac.new(
        signing_key, string_to_sign.encode("utf-8"), hashlib.sha256
    ).hexdigest()

    # 6. Build the Authorization header
    auth = (
        f"SLS4-HMAC-SHA256 "
        f"Credential={access_key_id}/{scope}, "
        f"SignedHeaders={signed_headers}, "
        f"Signature={signature}"
    )
    headers["Authorization"] = auth
    return headers

PHP

<?php
function sha256Hex($data) {
    return hash('sha256', $data);
}

function hmacSha256($key, $msg) {
    return hash_hmac('sha256', $msg, $key, true);
}

function getSigningKey($secret, $date, $region, $service = 'sls') {
    $kDate    = hmacSha256('aliyun_v4' . $secret, $date);
    $kRegion  = hmacSha256($kDate, $region);
    $kService = hmacSha256($kRegion, $service);
    return hmacSha256($kService, 'aliyun_v4_request');
}

function signV4($method, $resource, &$headers, $body,
                $accessKeyId, $accessKeySecret, $region) {
    // 1. Prepare time parameters
    $now       = gmdate('Ymd\THis\Z');
    $dateStamp = gmdate('Ymd');
    $scope     = "{$dateStamp}/{$region}/sls/aliyun_v4_request";

    // 2. Set required request headers
    $headers['x-log-date']            = $now;
    $headers['x-log-signaturemethod'] = 'hmac-sha256';
    $headers['x-log-apiversion']      = '0.6.0';
    $headers['Date']                  = gmdate('D, d M Y H:i:s') . ' GMT';

    // 3. Build the CanonicalRequest
    $sortedHeaders = [];
    foreach ($headers as $k => $v) {
        $sortedHeaders[strtolower($k)] = trim($v);
    }
    ksort($sortedHeaders);

    $canonicalHeaders = '';
    $signedHeaderKeys = [];
    foreach ($sortedHeaders as $k => $v) {
        $canonicalHeaders .= "{$k}:{$v}\n";
        $signedHeaderKeys[] = $k;
    }
    $signedHeaders = implode(';', $signedHeaderKeys);
    $payloadHash   = sha256Hex($body ?: '');

    $canonicalRequest = implode("\n", [
        strtoupper($method), $resource, '',
        $canonicalHeaders, $signedHeaders, $payloadHash,
    ]);

    // 4. Build the StringToSign
    $stringToSign = implode("\n", [
        'SLS4-HMAC-SHA256', $now, $scope,
        sha256Hex($canonicalRequest),
    ]);

    // 5. Calculate the signature
    $signingKey = getSigningKey($accessKeySecret, $dateStamp, $region);
    $signature  = hash_hmac('sha256', $stringToSign, $signingKey);

    // 6. Build the Authorization header
    $headers['Authorization'] =
        "SLS4-HMAC-SHA256 Credential={$accessKeyId}/{$scope}, "
        . "SignedHeaders={$signedHeaders}, Signature={$signature}";
}
?>

Go

package sls

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"net/http"
	"sort"
	"strings"
	"time"
)

func sha256Hex(data []byte) string {
	h := sha256.Sum256(data)
	return hex.EncodeToString(h[:])
}

func hmacSHA256(key []byte, msg string) []byte {
	mac := hmac.New(sha256.New, key)
	mac.Write([]byte(msg))
	return mac.Sum(nil)
}

func getSigningKey(secret, date, region string) []byte {
	kDate := hmacSHA256([]byte("aliyun_v4"+secret), date)
	kRegion := hmacSHA256(kDate, region)
	kService := hmacSHA256(kRegion, "sls")
	return hmacSHA256(kService, "aliyun_v4_request")
}

func SignV4(method, resource string, header http.Header, body []byte,
	accessKeyID, accessKeySecret, region string) {

	// 1. Prepare time parameters
	now := time.Now().UTC()
	dateStamp := now.Format("20060102")
	timestamp := now.Format("20060102T150405Z")
	scope := fmt.Sprintf("%s/%s/sls/aliyun_v4_request", dateStamp, region)

	// 2. Set required request headers
	header.Set("x-log-date", timestamp)
	header.Set("x-log-signaturemethod", "hmac-sha256")
	header.Set("x-log-apiversion", "0.6.0")
	header.Set("Date", now.Format(http.TimeFormat))

	// 3. Build the CanonicalRequest
	var keys []string
	for k := range header {
		keys = append(keys, strings.ToLower(k))
	}
	sort.Strings(keys)

	var canonicalHeaders, signedHeadersList []string
	for _, k := range keys {
		v := strings.TrimSpace(header.Get(k))
		canonicalHeaders = append(canonicalHeaders, k+":"+v)
		signedHeadersList = append(signedHeadersList, k)
	}
	canonicalHeaderStr := strings.Join(canonicalHeaders, "\n") + "\n"
	signedHeaders := strings.Join(signedHeadersList, ";")

	payloadHash := sha256Hex(body)

	canonicalRequest := strings.Join([]string{
		strings.ToUpper(method), resource, "",
		canonicalHeaderStr, signedHeaders, payloadHash,
	}, "\n")

	// 4. Build the StringToSign
	stringToSign := strings.Join([]string{
		"SLS4-HMAC-SHA256", timestamp, scope,
		sha256Hex([]byte(canonicalRequest)),
	}, "\n")

	// 5. Calculate the signature
	signingKey := getSigningKey(accessKeySecret, dateStamp, region)
	signature := hex.EncodeToString(hmacSHA256(signingKey, stringToSign))

	// 6. Build the Authorization header
	auth := fmt.Sprintf(
		"SLS4-HMAC-SHA256 Credential=%s/%s, SignedHeaders=%s, Signature=%s",
		accessKeyID, scope, signedHeaders, signature)
	header.Set("Authorization", auth)
}

C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;

public class SlsV4Signer
{
    private static byte[] HmacSha256(byte[] key, string msg)
    {
        using var hmac = new HMACSHA256(key);
        return hmac.ComputeHash(Encoding.UTF8.GetBytes(msg));
    }

    private static string Sha256Hex(byte[] data)
    {
        using var sha = SHA256.Create();
        var hash = sha.ComputeHash(data);
        return BitConverter.ToString(hash).Replace("-", "").ToLower();
    }

    private static byte[] GetSigningKey(string secret, string date,
                                        string region)
    {
        var kDate    = HmacSha256(
            Encoding.UTF8.GetBytes("aliyun_v4" + secret), date);
        var kRegion  = HmacSha256(kDate, region);
        var kService = HmacSha256(kRegion, "sls");
        return HmacSha256(kService, "aliyun_v4_request");
    }

    public static void Sign(string method, string resource,
        Dictionary<string, string> headers, byte[] body,
        string accessKeyId, string accessKeySecret, string region)
    {
        // 1. Prepare time parameters
        var now       = DateTime.UtcNow;
        var dateStamp = now.ToString("yyyyMMdd");
        var timestamp = now.ToString("yyyyMMdd'T'HHmmss'Z'");
        var scope     = $"{dateStamp}/{region}/sls/aliyun_v4_request";

        // 2. Set required request headers
        headers["x-log-date"]            = timestamp;
        headers["x-log-signaturemethod"] = "hmac-sha256";
        headers["x-log-apiversion"]      = "0.6.0";
        headers["Date"] = now.ToString("R");

        // 3. Build the CanonicalRequest
        var sorted = headers
            .OrderBy(h => h.Key.ToLower())
            .Select(h => new { Key = h.Key.ToLower(), Val = h.Value.Trim() })
            .ToList();

        var canonicalHeaders = string.Join("",
            sorted.Select(h => $"{h.Key}:{h.Val}\n"));
        var signedHeaders = string.Join(";", sorted.Select(h => h.Key));
        var payloadHash = Sha256Hex(body ?? Array.Empty<byte>());

        var canonicalRequest = string.Join("\n",
            method.ToUpper(), resource, "",
            canonicalHeaders, signedHeaders, payloadHash);

        // 4. Build the StringToSign
        var stringToSign = string.Join("\n",
            "SLS4-HMAC-SHA256", timestamp, scope,
            Sha256Hex(Encoding.UTF8.GetBytes(canonicalRequest)));

        // 5. Calculate the signature
        var signingKey = GetSigningKey(accessKeySecret, dateStamp, region);
        var sigBytes   = HmacSha256(signingKey, stringToSign);
        var signature  = BitConverter.ToString(sigBytes)
            .Replace("-", "").ToLower();

        // 6. Build the Authorization header
        headers["Authorization"] =
            $"SLS4-HMAC-SHA256 Credential={accessKeyId}/{scope}, "
            + $"SignedHeaders={signedHeaders}, Signature={signature}";
    }
}

C++

#include <openssl/hmac.h>
#include <openssl/sha.h>
#include <algorithm>
#include <ctime>
#include <iomanip>
#include <map>
#include <sstream>
#include <string>
#include <vector>

static std::string toHex(const unsigned char* data, size_t len) {
    std::ostringstream oss;
    for (size_t i = 0; i < len; ++i)
        oss << std::hex << std::setfill('0') << std::setw(2) << (int)data[i];
    return oss.str();
}

static std::string sha256Hex(const std::string& data) {
    unsigned char hash[SHA256_DIGEST_LENGTH];
    SHA256(reinterpret_cast<const unsigned char*>(data.c_str()),
           data.size(), hash);
    return toHex(hash, SHA256_DIGEST_LENGTH);
}

static std::vector<unsigned char> hmacSha256(
        const std::vector<unsigned char>& key, const std::string& msg) {
    unsigned char out[EVP_MAX_MD_SIZE];
    unsigned int outLen = 0;
    HMAC(EVP_sha256(), key.data(), key.size(),
         reinterpret_cast<const unsigned char*>(msg.c_str()),
         msg.size(), out, &outLen);
    return std::vector<unsigned char>(out, out + outLen);
}

static std::vector<unsigned char> hmacSha256(
        const std::string& key, const std::string& msg) {
    return hmacSha256(
        std::vector<unsigned char>(key.begin(), key.end()), msg);
}

static std::vector<unsigned char> getSigningKey(
        const std::string& secret,
        const std::string& date, const std::string& region) {
    auto kDate    = hmacSha256("aliyun_v4" + secret, date);
    auto kRegion  = hmacSha256(kDate, region);
    auto kService = hmacSha256(kRegion, "sls");
    return hmacSha256(kService, "aliyun_v4_request");
}

void signV4(const std::string& method, const std::string& resource,
            std::map<std::string, std::string>& headers,
            const std::string& body,
            const std::string& accessKeyId,
            const std::string& accessKeySecret,
            const std::string& region) {

    // 1. Prepare time parameters
    std::time_t now = std::time(nullptr);
    std::tm* gmt = std::gmtime(&now);
    char dateStamp[16], timestamp[32], rfc1123[64];
    std::strftime(dateStamp, sizeof(dateStamp), "%Y%m%d", gmt);
    std::strftime(timestamp, sizeof(timestamp), "%Y%m%dT%H%M%SZ", gmt);
    std::strftime(rfc1123, sizeof(rfc1123), "%a, %d %b %Y %H:%M:%S GMT", gmt);
    std::string scope = std::string(dateStamp) + "/" + region
                        + "/sls/aliyun_v4_request";

    // 2. Set required request headers
    headers["x-log-date"]            = timestamp;
    headers["x-log-signaturemethod"] = "hmac-sha256";
    headers["x-log-apiversion"]      = "0.6.0";
    headers["Date"]                  = rfc1123;

    // 3. Build the CanonicalRequest
    std::map<std::string, std::string> sorted;
    for (auto& h : headers) {
        std::string k = h.first;
        std::transform(k.begin(), k.end(), k.begin(), ::tolower);
        sorted[k] = h.second;
    }
    std::string canonicalHeaders, signedHeaders;
    for (auto& h : sorted) {
        canonicalHeaders += h.first + ":" + h.second + "\n";
        if (!signedHeaders.empty()) signedHeaders += ";";
        signedHeaders += h.first;
    }
    std::string payloadHash = sha256Hex(body);

    std::string canonicalRequest =
        method + "\n" + resource + "\n\n"
        + canonicalHeaders + "\n" + signedHeaders + "\n" + payloadHash;

    // 4. Build the StringToSign
    std::string stringToSign =
        "SLS4-HMAC-SHA256\n" + std::string(timestamp) + "\n"
        + scope + "\n" + sha256Hex(canonicalRequest);

    // 5. Calculate the signature
    auto signingKey = getSigningKey(accessKeySecret, dateStamp, region);
    auto sig = hmacSha256(signingKey, stringToSign);
    std::string signature = toHex(sig.data(), sig.size());

    // 6. Build the Authorization header
    headers["Authorization"] =
        "SLS4-HMAC-SHA256 Credential=" + accessKeyId + "/" + scope
        + ", SignedHeaders=" + signedHeaders
        + ", Signature=" + signature;
}

TypeScript

import * as crypto from "crypto";

function sha256Hex(data: string | Buffer): string {
  return crypto.createHash("sha256").update(data).digest("hex");
}

function hmacSha256(key: string | Buffer, msg: string): Buffer {
  return crypto.createHmac("sha256", key).update(msg).digest();
}

function getSigningKey(
  secret: string, date: string, region: string
): Buffer {
  const kDate = hmacSha256("aliyun_v4" + secret, date);
  const kRegion = hmacSha256(kDate, region);
  const kService = hmacSha256(kRegion, "sls");
  return hmacSha256(kService, "aliyun_v4_request");
}

export function signV4(
  method: string,
  resource: string,
  headers: Record<string, string>,
  body: Buffer | null,
  accessKeyId: string,
  accessKeySecret: string,
  region: string
): void {
  // 1. Prepare time parameters
  const now = new Date();
  const dateStamp = now.toISOString().slice(0, 10).replace(/-/g, "");
  const timestamp = dateStamp + "T" +
    now.toISOString().slice(11, 19).replace(/:/g, "") + "Z";
  const scope = `${dateStamp}/${region}/sls/aliyun_v4_request`;

  // 2. Set required request headers
  headers["x-log-date"] = timestamp;
  headers["x-log-signaturemethod"] = "hmac-sha256";
  headers["x-log-apiversion"] = "0.6.0";
  headers["Date"] = now.toUTCString();

  // 3. Build the CanonicalRequest
  const sortedKeys = Object.keys(headers)
    .map((k) => k.toLowerCase())
    .sort();
  const canonicalHeaders = sortedKeys
    .map((k) => `${k}:${headers[k]?.trim() ?? ""}`)
    .join("\n") + "\n";
  const signedHeaders = sortedKeys.join(";");
  const payloadHash = sha256Hex(body ?? Buffer.alloc(0));

  const canonicalRequest = [
    method.toUpperCase(), resource, "",
    canonicalHeaders, signedHeaders, payloadHash,
  ].join("\n");

  // 4. Build the StringToSign
  const stringToSign = [
    "SLS4-HMAC-SHA256", timestamp, scope,
    sha256Hex(canonicalRequest),
  ].join("\n");

  // 5. Calculate the signature
  const signingKey = getSigningKey(accessKeySecret, dateStamp, region);
  const signature = crypto
    .createHmac("sha256", signingKey)
    .update(stringToSign)
    .digest("hex");

  // 6. Build the Authorization header
  headers["Authorization"] =
    `SLS4-HMAC-SHA256 Credential=${accessKeyId}/${scope}, ` +
    `SignedHeaders=${signedHeaders}, Signature=${signature}`;
}

Rust

use chrono::Utc;
use hmac::{Hmac, Mac};
use sha2::{Digest, Sha256};
use std::collections::BTreeMap;

type HmacSha256 = Hmac<Sha256>;

fn sha256_hex(data: &[u8]) -> String {
    let mut hasher = Sha256::new();
    hasher.update(data);
    hex::encode(hasher.finalize())
}

fn hmac_sha256(key: &[u8], msg: &str) -> Vec<u8> {
    let mut mac = HmacSha256::new_from_slice(key)
        .expect("HMAC key error");
    mac.update(msg.as_bytes());
    mac.finalize().into_bytes().to_vec()
}

fn get_signing_key(secret: &str, date: &str, region: &str) -> Vec<u8> {
    let initial = format!("aliyun_v4{}", secret);
    let k_date = hmac_sha256(initial.as_bytes(), date);
    let k_region = hmac_sha256(&k_date, region);
    let k_service = hmac_sha256(&k_region, "sls");
    hmac_sha256(&k_service, "aliyun_v4_request")
}

pub fn sign_v4(
    method: &str,
    resource: &str,
    headers: &mut BTreeMap<String, String>,
    body: &[u8],
    access_key_id: &str,
    access_key_secret: &str,
    region: &str,
) {
    // 1. Prepare time parameters
    let now = Utc::now();
    let date_stamp = now.format("%Y%m%d").to_string();
    let timestamp = now.format("%Y%m%dT%H%M%SZ").to_string();
    let scope = format!("{}/{}/sls/aliyun_v4_request", date_stamp, region);

    // 2. Set required request headers
    headers.insert("x-log-date".into(), timestamp.clone());
    headers.insert("x-log-signaturemethod".into(), "hmac-sha256".into());
    headers.insert("x-log-apiversion".into(), "0.6.0".into());
    headers.insert("Date".into(),
        now.format("%a, %d %b %Y %H:%M:%S GMT").to_string());

    // 3. Build the CanonicalRequest
    let sorted: BTreeMap<String, String> = headers
        .iter()
        .map(|(k, v)| (k.to_lowercase(), v.trim().to_string()))
        .collect();

    let canonical_headers: String = sorted
        .iter()
        .map(|(k, v)| format!("{}:{}\n", k, v))
        .collect();
    let signed_headers: String = sorted.keys()
        .cloned()
        .collect::<Vec<_>>()
        .join(";");
    let payload_hash = sha256_hex(body);

    let canonical_request = format!(
        "{}\n{}\n\n{}\n{}\n{}",
        method.to_uppercase(), resource,
        canonical_headers, signed_headers, payload_hash
    );

    // 4. Build the StringToSign
    let string_to_sign = format!(
        "SLS4-HMAC-SHA256\n{}\n{}\n{}",
        timestamp, scope, sha256_hex(canonical_request.as_bytes())
    );

    // 5. Calculate the signature
    let signing_key = get_signing_key(access_key_secret, &date_stamp, region);
    let signature = hex::encode(
        hmac_sha256(&signing_key, &string_to_sign));

    // 6. Build the Authorization header
    headers.insert("Authorization".into(), format!(
        "SLS4-HMAC-SHA256 Credential={}/{}, SignedHeaders={}, Signature={}",
        access_key_id, scope, signed_headers, signature
    ));
}

Comparison between V1 and V4 signatures

The following are the main differences between V1 (default signature) and V4 signatures:

Item

V1 signature (default)

V4 signature

Signature algorithm

HMAC-SHA1

HMAC-SHA256

Algorithm identifier

LOG

SLS4-HMAC-SHA256

Authorization format

LOG AccessKeyId:Signature

SLS4-HMAC-SHA256 Credential=.../Scope, SignedHeaders=..., Signature=...

Signing key

Uses the AccessKeySecret directly

Derives the signing key step by step through four-layer HMAC

Signature scope

No Scope concept

Includes date/region/sls/aliyun_v4_request

Timestamp header

Date only

Date + x-log-date (ISO 8601)

Payload hash

Not included

Includes SHA-256(Body) in the canonical request

Security

Standard

Higher; supports key isolation at the region and service dimensions

Notes

Time synchronization: The deviation between the timestamp in the request and the server time cannot exceed 15 minutes; otherwise, the server rejects the request. We recommend that you use NTP to synchronize the local time.

Encoding rules: Special characters in the URI and query parameters must be URI-encoded (RFC 3986). Note that / in the CanonicalURI does not need to be encoded.

Header handling: Header names must be converted to lowercase during signing. Leading and trailing spaces must be removed from header values, and consecutive spaces must be merged into a single space.

STS token support: When you use STS temporary credentials, you must add the x-acs-security-token field to the request headers and include it in the signature calculation.