Baidu AI Cloud
中国站

百度智能云

Reference

Sample-Code

Python Example

Assume that the user uploads the last part of a file to the BOS cluster in Beijing with the UploadPart interface, whose content is Example.

  • Bucket name: test
  • Object key: myfolder/readme.txt
  • uploadId: a44cc9bab11cbd156984767aad637851
  • partNumber: 9
  • Access Key ID: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  • Secret Access Key: bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
  • Time: Beijing time at 16:23:49 on April 27, 2015 (translated to UTC time: 8:23:49 on April 27, 2015)

The HTTP request is as follows:

PUT /test/myfolder/readme.txt?partNumber=9&uploadId=a44cc9bab11cbd156984767aad637851 HTTP/1.1
Host: bj.bcebos.com
Date: Mon, 27 Apr 2015 16:23:49 +0800
Content-Type: text/plain
Content-Length: 8
Content-Md5: NFzcPqhviddjRNnSOGo4rw==
x-bce-date: 2015-04-27T08:23:49Z

Example

Users can fill in the fields in the following functions according to the above HTTP request.

 if __name__ == "__main__":
     credentials = BceCredentials("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb")
     http_method = "PUT"
     path = "/test/myfolder/readme.txt"
     headers = {"host": "bj.bcebos.com",
                "content-length": 8,
                "content-md5": "NFzcPqhviddjRNnSOGo4rw==",
                "content-type":"text/plain",
                "x-bce-date": "2015-04-27T08:23:49Z"}
     params = {"partNumber": 9,
               "uploadId": "a44cc9bab11cbd156984767aad637851"}
     timestamp = 1430123029
     result = sign(credentials, http_method, path, headers, params, timestamp)
     print result

Please click here for the full code

The complete code is as follows:

 # -*- coding: UTF-8 -*-
 import hashlib
 import hmac
 import string
 import datetime
 
 
 AUTHORIZATION = "authorization"
 BCE_PREFIX = "x-bce-"
 DEFAULT_ENCODING = 'UTF-8'
 
 
 # Save AK/SK classes 
 class BceCredentials(object):
     def __init__(self, access_key_id, secret_access_key):
         self.access_key_id = access_key_id
         self.secret_access_key = secret_access_key
 
 
 # According to RFC 3986, except: 
 #    1. Uppercase and lowercase English characters 
 #    2. Arabic numerals 
 #    3. Dot '.', tilde '~', minus sign '-', and underscore '_' 
 # The others need to be encoded 
 RESERVED_CHAR_SET = set(string.ascii_letters + string.digits + '.~-_')
 def get_normalized_char(i):
     char = chr(i)
     if char in RESERVED_CHAR_SET:
         return char
     else:
         return '%%%02X' % i
 NORMALIZED_CHAR_LIST = [get_normalized_char(i) for i in range(256)]
 
 
 # Normalize String
 def normalize_string(in_str, encoding_slash=True):
     if in_str is None:
         return ''
 
     # If the input is unicode, it shall be encoded by UTF8 first and then encoded 
     in_str = in_str.encode(DEFAULT_ENCODING) if isinstance(in_str, unicode) else str(in_str)
 
     # When generating a canonical URI. No need to encode the slash '/', otherwise 
     if encoding_slash:
         encode_f = lambda c: NORMALIZED_CHAR_LIST[ord(c)]
     else:
         # Only when generating canonical URIs. No need to encode the slash '/' 
         encode_f = lambda c: NORMALIZED_CHAR_LIST[ord(c)] if c != '/' else c
 
     # Encoding according to RFC 3986 
     return ''.join([encode_f(ch) for ch in in_str])
 
 
 # Generate canonical timestamp 
 def get_canonical_time(timestamp=0):
     # Return the current time when called without any parameters 
     if timestamp == 0:
         utctime = datetime.datetime.utcnow()
     else:
         utctime = datetime.datetime.utcfromtimestamp(timestamp)
 
     # Timestamp format: [year]-[month]-[day]T[hour]:[minute]:[second]Z 
     return "%04d-%02d-%02dT%02d:%02d:%02dZ" % (
         utctime.year, utctime.month, utctime.day,
         utctime.hour, utctime.minute, utctime.second)
 
 
 # Generate normalized URI 
 def get_canonical_uri(path):
     # The format of a normalized URI is: / {bucket}/{object}, and encode all characters except the slash "/" 
     return normalize_string(path, False)
 
 
 # Generate normalized query string 
 def get_canonical_querystring(params):
     if params is None:
         return ''
 
     # In addition to authorization, all query strings are added to the encoding 
     result = ['%s=%s' % (normalize_string(k), normalize_string(v)) for k, v in params.items() if k.lower != AUTHORIZATION]
 
     # Sort in lexicographic order 
     result.sort () 
 
     # Concatenate all strings with&symbol and return 
     return '&'.join(result)
 
 
 # Generate normalized header 
 def get_canonical_headers(headers, headers_to_sign=None):
     headers = headers or {}
 
     # If header_to_sign is not specified, use by default: 
     #   1.host
     #   2.content-md5
     #   3.content-length
     #   4.content-type
     #   5. All header items that start with x-bce- 
     # Generate normalized header 
     if headers_to_sign is None or len(headers_to_sign) == 0:
         headers_to_sign = {"host", "content-md5", "content-length", "content-type"}
 
     # For the key in the header, it needs to be converted to lowercase after removing the white space before and after 
     # For the value in the header, remove the white space before and after the conversion to str 
     f = lambda (key, value): (key.strip().lower(), str(value).strip())
 
     result = []
     for k, v in map(f, headers.iteritems()):
         # In any case, header items starting with x-bce- need to be added to the canonical header 
         if k.startswith(BCE_PREFIX) or k in headers_to_sign:
             result.append("%s:%s" % (normalize_string(k), normalize_string(v)))
 
     # Sort in lexicographic order 
     result.sort () 
 
     # Concatenate all strings with\n symbol and return 
     return '\n'.join(result)
 
 
 # Signature Master Algorithm 
 def sign(credentials, http_method, path, headers, params,
          timestamp=0, expiration_in_seconds=1800, headers_to_sign=None):
     headers = headers or {}
     params = params or {}
 
     # 1. Generate sign key 
     # 1.1. Generate auth-string in the format: bce-auth-v1/{accessKeyId}/{timestamp}/{expirationPeriodInSeconds} 
     sign_key_info = 'bce-auth-v1/%s/%s/%d' % (
         credentials.access_key_id,
         get_canonical_time(timestamp),
         expiration_in_seconds)
     # 1.2. Use auth-string plus SK and SHA-256 to generate sign key 
     sign_key = hmac.new(
         credentials.secret_access_key,
         sign_key_info,
         hashlib.sha256).hexdigest()
 
     # 2. Generate normalized uri 
     canonical_uri = get_canonical_uri(path)
 
     # 3. Generate a normalized query string 
     canonical_querystring = get_canonical_querystring(params)
 
     # 4. Generate a normalized header 
     canonical_headers = get_canonical_headers(headers, headers_to_sign)
 
     # 5. Use '\ n' to connect the HTTP METHOD with the results in 2, 3, 4 into a large string 
     string_to_sign = '\n'.join(
         [http_method, canonical_uri, canonical_querystring, canonical_headers])
 
     # 6. Use the signature string generated in 5 and the sign key generated in 1 to generate a signature result using the SHA-256 algorithm 
     sign_result = hmac.new(sign_key, string_to_sign, hashlib.sha256).hexdigest()
 
     # 7. Stitch the final signature result string 
     if headers_to_sign:
         # Specify header to sign 
         result = '%s/%s/%s' % (sign_key_info, ';'.join(headers_to_sign), sign_result)
     else:
         # Default signature result string without specifying header to sign 
         result = '%s//%s' % (sign_key_info, sign_result)
 
     return result
 
 if __name__ == "__main__":
     credentials = BceCredentials("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb")
     http_method = "PUT"
     path = "/test/myfolder/readme.txt"
     headers = {"host": "bj.bcebos.com",
                "content-length": 8,
                "content-md5": "NFzcPqhviddjRNnSOGo4rw==",
                "content-type":"text/plain",
                "x-bce-date": "2015-04-27T08:23:49Z"}
     params = {"partNumber": 9,
               "uploadId": "a44cc9bab11cbd156984767aad637851"}
     timestamp = 1430123029
     result = sign(credentials, http_method, path, headers, params, timestamp)
     print result

Php Example

Assume that the user uploads the last part of a file to the BOS cluster in Beijing with the UploadPart interface, whose content is Example.

  • Bucket name: test
  • Object key: myfolder/readme.txt
  • uploadId: a44cc9bab11cbd156984767aad637851
  • partNumber: 9
  • Access Key ID: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  • Secret Access Key: bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
  • Time: Beijing time at 16:23:49 on April 27, 2015 (translated to UTC time: 8:23:49 on April 27, 2015)

The HTTP request is as follows:

PUT /test/myfolder/readme.txt?partNumber=9&uploadId=a44cc9bab11cbd156984767aad637851 HTTP/1.1
Host: bj.bcebos.com
Date: Mon, 27 Apr 2015 16:23:49 +0800
Content-Type: text/plain
Content-Length: 8
Content-Md5: NFzcPqhviddjRNnSOGo4rw==
x-bce-date: 2015-04-27T08:23:49Z

Example

Signing sample code

$signer = new SampleSigner();
$credentials = array("ak" => "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","sk" => "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
$httpMethod = "PUT";
$path = "/v1/test/myfolder/readme.txt";
$headers = array("Host" => "bj.bcebos.com",
                "Content-Length" => 8,
                "Content-MD5" => "NFzcPqhviddjRNnSOGo4rw==",
                "Content-Type" => "text/plain",
                "x-bce-date" => "2015-04-27T08:23:49Z");
$params = array("partNumber" => 9, "uploadId" => "a44cc9bab11cbd156984767aad637851");
$timestamp = new \DateTime();
$timestamp->setTimestamp(1430123029);
$options = array(SignOption::TIMESTAMP => $timestamp);
$ret = $signer->sign($credentials, $httpMethod, $path, $headers, $params, $options);
print $ret;

Please click here for the full code

The complete code is as follows:

<?php
/*
* Copyright (c) 2014 Baidu.com, Inc. All Rights Reserved
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* Http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing privileges and limitations under
* the License.
*/

namespace BaiduBce\Auth;

class SignOption
{
    const EXPIRATION_IN_SECONDS = 'expirationInSeconds';

    const HEADERS_TO_SIGN = 'headersToSign';

    const TIMESTAMP = 'timestamp';

    const DEFAULT_EXPIRATION_IN_SECONDS = 1800;

    const MIN_EXPIRATION_IN_SECONDS = 300;

    const MAX_EXPIRATION_IN_SECONDS = 129600;
}

class HttpUtil
{
    // According to RFC 3986, except: 
    //    1. Uppercase and lowercase English characters 
    //    2. Arabic numerals 
    //    3. Dot '.', tilde '~', minus sign '-', and underscore '_' 
    // The others need to be encoded 
    public static $PERCENT_ENCODED_STRINGS;

    // Fill the encoded array 
    public static function __init()
    {
        HttpUtil::$PERCENT_ENCODED_STRINGS = array();
        for ($i = 0; $i < 256; ++$i) {
            HttpUtil::$PERCENT_ENCODED_STRINGS[$i] = sprintf("%%%02X", $i);
        }

        // a-z does not encode 
        foreach (range('a', 'z') as $ch) {
            HttpUtil::$PERCENT_ENCODED_STRINGS[ord($ch)] = $ch;
        }

        // A-Z does not encode 
        foreach (range('A', 'Z') as $ch) {
            HttpUtil::$PERCENT_ENCODED_STRINGS[ord($ch)] = $ch;
        }

        // 0-9 does not encode 
        foreach (range('0', '9') as $ch) {
            HttpUtil::$PERCENT_ENCODED_STRINGS[ord($ch)] = $ch;
        }

        // The following 4 characters are not encoded 
        HttpUtil::$PERCENT_ENCODED_STRINGS[ord('-')] = '-';
        HttpUtil::$PERCENT_ENCODED_STRINGS[ord('.')] = '.';
        HttpUtil::$PERCENT_ENCODED_STRINGS[ord('_')] = '_';
        HttpUtil::$PERCENT_ENCODED_STRINGS[ord('~')] = '~';
    }

    // Can't encode '/' in uri encoding 
    public static function urlEncodeExceptSlash($path)
    {
        return str_replace("%2F", "/", HttpUtil::urlEncode($path));
    }

    // use encoding array encoding 
    public static function urlEncode($value)
    {
        $result = '';
        for ($i = 0; $i < strlen($value); ++$i) {
            $result .= HttpUtil::$PERCENT_ENCODED_STRINGS[ord($value[$i])];
        }
        return $result;
    }

    // Generate standardized QueryString 
    public static function getCanonicalQueryString(array $parameters)
    {
        // No parameters, return empty string directly 
        if (count($parameters) == 0) {
            return '';
        }

        $parameterStrings = array();
        foreach ($parameters as $k => $v) {
            // Skip the Authorization field 
            if (strcasecmp('Authorization', $k) == 0) {
                continue;
            }
            if (!isset($k)) {
                throw new \InvalidArgumentException(
                    "parameter key should not be null"
                );
            }
            if (isset($v)) {
                // For the value, put it on both sides of = after encoding 
                $parameterStrings[] = HttpUtil::urlEncode($k)
                    . '=' . HttpUtil::urlEncode((string) $v);
            } else {
                // For those without a value, just encode the key and place it to the left of =, leaving the right blank 
                $parameterStrings[] = HttpUtil::urlEncode($k) . '=';
            }
        }
        // Sort in lexicographic order 
        sort($parameterStrings);

        // connect them using the '&' symbol 
        return implode('&', $parameterStrings);
    }

    // Generate standardized uri 
    public static function getCanonicalURIPath($path)
    {
        // The empty path is set to '/' 
        if (empty($path)) {
            return '/';
        } else {
            // All uri must start with '/' 
            if ($path[0] == '/') {
                return HttpUtil::urlEncodeExceptSlash($path);
            } else {
                return '/' . HttpUtil::urlEncodeExceptSlash($path);
            }
        }
    }

    // Generate a standardized http request header string 
    public static function getCanonicalHeaders($headers)
    {
        // If no headers, return empty string 
        if (count($headers) == 0) {
            return '';
        }

        $headerStrings = array();
        foreach ($headers as $k => $v) {
            // Skip the null key 
            if ($k === null) {
                continue;
            }
            // If value is null, the assignment is an empty string 
            if ($v === null) {
                $v = '';
            }
            // encode after trim, then connect with ':' 
            $headerStrings[] = HttpUtil::urlEncode(strtolower(trim($k))) . ':' . HttpUtil::urlEncode(trim($v));
        }
        // Sort in lexicographic order 
        sort($headerStrings);

        // connect them with '\ n' 
        return implode("\n", $headerStrings);
    }

}
HttpUtil::__init();


class SampleSigner
{

    const BCE_AUTH_VERSION = "bce-auth-v1";
    const BCE_PREFIX = 'x-bce-';

    // If headersToSign is not specified, the default http header is signed, including: 
    //    1.host
    //    2.content-length
    //    3.content-type
    //    4.content-md5
    public static $defaultHeadersToSign;

    public static function  __init()
    {
        SampleSigner::$defaultHeadersToSign = array(
            "host",
            "content-length",
            "content-type",
            "content-md5",
        );
    }

    // Signature function 
    public function sign(
        array $credentials,
        $httpMethod,
        $path,
        $headers,
        $params,
        $options = array()
    ) {
        // Set signature validity time 
        if (!isset($options[SignOption::EXPIRATION_IN_SECONDS])) {
            // The default value is 1800 seconds 
            $expirationInSeconds = SignOption::DEFAULT_EXPIRATION_IN_SECONDS;
        } else {
            $expirationInSeconds = $options[SignOption::EXPIRATION_IN_SECONDS];
        }

        // Parse ak sk 
        $accessKeyId = $credentials['ak'];
        $secretAccessKey = $credentials['sk'];

        // Set the timestamp, Note: If you specify the timestamp yourself, it needs to be UTC time 
        if (!isset($options[SignOption::TIMESTAMP])) {
            // default value current time 
            $timestamp = new \DateTime();
        } else {
            $timestamp = $options[SignOption::TIMESTAMP];
        }
        $timestamp->setTimezone(new \DateTimeZone("UTC"));

        // Generate authString 
        $authString = SampleSigner::BCE_AUTH_VERSION . '/' . $accessKeyId . '/'
            . $timestamp->format("Y-m-d\TH:i:s\Z") . '/' . $expirationInSeconds;

        // Generate signKey using sk and authString 
        $signingKey = hash_hmac('sha256', $authString, $secretAccessKey);

        // Generate a standardized URI 
        $canonicalURI = HttpUtil::getCanonicalURIPath($path);

        // Generate standardized QueryString 
        $canonicalQueryString = HttpUtil::getCanonicalQueryString($params);

        // fill headersToSign, which indicates which headers participate in the signature 
        $headersToSignOption = null;
        if (isset($options[SignOption::HEADERS_TO_SIGN])) {
            $headersToSignOption = $options[SignOption::HEADERS_TO_SIGN];
        }
        
        $headersToSign = SampleSigner::getHeadersToSign($headers, $headersToSignOption);
        
        // Generate standardized header 
        $canonicalHeader = HttpUtil::getCanonicalHeaders($headersToSign);
        
        $headersToSign = array_keys($headersToSign);
        sort($headersToSign);
        // Fix headersToSign and connect with ';' 
        $signedHeaders = '';
        if ($headersToSignOption !== null) {
            $signedHeaders = strtolower(
                trim(implode(";", $headersToSign))
            );
        }

        // make up a standard request string 
        $canonicalRequest = "$httpMethod\n$canonicalURI\n"
            . "$canonicalQueryString\n$canonicalHeader";

        // Use signKey and standard request string to complete the signature 
        $signature = hash_hmac('sha256', $canonicalRequest, $signingKey);

        // make up the final signature string 
        $authorizationHeader = "$authString/$signedHeaders/$signature";

        return $authorizationHeader;
    }

    /** Filter headers that should participate in signature based on headsToSign 
     * 
     * @param $headers array
     * @param $headersToSign array
     * @return array
     */
    public static function getHeadersToSign($headers, $headersToSign)
    {

        $ret = array();
        if ($headersToSign !== null) {
            $tmp = array();

            // Handle the keys of the headers: Remove whitespace before and after and convert to lowercase 
            foreach ($headersToSign as $header) {
                $tmp[] = strtolower(trim($header));
            }
            $headersToSign = $tmp;
        }
        foreach ($headers as $k => $v) {
            if (trim((string) $v) !== '') {
                if ($headersToSign !== null) {
                    // Preprocessing headersToSign: Remove whitespace before and after and convert to lowercase 
                    if (in_array(strtolower(trim($k)), $headersToSign)) {
                        $ret[$k] = $v;
                    }
                } else {
                    // If there is no headersToSign, the headers are selected according to the default rules 
                    if (SampleSigner::isDefaultHeaderToSign($k, $headersToSign)) {
                        $ret[$k] = $v;
                    }
                }
            }
        }
        return $ret;
    }

    /**
     * Check if the header is signed by default: 
     * 1. it is one of host, content-type, content-md5, content-length 
     * 2. Start with x-bce 
     *
     * @param $header string
     * @return bool
     */
    public static function isDefaultHeaderToSign($header)
    {
        $header = strtolower(trim($header));
        if (in_array($header, SampleSigner::$defaultHeadersToSign)) {
            return true;
        }
        $prefix = substr($header, 0, strlen(SampleSigner::BCE_PREFIX));
        if ($prefix === SampleSigner::BCE_PREFIX) {
            return true;
        } else {
            return false;
        }
    }
}

SampleSigner::__init();



// Sign the sample code 
$signer = new SampleSigner();
$credentials = array("ak" => "0b0f67dfb88244b289b72b142befad0c","sk" => "bad522c2126a4618a8125f4b6cf6356f");
$httpMethod = "PUT";
$path = "/v1/test/myfolder/readme.txt";
$headers = array("Host" => "bj.bcebos.com",
                "Content-Length" => 8,
                "Content-MD5" => "0a52730597fb4ffa01fc117d9e71e3a9",
                "Content-Type" => "text/plain",
                "x-bce-date" => "2015-04-27T08:23:49Z");
$params = array("partNumber" => 9, "uploadId" => "VXBsb2FkIElpZS5tMnRzIHVwbG9hZA");
date_default_timezone_set("PRC");
$timestamp = new \DateTime();
$timestamp->setTimestamp(1430123029);
$options = array(SignOption::TIMESTAMP => $timestamp);
// $options = array(SignOption::TIMESTAMP => $timestamp, SignOption::HEADERS_TO_SIGN => array("Content-Type", "Host", "x-bce-date"));
$ret = $signer->sign($credentials, $httpMethod, $path, $headers, $params, $options);
print $ret;

Java Example

Users can refer to the following code to learn more about Baidu AI Cloud API authentication mechanism.

Code download path: https://github.com/baidubce/bce-sdk-java/blob/master/src/main/java/com/baidubce/auth/BceV1Signer.java

Note: The API authentication sample code of the Android language is consistent with the Java sample code.

/*
 * Copyright (c) 2014 Baidu.com, Inc. All Rights Reserved
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
 * specific language governing privileges and limitations under the License.
 */

package com.baidubce.auth;

import com.baidubce.BceClientException;
import com.baidubce.http.Headers;
import com.baidubce.internal.InternalRequest;
import com.baidubce.util.DateUtils;
import com.baidubce.util.HttpUtils;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

import org.apache.commons.codec.binary.Hex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import java.nio.charset.Charset;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;

import static com.google.common.base.Preconditions.checkNotNull;

/**
 * The V1 implementation of Signer with the BCE signing protocol.
 */
public class BceV1Signer implements Signer {

    private static final Logger logger = LoggerFactory.getLogger(BceV1Signer.class);

    private static final String BCE_AUTH_VERSION = "bce-auth-v1";
    private static final String DEFAULT_ENCODING = "UTF-8";
    private static final Charset UTF8 = Charset.forName(DEFAULT_ENCODING);

    // Default headers to sign with the BCE signing protocol.
    private static final Set<String> defaultHeadersToSign = Sets.newHashSet();
    private static final Joiner headerJoiner = Joiner.on('\n');
    private static final Joiner signedHeaderStringJoiner = Joiner.on(';');

    static {
        BceV1Signer.defaultHeadersToSign.add(Headers.HOST.toLowerCase());
        BceV1Signer.defaultHeadersToSign.add(Headers.CONTENT_LENGTH.toLowerCase());
        BceV1Signer.defaultHeadersToSign.add(Headers.CONTENT_TYPE.toLowerCase());
        BceV1Signer.defaultHeadersToSign.add(Headers.CONTENT_MD5.toLowerCase());
    }

    /**
     * @see com.baidubce.auth.Signer#sign(InternalRequest, BceCredentials)
     */
    @Override
    public void sign(InternalRequest request, BceCredentials credentials) {
        this.sign(request, credentials, null);
    }

    /**
     * Sign the given request with the given set of credentials. Modifies the passed-in request to apply the signature.
     *
     * @param request     the request to sign.
     * @param credentials the credentials to sign the request with.
     * @param options     the options for signing.
     */
    @Override
    public void sign(InternalRequest request, BceCredentials credentials, SignOptions options) {
        checkNotNull(request, "request should not be null.");

        if (credentials == null) {
            return;
        }

        if (options == null) {
            if (request.getSignOptions() != null) {
                options = request.getSignOptions();
            } else {
                options = SignOptions.DEFAULT;
            }
        }

        String accessKeyId = credentials.getAccessKeyId();
        String secretAccessKey = credentials.getSecretKey();

        request.addHeader(Headers.HOST, HttpUtils.generateHostHeader(request.getUri()));

        Date timestamp = options.getTimestamp();
        if (timestamp == null) {
            timestamp = new Date();
        }

        String authString =
                BceV1Signer.BCE_AUTH_VERSION + "/" + accessKeyId + "/"
                        + DateUtils.formatAlternateIso8601Date(timestamp) + "/" + options.getExpirationInSeconds();

        String signingKey = this.sha256Hex(secretAccessKey, authString);
        // Formatting the URL with signing protocol.
        String canonicalURI = this.getCanonicalURIPath(request.getUri().getPath());
        // Formatting the query string with signing protocol.
        String canonicalQueryString = HttpUtils.getCanonicalQueryString(request.getParameters(), true);
        // Sorted the headers should be signed from the request.
        SortedMap<String, String> headersToSign =
                this.getHeadersToSign(request.getHeaders(), options.getHeadersToSign());
        // Formatting the headers from the request based on signing protocol.
        String canonicalHeader = this.getCanonicalHeaders(headersToSign);
        String signedHeaders = "";
        if (options.getHeadersToSign() != null) {
            signedHeaders = BceV1Signer.signedHeaderStringJoiner.join(headersToSign.keySet());
            signedHeaders = signedHeaders.trim().toLowerCase();
        }

        String canonicalRequest =
                request.getHttpMethod() + "\n" + canonicalURI + "\n" + canonicalQueryString + "\n" + canonicalHeader;

        // Signing the canonical request using key with sha-256 algorithm.
        String signature = this.sha256Hex(signingKey, canonicalRequest);

        String authorizationHeader = authString + "/" + signedHeaders + "/" + signature;

        logger.debug("CanonicalRequest:{}\tAuthorization:{}", canonicalRequest.replace("\n", "[\\n]"),
                authorizationHeader);

        request.addHeader(Headers.AUTHORIZATION, authorizationHeader);
    }

    private String getCanonicalURIPath(String path) {
        if (path == null) {
            return "/";
        } else if (path.startsWith("/")) {
            return HttpUtils.normalizePath(path);
        } else {
            return "/" + HttpUtils.normalizePath(path);
        }
    }

    private String getCanonicalHeaders(SortedMap<String, String> headers) {
        if (headers.isEmpty()) {
            return "";
        }

        List<String> headerStrings = Lists.newArrayList();
        for (Map.Entry<String, String> entry : headers.entrySet()) {
            String key = entry.getKey();
            if (key == null) {
                continue;
            }
            String value = entry.getValue();
            if (value == null) {
                value = "";
            }
            headerStrings.add(HttpUtils.normalize(key.trim().toLowerCase()) + ':' + HttpUtils.normalize(value.trim()));
        }
        Collections.sort(headerStrings);

        return headerJoiner.join(headerStrings);
    }

    private SortedMap<String, String> getHeadersToSign(Map<String, String> headers, Set<String> headersToSign) {
        SortedMap<String, String> ret = Maps.newTreeMap();
        if (headersToSign != null) {
            Set<String> tempSet = Sets.newHashSet();
            for (String header : headersToSign) {
                tempSet.add(header.trim().toLowerCase());
            }
            headersToSign = tempSet;
        }
        for (Map.Entry<String, String> entry : headers.entrySet()) {
            String key = entry.getKey();
            if (entry.getValue() != null&&!entry.getValue().isEmpty()) {
                if ((headersToSign == null&&this.isDefaultHeaderToSign(key))
                        || (headersToSign != null&&headersToSign.contains(key.toLowerCase())
                               &&!Headers.AUTHORIZATION.equalsIgnoreCase(key))) {
                    ret.put(key, entry.getValue());
                }
            }
        }
        return ret;
    }

    private boolean isDefaultHeaderToSign(String header) {
        header = header.trim().toLowerCase();
        return header.startsWith(Headers.BCE_PREFIX) || defaultHeadersToSign.contains(header);
    }

    private String sha256Hex(String signingKey, String stringToSign) {
        try {
            Mac mac = Mac.getInstance("HmacSHA256");
            mac.init(new SecretKeySpec(signingKey.getBytes(UTF8), "HmacSHA256"));
            return new String(Hex.encodeHex(mac.doFinal(stringToSign.getBytes(UTF8))));
        } catch (Exception e) {
            throw new BceClientException("Fail to generate the signature", e);
        }
    }

}

Javascript Example

Users can refer to the following code to learn more about Baidu AI Cloud API authentication mechanism in Javascript language.

Code download path: https://github.com/baidubce/bce-sdk-js/blob/master/test/sdk/auth.spec.js

var Auth = require('@baiducloud/sdk').Auth;


    var auth = new Auth('my_ak', 'my_sk');

    var method = 'PUT';
    var uri = '/v1/bucket/object1';
    var params = {
        A: null,
        b: '',
        C: 'd'
    };
    var headers = {
        'Host': 'bce.baidu.com',
        'abc': '123',
        'x-bce-meta-key1': 'ABC'
    };

    var signature = auth.generateAuthorization(method, uri, params, headers, 1402639056);
    expect(signature).to.eql('bce-auth-v1/my_ak/2014-06-13T05:57:36Z/1800/host;x-bce-meta-key1/'
                             + '80c9672aca2ea9af4bb40b9a8ff458d72df94e97d550840727f3a929af271d25');

    signature = auth.generateAuthorization(method, uri, params, headers, 1402639056, 1800);
    expect(signature).to.eql('bce-auth-v1/my_ak/2014-06-13T05:57:36Z/1800/host;'
                              + 'x-bce-meta-key1/80c9672aca2ea9af4bb40b9a8ff458d72'
                              + 'df94e97d550840727f3a929af271d25');

    method = 'DELETE';
    uri = '/v1/test-bucket1361199862';
    params = {};
    headers = {
        'Content-Type': 'application/json; charset=utf-8',
        'Content-Length': 0,
        'User-Agent': 'This is the user-agent'
    };
    signature = auth.generateAuthorization(method, uri, params, headers, 1402639056, 1800);
    expect(signature).to.eql('bce-auth-v1/my_ak/2014-06-13T05:57:36Z/1800/'
                              + 'content-length;content-type/'
                              + 'c9386b15d585960ae5e6972f73ed92a9a682dc81025480ba5b41206d3e489822');

C # Example

Users can refer to the following code to learn more about Baidu AI Cloud API authentication mechanism in C # language. Complete example code:

using BaiduBce;
using BaiduBce.Auth;
using BaiduBce.Services.Bos;
using BaiduBce.Services.Bos.Model;
using BaiduBce.Services.Sts;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Web;

namespace BOSTest
{
    class Program
    {
        static string UriEncode(string input, bool encodeSlash = false)
        {
            StringBuilder builder = new StringBuilder();
            foreach (byte b in Encoding.UTF8.GetBytes(input))
            {
                if ((b >= 'a'&&b <= 'z') || (b >= 'A'&&b <= 'Z') || (b >= '0'&&b <= '9') || b == '_' || b == '-' || b == '~' || b == '.')
                {
                    builder.Append((char)b);
                }
                else if (b == '/')
                {
                    if (encodeSlash)
                    {
                        builder.Append("%2F");
                    }
                    else
                    {
                        builder.Append((char)b);
                    }
                }
                else
                {
                    builder.Append('%').Append(b.ToString("X2"));
                }
            }
            return builder.ToString();
        }

        static string Hex(byte[] data)
        {
            var sb = new StringBuilder();
            foreach (var b in data)
            {
                sb.Append(b.ToString("x2"));
            }
            return sb.ToString();
        }

        static string CanonicalRequest(HttpWebRequest req)
        {
            Uri uri = req.RequestUri;
            StringBuilder canonicalReq = new StringBuilder();
            canonicalReq.Append(req.Method).Append("\n").Append(UriEncode(Uri.UnescapeDataString(uri.AbsolutePath))).Append("\n");

            var parameters = HttpUtility.ParseQueryString(uri.Query);
            List<string> parameterStrings = new List<string>();
            foreach (KeyValuePair<string, string> entry in parameters)
            {
                parameterStrings.Add(UriEncode(entry.Key) + '=' + UriEncode(entry.Value));
            }
            parameterStrings.Sort();
            canonicalReq.Append(string.Join("&", parameterStrings.ToArray())).Append("\n");

            string host = uri.Host;
            if (!(uri.Scheme == "https"&&uri.Port == 443)&&!(uri.Scheme == "http"&&uri.Port == 80))
            {
                host += ":" + uri.Port;
            }
            canonicalReq.Append("host:" + UriEncode(host));
            return canonicalReq.ToString();
        }

        static void Main(string[] args)
        {
            string bucket = "mybucket";
            string key = "myfile";
            string ak = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
            string sk = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";
            DateTime now = DateTime.Now;
            int expirationInSeconds = 1200;

            HttpWebRequest req = WebRequest.Create("http://bj.bcebos.com/" + bucket + "/" + key) as HttpWebRequest;
            Uri uri = req.RequestUri;
            req.Method = "GET";

            string signDate = now.ToUniversalTime().ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ssK");
            Console.WriteLine(signDate);
            string authString = "bce-auth-v1/" + ak + "/" + signDate + "/" + expirationInSeconds;
            string signingKey = Hex(new HMACSHA256(Encoding.UTF8.GetBytes(sk)).ComputeHash(Encoding.UTF8.GetBytes(authString)));
            Console.WriteLine(signingKey);

            string canonicalRequestString = CanonicalRequest(req);
            Console.WriteLine(canonicalRequestString);

            string signature = Hex(new HMACSHA256(Encoding.UTF8.GetBytes(signingKey)).ComputeHash(Encoding.UTF8.GetBytes(canonicalRequestString)));
            string authorization = authString + "/host/" + signature;
            Console.WriteLine(authorization);

            req.Headers.Add("x-bce-date", signDate);
            req.Headers.Add(HttpRequestHeader.Authorization, authorization);

            HttpWebResponse res;
            string message = "";
            try
            {
                res = req.GetResponse() as HttpWebResponse;
            }
            catch (WebException e)
            {
                res = e.Response as HttpWebResponse;
                message = new StreamReader(res.GetResponseStream()).ReadToEnd();
            }
            Console.WriteLine((int)res.StatusCode);
            Console.WriteLine(res.Headers);
            Console.WriteLine(message);
            Console.ReadLine();
        }
    }
}

iOS Example

Users can refer to the following code to learn more about Baidu AI Cloud API authentication mechanism for iOS. The code contains two types of class code and calling code, the complete code address: Code. For the calling code, please see:

#import <XCTest/XCTest.h>
#import "BDCloudSigner.h"

@interface UT : XCTestCase
@end

@implementation UT

id<BDCloudSigner> createSigner() {
    BDCloudCredentials* credentials = [BDCloudCredentials new];
    credentials.accessKey = @"<access key>";
    credentials.secretKey = @"<secret key>";

    id<BDCloudSigner> signer = [[BDCloudAKSKSigner alloc] initWithCredentials:credentials];
    signer.expiredTimeInSeconds = 3600;

    return signer;
}

NSMutableURLRequest* createRequest() {
    // create url directly, or use NSURLComponents.
    NSURL* url = [NSURL URLWithString:@"http://bj.bcebos.com/v1/bucket/object?append"];

    // create request.
    NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:url];
    request.HTTPMethod = @"POST";
    [request setValue:@"<length>" forHTTPHeaderField:@"Content-Length"];
    [request setValue:@"<md5>" forHTTPHeaderField:@"Content-MD5"];
    [request setValue:@"text/plain" forHTTPHeaderField:@"Content-Type"];

    // custom metadata key should begin with lower case prefix 'x-bce-'.
    [request setValue:@"2017-01-08T21:42:30Z" forHTTPHeaderField:@"x-bce-user-metadata-createtime"];

    // Host will be set when call sign.
    //[request setValue:@"bj.bcebos.com" forHTTPHeaderField:@"Host"];

    return request;
}

void sign() {
    id<BDCloudSigner> signer = createSigner();
    NSMutableURLRequest* request = createRequest();
    if (![signer sign:request]) {
        return;
    }

    // url
    NSURL* fileURL = [NSURL fileURLWithPath:@"<file path>"];

    // send request
    // sample purpose, don't care task will running correctly.
    [[NSURLSession sharedSession] uploadTaskWithRequest:request
                                               fromFile:fileURL];
}

- (void)testAKSKSigner {
    sign();
}

@end
Previous
Include the Authentication String into the URL
Next
Troubleshooting of Common Signature Verification Errors