Request signatures

Updated at:

ApsaraMQ for RocketMQ authenticates every HTTP API request by verifying a signature in the Authorization header. The following sections explain how to build a valid signature and attach it to your requests.

How authentication works

ApsaraMQ for RocketMQ uses symmetric encryption to verify the identity of each request sender:

  1. You build a signature from the request content, using your AccessKey secret as the signing key.

  2. You attach the signature to the Authorization header of the HTTP request.

  3. The server computes the expected signature using the same algorithm and your AccessKey secret.

  4. If the two signatures match, the request is authenticated. Otherwise, the server rejects the request with HTTP 403.

Prerequisites

Before you begin, make sure that you have:

Apsara Stack issues an AccessKey pair to each user. You can apply for and manage AccessKey pairs in the Apsara Uni-manager Management Console. An AccessKey pair consists of two parts:

ComponentPurpose
AccessKey IDIdentifies who is making the request
AccessKey secretSigns the request to prove your identity. Keep this value strictly confidential

Authorization header format

Set the Authorization header in the following format:

MQ <AccessKey ID>:<Signature>

Example with placeholder values:

MQ LTAI5tExampleKey:dGhpcyBpcyBhbiBleGFtcGxlIHNpZw==

Build the signature

Building a valid signature involves three steps: assembling the string-to-sign, computing the HMAC-SHA1 hash, and Base64-encoding the result.

Step 1: Assemble the string-to-sign

Concatenate the following fields, separated by newline characters (\n), into a single UTF-8 string:

HTTP_METHOD + "\n"
+ "\n"
+ CONTENT-TYPE + "\n"
+ DATE + "\n"
+ "x-mq-version:" + MQVersion + "\n"
+ CanonicalizedResource

The following table describes each field:

FieldDescriptionValue or format
HTTP_METHODThe HTTP method, in uppercasePUT, GET, POST, or DELETE
CONTENT-TYPEThe media type of the request bodytext/xml; charset=utf-8
DATEThe request timestamp in RFC 2616 format (UTC)Example: Thu, 07 Mar 2012 18:49:58 GMT
MQVersionThe ApsaraMQ for RocketMQ API version2015-06-06
CanonicalizedResourceThe URI of the resource being accessed, including query parametersExample: /topics/abc/messages?consumer=GID_abc
Note

The second line in the string-to-sign is intentionally empty. Include the newline character but no content.

Example string-to-sign for a consume request:

POST

text/xml; charset=utf-8
Thu, 07 Mar 2012 18:49:58 GMT
x-mq-version:2015-06-06
/topics/abc/messages?consumer=GID_abc

Step 2: Compute the HMAC-SHA1 hash

Apply the HMAC-SHA1 algorithm (RFC 2104) to the string-to-sign, using your AccessKey secret as the signing key.

Step 3: Base64-encode the result

Base64-encode the binary HMAC-SHA1 output to produce the final signature string.

The complete formula:

Signature = base64(hmac-sha1(AccessKeySecret, StringToSign))

Function reference

FunctionDescription
hmac-sha1(key, message)Computes an HMAC digest using the SHA-1 hash algorithm, as defined in RFC 2104. The key is your AccessKey secret
base64(data)Encodes binary data into a Base64 string

Complete example

This example signs a message consumption request with the following parameters:

ParameterValue
HTTP methodPOST
Content-Typetext/xml; charset=utf-8
DateThu, 07 Mar 2012 18:49:58 GMT
API version2015-06-06
Resource URI/topics/abc/messages?consumer=GID_abc
AccessKey IDLTAI5tExampleKey
AccessKey secretxXxExampleSecretxXx

Step 1 -- Assemble the string-to-sign:

POST

text/xml; charset=utf-8
Thu, 07 Mar 2012 18:49:58 GMT
x-mq-version:2015-06-06
/topics/abc/messages?consumer=GID_abc
Note

The blank line after POST is the intentionally empty second line.

Step 2 and 3 -- Compute HMAC-SHA1 and Base64-encode:

Signature = base64(hmac-sha1("xXxExampleSecretxXx", StringToSign))

Resulting Authorization header:

Authorization: MQ LTAI5tExampleKey:<computed-signature>

Replace <computed-signature> with the Base64-encoded HMAC-SHA1 output.

Code examples

The following examples show how to generate the signature in common programming languages. Each example builds the string-to-sign, computes the HMAC-SHA1 hash, and Base64-encodes the result.

Python

import hmac
import hashlib
import base64
import os
from email.utils import formatdate

# Obtain credentials from environment variables.
access_key_id = os.environ["MQ_ACCESS_KEY_ID"]
access_key_secret = os.environ["MQ_ACCESS_KEY_SECRET"]

# Request parameters
http_method = "POST"
content_type = "text/xml; charset=utf-8"
date = formatdate(usegmt=True)  # RFC 2616 format, e.g. "Thu, 07 Mar 2012 18:49:58 GMT"
mq_version = "2015-06-06"
canonicalized_resource = "/topics/abc/messages?consumer=GID_abc"

# Step 1: Assemble the string-to-sign.
string_to_sign = (
    http_method + "\n"
    + "\n"
    + content_type + "\n"
    + date + "\n"
    + "x-mq-version:" + mq_version + "\n"
    + canonicalized_resource
)

# Step 2-3: Compute HMAC-SHA1 and Base64-encode.
signature = base64.b64encode(
    hmac.new(
        access_key_secret.encode("utf-8"),
        string_to_sign.encode("utf-8"),
        hashlib.sha1,
    ).digest()
).decode("utf-8")

# Build the Authorization header.
authorization = f"MQ {access_key_id}:{signature}"
print(authorization)

Java

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Base64;
import java.util.Locale;

public class MqSignature {
    public static void main(String[] args) throws Exception {
        // Obtain credentials from environment variables.
        String accessKeyId = System.getenv("MQ_ACCESS_KEY_ID");
        String accessKeySecret = System.getenv("MQ_ACCESS_KEY_SECRET");

        // Request parameters
        String httpMethod = "POST";
        String contentType = "text/xml; charset=utf-8";
        String date = DateTimeFormatter
                .ofPattern("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US)
                .format(ZonedDateTime.now(ZoneOffset.UTC));
        String mqVersion = "2015-06-06";
        String canonicalizedResource = "/topics/abc/messages?consumer=GID_abc";

        // Step 1: Assemble the string-to-sign.
        String stringToSign = httpMethod + "\n"
                + "\n"
                + contentType + "\n"
                + date + "\n"
                + "x-mq-version:" + mqVersion + "\n"
                + canonicalizedResource;

        // Step 2-3: Compute HMAC-SHA1 and Base64-encode.
        Mac mac = Mac.getInstance("HmacSHA1");
        mac.init(new SecretKeySpec(
                accessKeySecret.getBytes(StandardCharsets.UTF_8), "HmacSHA1"));
        byte[] rawSignature = mac.doFinal(
                stringToSign.getBytes(StandardCharsets.UTF_8));
        String signature = Base64.getEncoder().encodeToString(rawSignature);

        // Build the Authorization header.
        String authorization = "MQ " + accessKeyId + ":" + signature;
        System.out.println(authorization);
    }
}

Go

package main

import (
	"crypto/hmac"
	"crypto/sha1"
	"encoding/base64"
	"fmt"
	"os"
	"time"
)

func main() {
	// Obtain credentials from environment variables.
	accessKeyID := os.Getenv("MQ_ACCESS_KEY_ID")
	accessKeySecret := os.Getenv("MQ_ACCESS_KEY_SECRET")

	// Request parameters
	httpMethod := "POST"
	contentType := "text/xml; charset=utf-8"
	date := time.Now().UTC().Format("Mon, 02 Jan 2006 15:04:05 GMT")
	mqVersion := "2015-06-06"
	canonicalizedResource := "/topics/abc/messages?consumer=GID_abc"

	// Step 1: Assemble the string-to-sign.
	stringToSign := httpMethod + "\n" +
		"\n" +
		contentType + "\n" +
		date + "\n" +
		"x-mq-version:" + mqVersion + "\n" +
		canonicalizedResource

	// Step 2-3: Compute HMAC-SHA1 and Base64-encode.
	mac := hmac.New(sha1.New, []byte(accessKeySecret))
	mac.Write([]byte(stringToSign))
	signature := base64.StdEncoding.EncodeToString(mac.Sum(nil))

	// Build the Authorization header.
	authorization := fmt.Sprintf("MQ %s:%s", accessKeyID, signature)
	fmt.Println(authorization)
}

Node.js

const crypto = require("crypto");

// Obtain credentials from environment variables.
const accessKeyId = process.env.MQ_ACCESS_KEY_ID;
const accessKeySecret = process.env.MQ_ACCESS_KEY_SECRET;

// Request parameters
const httpMethod = "POST";
const contentType = "text/xml; charset=utf-8";
const date = new Date().toUTCString(); // RFC 2616 format
const mqVersion = "2015-06-06";
const canonicalizedResource = "/topics/abc/messages?consumer=GID_abc";

// Step 1: Assemble the string-to-sign.
const stringToSign = [
  httpMethod,
  "",
  contentType,
  date,
  `x-mq-version:${mqVersion}`,
  canonicalizedResource,
].join("\n");

// Step 2-3: Compute HMAC-SHA1 and Base64-encode.
const signature = crypto
  .createHmac("sha1", accessKeySecret)
  .update(stringToSign, "utf-8")
  .digest("base64");

// Build the Authorization header.
const authorization = `MQ ${accessKeyId}:${signature}`;
console.log(authorization);

Troubleshooting

The following table lists common signature errors and their solutions:

ErrorCauseSolution
HTTP 403: Signature mismatchThe string-to-sign does not match the server's expectationPrint your string-to-sign and verify each field. Check that the empty second line is present and that no extra whitespace is appended
HTTP 403: Date offset too largeThe DATE header value differs too much from the server clockSynchronize your system clock with an NTP server and use UTC
HTTP 403: Invalid AccessKey IDThe AccessKey ID does not exist or is disabledVerify that your AccessKey ID is correct and active in the console
Signature differs across runsThe DATE header changes per request, producing a different signature each timeThis is expected behavior. Generate a new signature for each request
Encoding errorsThe string-to-sign is not encoded as UTF-8Make sure all string operations use UTF-8 encoding

Constraints

  • The string-to-sign must use UTF-8 encoding.

  • The DATE header value must be in UTC. Use the RFC 2616 date format: Day, DD Mon YYYY HH:MM:SS GMT.

  • The Authorization header must be included in every HTTP request.