Using an Authorization Header
Function
Using a header for authentication is the most common method of carrying a signature, supported by all OBS APIs. With this method, the signature is carried in the Authorization header of a request, as shown in Figure 1.
The header format is:
Authorization: OBS AccessKeyID:Signature
An OBS string, an access key ID (AK), and a signature comprise an Authorization header. The string OBS and AK are separated by a space, and the AK and signature are separated by a colon (:).
- To obtain an access key ID (AK), see Access Keys.
- To calculate a signature, see Table 1.
Table 1 Calculating a signature carried in the Authorization header Method
Description
Link
Using SDKs
All available OBS SDKs provide automatic calculation. Save time by using them directly.
Using signature generators
OBS provides graphical tools to make it easier to generate signatures.
Manual calculation
You can manually calculate a signature using the provided signing algorithm.
Using SDKs
OBS SDKs provide built-in signature calculation, so you do not need to take care of this operation. Table 2 lists the source code files for signature calculation for OBS SDKs in different languages.
Using a Signature Generator
OBS provides a graphical tool to make it easier to generate signatures. You can find the tool here. To learn how to use the tool, see Using Signature Generators.
Manually Calculating a Signature
Use the following algorithm to calculate the signature carried in the header:
Signature = Base64( HMAC-SHA1( YourSecretAccessKeyID, UTF-8-Encoding-Of( StringToSign ) ) )
The following figure shows how the signature carried in the header is calculated:
- Construct a StringToSign. Below are the StringToSign structure and parameters. For more StringToSign examples, see Example StringToSign.
StringToSign = HTTP-Verb + "\n" + Content-MD5 + "\n" + Content-Type + "\n" + Date + "\n" + CanonicalizedHeaders + "\n" + CanonicalizedResource
Table 3 Parameters required for constructing a StringToSign Parameter
Type
Mandatory (Yes/No)
Description
HTTP-Verb
String
Yes
Explanation:
The HTTP method used to make a request (also called an operation). For RESTful APIs, HTTP methods include PUT, GET, DELETE, and other operations. Select a method based on the API to be called.
Restrictions:
None
Value range:
- GET: Requests that a server return a specific resource, for example, obtaining a bucket list or downloading an object.
- PUT: Requests that a server update a specific resource, for example, creating a bucket or uploading an object.
- POST: Requests that a server add a resource or perform special operations such as initiating a multipart upload or assembling parts.
- DELETE: Requests that a server delete a specific resource such as an object.
- HEAD: Requests that a server return the description of a specific resource, for example, obtaining object metadata.
- OPTIONS (not supported for signature generators): Requests that a server check whether the user has the permissions to perform an operation on a resource. CORS must be configured for the bucket.
Default value:
None
Content-MD5
String
No
Explanation:
The base64-encoded 128-bit MD5 digest of the request body based on RFC 1864.
Restrictions:
None
Value range:
A string of 22 characters.
Default value:
This parameter is left blank by default.
Content-Type
String
No
Explanation:
The file type of an object—for example, text/plain—which determines what format and encoding a browser uses to read the file.
Restrictions:
None
Value range:
See What Is Content-Type (MIME)?
Default value:
If this parameter is not included, an empty string is used by default. Table 4 shows an example.
Date
String
Yes
Explanation:
When a request was made. If the value of Date is over 15 minutes away from the current server time, the server returns 403, indicating that the request is invalid.
Restrictions:
- The value must be a GMT in RFC 1123 format.
- If Date and x-obs-date headers are both specified, x-obs-date applies.
Value range:
None
Default value:
None
CanonicalizedHeaders
String
No
Explanation:
Additional headers defined by OBS that include the x-obs- prefix, for example, x-obs-date, x-obs-acl, and x-obs-meta-*. For each additional header, separate its name and value by a colon (:). In x-obs-storage-class:STANDARD, for example, x-obs-storage-class is the header name, and STANDARD is the header value.
Restrictions:
- Header names must be lowercase. The header value is case sensitive. An example header is x-obs-storage-class:STANDARD.
- A header name cannot contain non-ASCII or unrecognizable characters, which are also not recommended for header values. If such characters are necessary, they must be encoded or decoded in URL or Base64 on the client side, because the server side does not perform any decoding.
- A header cannot contain meaningless tabs or spaces. For example, x-obs-meta-name: name (with a meaningless space before name) must be changed to x-obs-meta-name:name.
- If multiple headers are involved, they must be sorted in ascending lexicographic order by header name.
- If a header has multiple values, these values need to be written together under their shared header name, separated by commas (,). For example, x-obs-meta-name:name1 and x-obs-meta-name:name2 must be combined into x-obs-meta-name:name1,name2.
- Each header requires a new line, and each line ends with a newline character (\n).
Value range:
Determined by the API to be called
Default value:
This parameter is left blank by default.
CanonicalizedResource
String
Yes
Explanation:
OBS resources specified in an HTTP request. The structure is as follows:
CanonicalizedResource = "/" + bucket-name + "/" + object-name + "?" + sub-resource
For example, if you want to call GetObject to obtain version xxx of object object-test stored in bucket-test and change Content-Type to text/plain, then CanonicalizedResource would be as follows:
/bucket-test/object-test?response-content-type=text/plain&versionId=xxx
- bucket-name:
If the bucket does not have a custom domain name associated, use its own name.
Otherwise, use its associated custom domain name. In /obs.ccc.com/object, for example, obs.ccc.com is a custom bucket domain name.
If an API operation does not require a bucket to be specified, for example, listing all buckets under an account, omit both the bucket name and object name by using, for example, /.
- object-name:
The name of the required object. Follow the object naming rules.
- sub-resource: Arrange multiple sub-resources in ascending lexicographic order and use ampersands (&) to separate them.
sub-resource identifiers: CDNNotifyConfiguration, acl, append, attname, backtosource, cors, customdomain, delete, deletebucket, directcoldaccess, encryption, inventory, length, lifecycle, location, logging, metadata, modify, name, notification, partNumber, policy, position, quota, rename, replication, restore, storageClass, storagePolicy, storageinfo, tagging, torrent, truncate, uploadId, uploads, versionId, versioning, versions, website,x-obs-security-token, object-lock, retention
Response header sub-resources: response-cache-control, response-content-disposition, response-content-encoding, response-content-language, response-content-type, response-expires
Image processing sub-resources: x-image-process, x-image-save-bucket, x-image-save-object
Restrictions:
A sub-resource usually has only one value. Listing multiple values for the same resource key—for example, key=value1&key=value2—is not recommended. If you do so, only the first sub-resource value is used to calculate the signature.
Value range:
None
Default value:
If this parameter is not specified, / is used.
- UTF-8 encode the result from step 1.
- Use your SK to calculate the HMAC-SHA1 of the result from step 2.
- Base64 encode the result from step 3 to obtain the signature.
Examples for Calculating Content-MD5 in Java
You can choose to add the Content-MD5 header when constructing the StringToSign. The code example below calculates the Content-MD5 header value.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
import java.security.MessageDigest; import sun.misc.BASE64Encoder; import java.io.UnsupportedEncodingException; import java.security.NoSuchAlgorithmException; public class Md5{ public static void main(String[] args) { try { String exampleString = "blog"; MessageDigest messageDigest = MessageDigest.getInstance("MD5"); BASE64Encoder encoder = new BASE64Encoder(); // Base64 encode the MD5 value of the string. String contentMd5 = encoder.encode(messageDigest.digest(exampleString.getBytes("utf-8"))); System.out.println("Content-MD5:" + contentMd5); } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) { e.printStackTrace(); } } } |
Signature = Base64( HMAC-SHA1( YourSecretAccessKeyID, UTF-8-Encoding-Of( StringToSign ) ) )
PUT / HTTP/1.1 Host: newbucketname2.obs.region.myhuaweicloud.com Content-Length: length Date: Fri, 06 Jul 2018 03:45:51 GMT x-obs-acl:private x-obs-storage-class:STANDARD Authorization: OBS UDSIAMSTUBTEST000254:ydH8ffpcbS6YpeOMcEZfn0wE90c= <CreateBucketConfiguration xmlns="http://obs.region.myhuaweicloud.com/doc/2015-06-30/"> <Location>region</Location> </CreateBucketConfiguration>
Code Examples
The signing code examples for different languages are as follows:
Java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 |
import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Arrays; import java.util.Base64; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.TreeMap; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; public class SignDemo { private static final String SIGN_SEP = "\n"; private static final String OBS_PREFIX = "x-obs-"; private static final String DEFAULT_ENCODING = "UTF-8"; private static final List<String> SUB_RESOURCES = Collections.unmodifiableList(Arrays.asList( "CDNNotifyConfiguration", "acl", "append", "attname", "backtosource", "cors", "customdomain", "delete", "deletebucket", "directcoldaccess", "encryption", "inventory", "length", "lifecycle", "location", "logging", "metadata", "mirrorBackToSource", "modify", "name", "notification", "obscompresspolicy", "partNumber", "policy", "position", "quota","rename", "replication", "response-cache-control", "response-content-disposition","response-content-encoding", "response-content-language", "response-content-type", "response-expires","restore", "storageClass", "storagePolicy", "storageinfo", "tagging", "torrent", "truncate", "uploadId", "uploads", "versionId", "versioning", "versions", "website", "x-image-process", "x-image-save-bucket", "x-image-save-object", "x-obs-security-token", "object-lock", "retention")); private String ak; private String sk; // UTF8 encode the character string. public String urlEncode(String input) throws UnsupportedEncodingException { return URLEncoder.encode(input, DEFAULT_ENCODING) .replaceAll("%7E", "~") //for browser .replaceAll("%2F", "/") .replaceAll("%20", "+"); } private String join(List<?> items, String delimiter) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < items.size(); i++) { String item = items.get(i).toString(); sb.append(item); if (i < items.size() - 1) { sb.append(delimiter); } } return sb.toString(); } private boolean isValid(String input) { return input != null && !input.equals(""); } // Use the SK to calculate the HmacSHA1. public String hmacSha1(String input) throws NoSuchAlgorithmException, InvalidKeyException, UnsupportedEncodingException { SecretKeySpec signingKey = new SecretKeySpec(this.sk.getBytes(DEFAULT_ENCODING), "HmacSHA1"); // Obtain a Mac instance and use the getInstance method to specify the HMAC-SHA1 for the algorithm. Mac mac = Mac.getInstance("HmacSHA1"); // Use the SK to initialize the Mac object. mac.init(signingKey); return Base64.getEncoder().encodeToString(mac.doFinal(input.getBytes(DEFAULT_ENCODING))); } // Construct a StringToSign. private String stringToSign(String httpMethod, Map<String, String[]> headers, Map<String, String> queries, String bucketName, String objectName) throws Exception{ String contentMd5 = ""; String contentType = ""; String date = ""; TreeMap<String, String> canonicalizedHeaders = new TreeMap<String, String>(); String key; List<String> temp = new ArrayList<String>(); for(Map.Entry<String, String[]> entry : headers.entrySet()) { key = entry.getKey(); if(key == null || entry.getValue() == null || entry.getValue().length == 0) { continue; } key = key.trim().toLowerCase(Locale.ENGLISH); if(key.equals("content-md5")) { contentMd5 = entry.getValue()[0]; continue; } if(key.equals("content-type")) { contentType = entry.getValue()[0]; continue; } if(key.equals("date")) { date = entry.getValue()[0]; continue; } if(key.startsWith(OBS_PREFIX)) { for(String value : entry.getValue()) { if(value != null) { temp.add(value.trim()); } } canonicalizedHeaders.put(key, this.join(temp, ",")); temp.clear(); } } // If the header contains x-obs-date, leave the Date header blank. if(canonicalizedHeaders.containsKey("x-obs-date")) { date = ""; } // Construct the StringToSign by concatenating HTTP-Verb, Content-MD5, Content-Type, and Date. StringBuilder stringToSign = new StringBuilder(); stringToSign.append(httpMethod).append(SIGN_SEP) .append(contentMd5).append(SIGN_SEP) .append(contentType).append(SIGN_SEP) .append(date).append(SIGN_SEP); // Construct the StringToSign by concatenating CanonicalizedHeaders. for(Map.Entry<String, String> entry : canonicalizedHeaders.entrySet()) { stringToSign.append(entry.getKey()).append(":").append(entry.getValue()).append(SIGN_SEP); } // Construct the StringToSign by concatenating CanonicalizedResource. stringToSign.append("/"); if(this.isValid(bucketName)) { stringToSign.append(bucketName).append("/"); if(this.isValid(objectName)) { stringToSign.append(this.urlEncode(objectName)); } } TreeMap<String, String> canonicalizedResource = new TreeMap<String, String>(); for(Map.Entry<String, String> entry : queries.entrySet()) { key = entry.getKey(); if(key == null) { continue; } if(SUB_RESOURCES.contains(key)) { canonicalizedResource.put(key, entry.getValue()); } } if(canonicalizedResource.size() > 0) { stringToSign.append("?"); for(Map.Entry<String, String> entry : canonicalizedResource.entrySet()) { stringToSign.append(entry.getKey()); if(this.isValid(entry.getValue())) { stringToSign.append("=").append(entry.getValue()); } stringToSign.append("&"); } stringToSign.deleteCharAt(stringToSign.length()-1); } // System.out.println(String.format("StringToSign:%s%s", SIGN_SEP, stringToSign.toString())); return stringToSign.toString(); } public String headerSignature(String httpMethod, Map<String, String[]> headers, Map<String, String> queries, String bucketName, String objectName) throws Exception { // Construct a StringToSign. String stringToSign = this.stringToSign(httpMethod, headers, queries, bucketName, objectName); // Calculate the signature. return String.format("OBS %s:%s", this.ak, this.hmacSha1(stringToSign)); } public static void main(String[] args) throws Exception { SignDemo demo = new SignDemo(); /* Hard-coded or plaintext AK and SK are risky. For security purposes, encrypt your AK and SK and store them in the configuration file or environment variables. In this example, the AK and SK are stored in environment variables for identity authentication. Before running the code in this example, configure environment variables HUAWEICLOUD_SDK_AK and HUAWEICLOUD_SDK_SK. */ demo.ak = System.getenv("HUAWEICLOUD_SDK_AK"); demo.sk = System.getenv("HUAWEICLOUD_SDK_SK"); String bucketName = "bucket-test"; String objectName = "hello.jpg"; Map<String, String[]> headers = new HashMap<String, String[]>(); headers.put("date", new String[] {"Sat, 12 Oct 2015 08:12:38 GMT"}); headers.put("x-obs-acl", new String[] {"public-read"}); headers.put("x-obs-meta-key1", new String[] {"value1"}); headers.put("x-obs-meta-key2", new String[] {"value2", "value3"}); Map<String, String> queries = new HashMap<String, String>(); queries.put("acl", null); //Calculate and print the signature carried in the header. System.out.println(demo.headerSignature("PUT", headers, queries, bucketName, objectName)); } } |
Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
import os import sys import hashlib import hmac import binascii from datetime import datetime IS_PYTHON2 = sys.version_info.major == 2 or sys.version < '3' # Hard-coded or plaintext AK and SK are risky. For security purposes, encrypt your AK and SK and store them in the configuration file or environment variables. # In this example, the AK and SK are stored in environment variables for identity authentication. Before running the code in this example, configure environment variables HUAWEICLOUD_SDK_AK and HUAWEICLOUD_SDK_SK. yourSecretAccessKeyID = os.getenv('HUAWEICLOUD_SDK_SK') httpMethod = "PUT" contentType = "application/xml" # "date" indicates when the request was generated. date = datetime.utcnow().strftime('%a, %d %b %Y %H:%M:%S GMT') canonicalizedHeaders = "x-obs-acl:private\n" CanonicalizedResource = "/newbucketname2" # Construct a StringToSign. canonical_string = httpMethod + "\n" + "\n" + contentType + "\n" + date + "\n" + canonicalizedHeaders + CanonicalizedResource # Calculate the signature using "Signature = Base64( HMAC-SHA1( YourSecretAccessKeyID, UTF-8-Encoding-Of( StringToSign ) ) )" if IS_PYTHON2: hashed = hmac.new(yourSecretAccessKeyID, canonical_string, hashlib.sha1) encode_canonical = binascii.b2a_base64(hashed.digest())[:-1] else: hashed = hmac.new(yourSecretAccessKeyID.encode('UTF-8'), canonical_string.encode('UTF-8'), hashlib.sha1) encode_canonical = binascii.b2a_base64(hashed.digest())[:-1].decode('UTF-8') print(encode_canonical) |
C
Download the sample code for calculating a signature with the C language.
- The API for calculating the signature is included in the sign.h header file.
- The example code for calculating the signature is included in the main.c file.
Addressing a Signature Mismatch
During an OBS API call, if the following error is reported:
Status code: 403 Forbidden
Error code: SignatureDoesNotMatch
Error message: The request signature we calculated does not match the signature you provided. Check your key and signing method.
Address the problem by referring to Why Don't the Signatures Match?
Feedback
Was this page helpful?
Provide feedbackThank you very much for your feedback. We will continue working to improve the documentation.See the reply and handling status in My Cloud VOC.
For any further questions, feel free to contact us through the chatbot.
Chatbot