You must sign all API requests to ensure data security. Alibaba Cloud uses the request signature to verify the identity of the API caller. Therefore, each API request must contain signature information, regardless of whether the request is sent over HTTP or HTTPS.

Sign a request

To sign API requests, you must log on to the User Management console to obtain an AccessKey pair and calculate the message authentication code (MAC). An AccessKey pair consists of an AccessKey ID and an AccessKey secret. The AccessKey ID is used to verify the identity of the API caller, whereas the AccessKey secret is used to encrypt and verify the signature string. You must keep your AccessKey secret strictly confidential.

Note IoT Platform provides SDKs for multiple programming languages, such as Java, Python, and PHP. If you use the SDKs to call API operations, you can skip the signing process. For more information about how to use the SDKs, see IoT Platform SDK (Original) and IoT Platform SDK (Upgraded).

To sign a request, perform the following steps:

  1. Create a canonicalized query string by arranging the request parameters.
    1. Sort the request parameters.

      Sort all request parameters in alphabetic order. The request parameters include all common and operation-specific parameters except the Signature parameter.

      Note If you use the GET method to submit a request, the parameters that follow the question mark (?) and are connected by ampersands (&) in the request URL are request parameters.
    2. Encode the canonicalized query string.

      Use UTF-8 to encode the names and values of the request parameters based on RFC 3986. The encoding format must comply with the following rules:

      • Letters, digits, hyphens (-), underscores (_), periods (.), and tildes (~) do not need to be encoded.
      • Other characters must be percent encoded in the %XY format. XY represents the ASCII code of the characters in hexadecimal notation. For example, double quotation marks (") are encoded as %22.
      • Extended UTF-8 characters are encoded in the %XY%ZA… format.
      • Spaces must be encoded as %20. Do not encode spaces as plus signs (+).

      The preceding encoding method is similar to but slightly different from the application/x-www-form-urlencoded Multipurpose Internet Mail Extensions (MIME) encoding algorithm.

      If you use java.net.URLEncoder from the Java standard library, use percentEncode to encode request parameters and their values. In the encoded query string, replace the plus signs (+) with %20, asterisks (*) with %2A, and %7E with tildes (~). This way, you can obtain an encoded query string that matches the preceding encoding rules.

      private static final String ENCODING = "UTF-8";
      private static String percentEncode(String value) throws UnsupportedEncodingException {
      return value != null ? URLEncoder.encode(value, ENCODING).replace("+", "%20").replace("*", "%2A").replace("%7E", "~") : null;
      }
    3. Connect the encoded request parameter names and values by using equal signs (=).
    4. Sort the encoded request parameters by parameter name in alphabetic order, and use ampersands (&) to connect the parameters.

    This way, a canonicalized query string (CanonicalizedQueryString) is created.

  2. Create a string-to-sign.

    Create a string-to-sign from the encoded canonicalized query string by using the percentEncode method. The following sample code shows how to create a string-to-sign:

    StringToSign=
      HTTPMethod + "&" +                      
      percentEncode("/") + "&" +              
      percentEncode(CanonicalizedQueryString)
    Parameters:
    • HTTPMethod: the HTTP method that is used to send the request, such as GET.
    • percentEncode(“/“): use UTF-8 to encode backslashes (/) as %2F.
    • percentEncode(CanonicalizedQueryString) // Encode the canonicalized query string that is created in the previous step.
  3. Calculate the hash-based message authentication code (HMAC) of the string-to-sign.

    Calculate the HMAC of the string-to-sign based on RFC 2104.

    HMAC-SHA1( AccessSecret, UTF-8-Encoding-Of(StringToSign) )
    Important Use the Secure Hash Algorithm 1 (SHA-1) algorithm to calculate the HMAC of the string-to-sign. The AccessKey secret to which an ampersand (&) (ASCII:38) is appended is used as the key for HMAC calculation.
  4. Calculate the signature string.

    Encode the HMAC in Base64 to obtain the signature string.

    Signature = Base64( HMAC-SHA1( AccessSecret, UTF-8-Encoding-Of(StringToSign) ) )
  5. Add the signature string.

    Add the signature string to the request as the Signature parameter and perform URL encoding based on RFC 3986.

Example

The following example shows how to sign a request. In this example, the Pub operation of IoT Platform is called. The following request parameters are configured in the request URL: AccessKeyId=testid, AccessKeySecret=testsecret, ProductKey=12345abcde, TopicFullName=/12345abcde/testdevice/user/get, MessageContent=aGVsbG8gd29ybGQ, and Qos=0.

  1. Create and encode a canonicalized query string.
    http://iot.cn-shanghai.aliyuncs.com/?Action=Pub&MessageContent=aGVsbG8gd29ybGQ&Timestamp=2018-07-31T07:43:57Z&SignatureVersion=1.0&Format=XML&Qos=0&SignatureNonce=3ee8c1b8-83d3-44af-a94f-4e0ad82fd6cf&Version=2018-01-20&AccessKeyId=testid&SignatureMethod=HMAC-SHA1&RegionId=cn-shanghai&ProductKey=12345abcde&TopicFullName=/12345abcde/testdevice/user/get
  2. Create a string-to-sign from the encoded canonicalized query string.
    GET&%2F&AccessKeyId%3Dtestid%26Action%3DPub%26Format%3DXML%26MessageContent%3DaGVsbG8gd29ybGQ%26ProductKey%3D12345abcde%26Qos%3D0%26RegionId%3Dcn-shanghai%26SignatureMethod%3DHMAC-SHA1%26SignatureNonce%3D3ee8c1b8-83d3-44af-a94f-4e0ad82fd6cf%26SignatureVersion%3D1.0%26Timestamp%3D2018-07-31T07%253A43%253A57Z%26TopicFullName%3D%252F12345abcde%252Ftestdevice%252Fuser%252Fget%26Version%3D2018-01-20
  3. Calculate the signature string.

    In this example, the AccessKey secret is testsecret. The key that is used for HMAC calculation is testsecret&. The following code shows the calculated signature string:

    NUh3otvAoXOZmG/a2gDShh6Ze9w=
  4. Add the signature string to the request as the Signature parameter. The following code shows the URL of the signed request:
    http://iot.cn-shanghai.aliyuncs.com/?MessageContent=aGVsbG8gd29ybGQ&Action=Pub&Timestamp=2018-07-31T07%253A43%253A57Z&SignatureVersion=1.0&Format=XML&Qos=0&SignatureNonce=3ee8c1b8-83d3-44af-a94f-4e0ad82fd6cf&Version=2018-01-20&AccessKeyId=testid&Signature=NUh3otvAoXOZmG%2Fa2gDShh6Ze9w%3D&SignatureMethod=HMAC-SHA1&RegionId=cn-shanghai&ProductKey=12345abcde&TopicFullName=%2F12345abcde%2Ftestdevice%2Fuser%2Fget

Sample code in Java

The following example shows how to obtain a signature by using Java:

  1. Modify the Config.java file.
    /*   
     * Copyright © 2018 Alibaba. All rights reserved.
     */
    package com.aliyun.iot.demo.sign;
    
    /**
     * The signature configurations for the server API.
     * 
     * @author: ali
     * @version: 0.1 2018-08-08 08:23:54
     */
    public class Config {
    
        // Your AccessKey pair.
        public static String accessKey = "123456******3456";
        public static String accessKeySecret = "123456******34567******4567890";
    
        public final static String CHARSET_UTF8 = "utf8";
    }
    ParameterExampleDescription
    accessKey 123456******3456

    Log on to the IoT Platform console, move the pointer over the profile picture, and then click AccessKey Management to obtain the AccessKey ID and AccessKey secret.

    Note If you use a RAM user, you must attach the AliyunIOTFullAccess permission policy to the user. This policy allows the user to manage IoT Platform resources. Otherwise, the connection with IoT Platform fails. For more information about how to grant permissions to a RAM user, see RAM user access.
    accessKeySecret 123456******34567******4567890
  2. Modify the UrlUtil.java file.
    /*   
     * Copyright © 2018 Alibaba. All rights reserved.
     */
    package com.aliyun.iot.demo.sign;
    
    import java.net.URLEncoder;
    import java.util.Map;
    
    import org.apache.commons.lang3.StringUtils;
    
    /**
     * The URL processing class.
     * 
     * @author: ali
     * @version: 0.1 2018-06-21 20:40:52
     */
    public class UrlUtil {
    
        private final static String CHARSET_UTF8 = "utf8";
    
        public static String urlEncode(String url) {
            if (!StringUtils.isEmpty(url)) {
                try {
                    url = URLEncoder.encode(url, "UTF-8");
                } catch (Exception e) {
                    System.out.println("Url encode error:" + e.getMessage());
                }
            }
            return url;
        }
    
        public static String generateQueryString(Map<String, String> params, boolean isEncodeKV) {
            StringBuilder canonicalizedQueryString = new StringBuilder();
            for (Map.Entry<String, String> entry : params.entrySet()) {
                if (isEncodeKV)
                    canonicalizedQueryString.append(percentEncode(entry.getKey())).append("=")
                            .append(percentEncode(entry.getValue())).append("&");
                else
                    canonicalizedQueryString.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
            }
            if (canonicalizedQueryString.length() > 1) {
                canonicalizedQueryString.setLength(canonicalizedQueryString.length() - 1);
            }
            return canonicalizedQueryString.toString();
        }
    
        public static String percentEncode(String value) {
            try {
                // After you use URLEncoder.encode for encoding, replace plus signs (+), asterisks (*), and %7E with values that conform to the encoding standard of the API. 
                return value == null ? null
                        : URLEncoder.encode(value, CHARSET_UTF8).replace("+", "%20").replace("*", "%2A").replace("%7E",
                                "~");
            } catch (Exception e) {
    
            }
            return "";
        }
    }
  3. Modify the SignatureUtils.java file.
    /*   
     * Copyright © 2018 Alibaba. All rights reserved.
     */
    package com.aliyun.iot.demo.sign;
    
    import java.io.IOException;
    import java.io.UnsupportedEncodingException;
    import java.net.URI;
    import java.net.URISyntaxException;
    import java.net.URLDecoder;
    import java.net.URLEncoder;
    import java.util.Map;
    import java.util.TreeMap;
    
    import javax.crypto.Mac;
    import javax.crypto.spec.SecretKeySpec;
    
    import org.apache.commons.codec.binary.Base64;
    import org.apache.commons.lang3.StringUtils;
    
    /**
     * The signature configurations for the server API.
     * 
     * @author: ali
     * @version: 0.1 2018-06-21 20:47:05
     */
    public class SignatureUtils {
    
        private final static String CHARSET_UTF8 = "utf8";
        private final static String ALGORITHM = "HmacSHA1";
        private final static String SEPARATOR = "&";
    
        public static Map<String, String> splitQueryString(String url)
                throws URISyntaxException, UnsupportedEncodingException {
            URI uri = new URI(url);
            String query = uri.getQuery();
            final String[] pairs = query.split("&");
            TreeMap<String, String> queryMap = new TreeMap<String, String>();
            for (String pair : pairs) {
                final int idx = pair.indexOf("=");
                final String key = idx > 0 ? pair.substring(0, idx) : pair;
                if (!queryMap.containsKey(key)) {
                    queryMap.put(key, URLDecoder.decode(pair.substring(idx + 1), CHARSET_UTF8));
                }
            }
            return queryMap;
        }
    
        public static String generate(String method, Map<String, String> parameter, String accessKeySecret)
                throws Exception {
            String signString = generateSignString(method, parameter);
            System.out.println("signString---" + signString);
            byte[] signBytes = hmacSHA1Signature(accessKeySecret + "&", signString);
            String signature = newStringByBase64(signBytes);
            System.out.println("signature----" + signature);
            if ("POST".equals(method))
                return signature;
            return URLEncoder.encode(signature, "UTF-8");
        }
    
        public static String generateSignString(String httpMethod, Map<String, String> parameter) throws IOException {
            TreeMap<String, String> sortParameter = new TreeMap<String, String>();
            sortParameter.putAll(parameter);
            String canonicalizedQueryString = UrlUtil.generateQueryString(sortParameter, true);
            if (null == httpMethod) {
                throw new RuntimeException("httpMethod can not be empty");
            }
            StringBuilder stringToSign = new StringBuilder();
            stringToSign.append(httpMethod).append(SEPARATOR);
            stringToSign.append(percentEncode("/")).append(SEPARATOR);
            stringToSign.append(percentEncode(canonicalizedQueryString));
            return stringToSign.toString();
        }
    
        public static String percentEncode(String value) {
            try {
                return value == null ? null
                        : URLEncoder.encode(value, CHARSET_UTF8).replace("+", "%20").replace("*", "%2A").replace("%7E",
                                "~");
            } catch (Exception e) {
            }
            return "";
        }
    
        public static byte[] hmacSHA1Signature(String secret, String baseString) throws Exception {
            if (StringUtils.isEmpty(secret)) {
                throw new IOException("secret can not be empty");
            }
            if (StringUtils.isEmpty(baseString)) {
                return null;
            }
            Mac mac = Mac.getInstance("HmacSHA1");
            SecretKeySpec keySpec = new SecretKeySpec(secret.getBytes(CHARSET_UTF8), ALGORITHM);
            mac.init(keySpec);
            return mac.doFinal(baseString.getBytes(CHARSET_UTF8));
        }
    
        public static String newStringByBase64(byte[] bytes) throws UnsupportedEncodingException {
            if (bytes == null || bytes.length == 0) {
                return null;
            }
            return new String(Base64.encodeBase64(bytes, false), CHARSET_UTF8);
        }
    }
  4. Modify the Main.java file.
    /*   
     * Copyright © 2018 Alibaba. All rights reserved.
     */
    package com.aliyun.iot.demo.sign;
    
    import java.io.UnsupportedEncodingException;
    import java.net.URLEncoder;
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * The main entry to the signature tool.
     * 
     * @author: ali
     * @version: 0.1 2018-09-18 15:06:48
     */
    public class Main {
    
        // 1. Modify the AccessKey pair in the Config.java file. 
        // 2. You must specify all parameters. We recommend that you use Method 2 in the following description. 
        // 3. The value of "Final signature" is the signature that you want to obtain. 
        public static void main(String[] args) throws UnsupportedEncodingException {
    
            // Method 1
            System.out.println("Method 1:");
            String str = "GET&%2F&AccessKeyId%3D" + Config.accessKey
                    + "%26Action%3DRegisterDevice%26DeviceName%3D1533023037%26Format%3DJSON%26ProductKey%3DaxxxUtgaRLB%26RegionId%3Dcn-shanghai%26SignatureMethod%3DHMAC-SHA1%26SignatureNonce%3D1533023037%26SignatureVersion%3D1.0%26Timestamp%3D2018-07-31T07%253A43%253A57Z%26Version%3D2018-01-20";
            byte[] signBytes;
            try {
                signBytes = SignatureUtils.hmacSHA1Signature(Config.accessKeySecret + "&", str.toString());
                String signature = SignatureUtils.newStringByBase64(signBytes);
                System.out.println("signString---" + str);
                System.out.println("signature----" + signature);
                System.out.println("Final signature:" + URLEncoder.encode(signature, Config.CHARSET_UTF8));
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println();
    
            // Method 2
            System.out.println("Method 2:");
            Map<String, String> map = new HashMap<String, String>();
            // The common parameters.
            map.put("Format", "JSON");
            map.put("Version", "2018-01-20");
            map.put("AccessKeyId", Config.accessKey);
            map.put("SignatureMethod", "HMAC-SHA1");
            map.put("Timestamp", "2018-07-31T07:43:57Z");
            map.put("SignatureVersion", "1.0");
            map.put("SignatureNonce", "1533023037");
            map.put("RegionId", "cn-shanghai");
            // The request parameters.
            map.put("Action", "RegisterDevice");
            map.put("DeviceName", "1533023037");
            map.put("ProductKey", "axxxUtgaRLB");
            try {
                String signature = SignatureUtils.generate("GET", map, Config.accessKeySecret);
                System.out.println("Final signature:" + signature);
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println();
        }
    }