Signature mechanism
Alibaba Cloud Link WAN authenticates the sender of each API request. To do this, you must include a signature (Signature) in every HTTP or HTTPS request.
To sign a request, go to the AccessKey Management page in the console to obtain the AccessKey ID and AccessKey secret for your Alibaba Cloud account. These credentials are used for symmetric encryption.
The signing procedure is as follows:
- Construct a canonicalized query string.
- Sort the parameters.
Sort all request parameters alphabetically by name. These parameters include both common parameters (except for the Signature parameter) and any parameters specific to the API.
Note When you submit a request using the GET method, these parameters are the part of the request URL that follows the question mark (?) and are separated by ampersands (&). - URL-encode the parameter names and values.
Encode the request parameter names and values in UTF-8 based on the rules in RFC3986. The encoding rules are as follows:
- Do not encode uppercase letters (A-Z), lowercase letters (a-z), digits (0-9), hyphens (-), underscores (_), periods (.), or tildes (~).
- Encode other characters into the
%XYformat, whereXYis the hexadecimal representation of the character's ASCII code. For example, a double quotation mark (") is encoded as%22. - Encode extended UTF-8 characters into the
%XY%ZA...format. - Encode a space as
%20, not as a plus sign (+).
This codec is similar to the
application/x-www-form-urlencodedMIME format encoding algorithm, but is not identical.If you use
java.net.URLEncoderfrom the Java standard library, you can first use theencodemethod, and then replace the plus sign (+) with%20, the asterisk (*) with%2A, and%7Ewith a tilde (~) to obtain an encoded string that complies with the preceding 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; } - Use an equal sign (
=) to connect the encoded parameter name and value. - Use an ampersand (
&) to connect the encoded request parameters. The parameter order must be the same as the order from the "Sort the parameters" step.
After you complete these steps, you obtain the canonicalized query string (CanonicalizedQueryString).
- Sort the parameters.
- Construct the string to sign.
You can use
percentEncodeto process the canonicalized string from Step 1 to create the string-to-sign based on the following rules.String stringToSign = httpMethod + "&" + // httpMethod: The HTTP method used to send the request, such as GET. percentEncode("/") + "&" + // percentEncode("/"): The value obtained by UTF-8 encoding the character '/', which is %2F. percentEncode(canonicalizedQueryString) // Your canonicalized query string. - Calculate the HMAC value.
As defined in RFC2104, calculate the HMAC signature value from the
stringToSignstring created in Step 2. The following pseudocode shows this calculation:HMAC-Value = HMAC-SHA1 ( AccessSecret, UTF-8-Encoding-Of ( StringToSign ) )Note When you calculate the HMAC value, the key is your AccessKey secret appended with an ampersand (&) character (ASCII code 38). The hash algorithm is SHA1. - Calculate the signature value.
Encode the HMAC value from Step 3 using Base64 encoding rules. The resulting string is the signature value (Signature).
- Add the signature.
URL-encode the signature value according to the rules in RFC3986. Then, add the encoded signature to the request as the Signature parameter. This completes the request signing process.
Signature example
The following example shows a call to the GetGateway API operation. In this example, AccessKeyId = testid and AccessKeySecret = testsecret.
- Request URL before signing:
https://linkwan.cn-shanghai.aliyuncs.com/ ?Format=JSON &Version=2019-01-20 &SignatureMethod=HMAC-SHA1 &SignatureNonce=15215528852396 &SignatureVersion=1.0 &AccessKeyId=testid &Timestamp=2019-01-20T12:00:00Z &RegionId=cn-shanghai &Action=GetGateway &GwEui=0000000000000000 - The string to sign (
StringToSignGET&%2F&AccessKeyId%3Dtestid&Action%3DGetGateway&Format%3DJSON&GwEui%3D0000000000000000&RegionId%3Dcn-shanghai&SignatureMethod%3DHMAC-SHA1&SignatureNonce%3D15215528852396&SignatureVersion%3D1.0&Timestamp%3D2019-01-20T12%253A00%253A00Z&Version%3D2019-01-20 - Calculate the signature value.
Because
AccessKeySecret = testsecret, the signing key istestsecret&. The resulting signature value is:yqWsF0aPGrECmuwTfALUIl0JM9M%3D - The signature is added to the request URL as the Signature parameter. The final URL is as follows:
https://linkwan.cn-shanghai.aliyuncs.com/ ?Format=JSON &Version=2019-01-20 &Signature=yqWsF0aPGrECmuwTfALUIl0JM9M%3D &SignatureMethod=HMAC-SHA1 &SignatureNonce=15215528852396 &SignatureVersion=1.0 &AccessKeyId=testid &Timestamp=2019-01-20T12:00:00Z &RegionId=cn-shanghai &Action=GetGateway &GwEui=0000000000000000
Java code example
The following is a Java sample for signing.
- Config.java
package aliyun.signature; /** * API signature configuration. * * @author Alibaba Cloud * @date 2019/01/20 */ public class Config { /** * The AccessKey ID of the Alibaba Cloud account. */ public static final String ACCESS_KEY_ID = "testid"; /** * The AccessKey secret of the Alibaba Cloud account. */ public static final String ACCESS_KEY_SECRET = "testsecret"; /** * The UTF-8 character set. */ public static final String CHARSET_UTF8 = "utf8"; } - UrlUtil.java
package aliyun.signature; import java.net.URLEncoder; import java.util.Map; /** * URL processing utility. * * @author Alibaba Cloud * @date 2019/01/20 */ public class UrlUtil { /** * UTF-8 encoding. */ private final static String CHARSET_UTF8 = "utf8"; /** * Encodes a URL. * @param url The URL to encode. * @return The encoded URL. */ public static String urlEncode(String url) { if (url != null && !url.isEmpty()) { try { url = URLEncoder.encode(url, "UTF-8"); } catch (Exception e) { System.out.println("URL encoding error:" + e.getMessage()); } } return url; } /** * Canonicalizes a query string. * @param params The key-value pairs of all parameters in the request. * @param shouldEncodeKv Specifies whether to encode the text in the key-value pairs. * @return The canonicalized query string. */ public static String canonicalizeQueryString(Map<String, String> params, boolean shouldEncodeKv) { StringBuilder canonicalizedQueryString = new StringBuilder(); for (Map.Entry<String, String> entry : params.entrySet()) { if (shouldEncodeKv) { 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(); } /** * Percent-encodes the original text. * @param text The original text. * @return The encoded result. */ public static String percentEncode(String text) { try { return text == null ? null : URLEncoder.encode(text, CHARSET_UTF8) .replace("+", "%20") .replace("*", "%2A") .replace("%7E", "~"); } catch (Exception e) { System.out.println("Percent encoding error:" + e.getMessage()); } return ""; } } - SignatureUtils.java
package aliyun.signature; 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; /** * API signature utility. * * @author Alibaba Cloud * @date 2019/01/20 */ public class SignatureUtils { private final static String CHARSET_UTF8 = "utf8"; private final static String ALGORITHM = "UTF-8"; private final static String SEPARATOR = "&"; private final static String METHOD_NAME_POST = "POST"; /** * Splits the parameters in a query string. * @param url The original URL. * @return A map of the split parameter names and values. */ 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; } /** * Calculates the signature and converts it to the appropriate encoding. * @param httpMethod The HTTP request method. * @param parameter A map of the original parameter names and values from the query string. * @param accessKeySecret The AccessKey secret of the Alibaba Cloud account. * @return The signature converted to the appropriate encoding. */ public static String generate(String httpMethod, Map<String, String> parameter, String accessKeySecret) throws Exception { String stringToSign = generateStringToSign(httpMethod, parameter); System.out.println("stringToSign---" + stringToSign); byte[] signBytes = hmacSHA1Signature(accessKeySecret + "&", stringToSign); String signature = newStringByBase64(signBytes); if (signature == null) { return ""; } System.out.println("signature----" + signature); if (METHOD_NAME_POST.equals(httpMethod)) { return signature; } return URLEncoder.encode(signature, ALGORITHM); } /** * Calculates the intermediate product for the signature, StringToSign. * @param httpMethod The HTTP request method. * @param parameter A map of the original parameter names and values from the query string. * @return The intermediate product for the signature, StringToSign. */ public static String generateStringToSign(String httpMethod, Map<String, String> parameter) throws IOException { TreeMap<String, String> sortParameter = new TreeMap<String, String>(parameter); String canonicalizedQueryString = UrlUtil.canonicalizeQueryString(sortParameter, true); if (httpMethod == null || httpMethod.isEmpty()) { throw new RuntimeException("httpMethod cannot be empty"); } StringBuilder stringToSign = new StringBuilder(); stringToSign.append(httpMethod).append(SEPARATOR); stringToSign.append(percentEncode("/")).append(SEPARATOR); stringToSign.append(percentEncode(canonicalizedQueryString)); return stringToSign.toString(); } /** * Percent-encodes the original text. * @param text The original text to process. * @return The percent-encoded text. */ public static String percentEncode(String text) { try { return text == null ? null : URLEncoder.encode(text, CHARSET_UTF8) .replace("+", "%20") .replace("*", "%2A") .replace("%7E", "~"); } catch (Exception e) { System.out.println("Percent encoding error:" + e.getMessage()); } return ""; } /** * HMAC-SHA1 keyed hash. * @param secret The secret used for HMAC-SHA1. * @param baseString The original text. * @return The hash value. */ public static byte[] hmacSHA1Signature(String secret, String baseString) throws Exception { if (secret == null || secret.isEmpty()) { throw new IOException("secret cannot be empty"); } if (baseString == null || baseString.isEmpty()) { 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)); } /** * Base64 encodes. * @param bytes The original text. * @return The Base64-encoded text. */ 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); } } - DemoApplication.java
package aliyun.signature; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.HashMap; import java.util.Map; /** * The main program for the API signature demo. * * @author Alibaba Cloud * @date 2019/01/20 */ public class DemoApplication { /** * 1. Modify the AccessKey information in Config.java. * 2. Use Method 2. All parameters must be filled in. * 3. The "Final signature" is the final signature result that you need. * @param args ... */ public static void main(String[] args) throws UnsupportedEncodingException { // Method 1. System.out.println("Method 1:"); String str = "GET&%2F&AccessKeyId%3D" + Config.ACCESS_KEY_ID + "&Action%3DGetGateway&Format%3DJSON&GwEui%3D" + "0000000000000000&RegionId%3Dcn-shanghai&Signa" + "tureMethod%3DHMAC-SHA1&SignatureNonce%3D1521552" + "8852396&SignatureVersion%3D1.0&Timestamp%3D20" + "19-01-20T12%253A00%253A00Z&Version%3D2019-01-20"; byte[] signBytes; try { signBytes = SignatureUtils.hmacSHA1Signature(Config.ACCESS_KEY_SECRET + "&", str.toString()); String signature = SignatureUtils.newStringByBase64(signBytes); System.out.println("stringToSign---" + 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>(); // Common parameters. map.put("Format", "JSON"); map.put("Version", "2019-01-20"); map.put("AccessKeyId", Config.ACCESS_KEY_ID); map.put("SignatureMethod", "HMAC-SHA1"); map.put("Timestamp", "2019-01-20T12:00:00Z"); map.put("SignatureVersion", "1.0"); map.put("SignatureNonce", "15215528852396"); map.put("RegionId", "cn-shanghai"); map.put("Action", "GetGateway"); // Request parameters. map.put("GwEui", "0000000000000000"); try { String signature = SignatureUtils.generate("GET", map, Config.ACCESS_KEY_SECRET); System.out.println("Final signature:" + signature); } catch (Exception e) { e.printStackTrace(); } System.out.println(); } }